Skip to content

perf(scheduler): stream snapshot JSON to avoid OOM on large clusters#1564

Merged
enoodle merged 1 commit into
kai-scheduler:mainfrom
yysindi:yusuf/upstream-stream-snapshot
May 11, 2026
Merged

perf(scheduler): stream snapshot JSON to avoid OOM on large clusters#1564
enoodle merged 1 commit into
kai-scheduler:mainfrom
yysindi:yusuf/upstream-stream-snapshot

Conversation

@yysindi
Copy link
Copy Markdown
Contributor

@yysindi yysindi commented May 7, 2026

Description

The /get-snapshot endpoint buffers the entire cluster snapshot as a JSON []byte via json.Marshal, then copies it again via string() conversion before writing to the zip writer — 3 copies of multi-GB data in memory for large clusters. On a 682-node / 15K-pod cluster this causes OOM kills even at 32Gi memory limit.

This PR streams JSON per-element directly into the zip writer using a jsonStream / jsonObjectWriter abstraction that encodes one K8s object at a time. Peak memory drops from ~3x the JSON payload to just the raw Go objects held by the informer cache plus trivial streaming buffers (~1x).

Before

rawObjects in memory (~X GB)
+ json.Marshal output []byte (~X GB)
+ string(jsonBytes) copy (~X GB)
= ~3X peak memory during snapshot

After

rawObjects in memory (~X GB)
+ per-element encoder buffer (~KB)
= ~1X peak memory during snapshot

Checklist

  • Self-reviewed
  • Added/updated tests (if needed) — existing test passes, validates roundtrip correctness
  • Updated documentation (if needed) — N/A

Breaking Changes

None. The HTTP response format is identical (ZIP containing snapshot.json). Consumers require no changes.

Additional Notes

  • The jsonStream helper uses json.Encoder.Encode per-element, which appends a \n after each value. This produces valid JSON with slightly different whitespace than json.Marshal — any JSON parser handles it correctly.
  • We hit this at scale running KAI on GKE with 682 H200 nodes and ~15K pods. The scheduler OOMs when serving /get-snapshot even with a 32Gi memory limit. This change eliminates the serialization overhead entirely.

Summary by CodeRabbit

  • Refactor
    • Optimized snapshot endpoint to stream JSON responses more efficiently, reducing memory usage and improving performance when generating large snapshots.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 7, 2026

Review Change Stack

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 77ab3c5c-d172-456f-b791-e0373e1be20f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The snapshot endpoint refactoring replaces in-memory JSON marshaling with streaming JSON directly into a ZIP file. A new internal JSON streaming abstraction emits object fields and slices incrementally. The HTTP handler creates a ZIP entry for snapshot.json and streams content via orchestration functions that list resources and discovery data with granular error handling and nil fallbacks.

Changes

Snapshot Endpoint Streaming Refactor

Layer / File(s) Summary
JSON Streaming Abstractions
pkg/scheduler/plugins/snapshot/snapshot.go
New internal types jsonStream, jsonObjectWriter, and jsonFieldWriter function type enable incremental JSON object and field emission to an io.Writer.
Endpoint Handler Rewrite
pkg/scheduler/plugins/snapshot/snapshot.go
serveSnapshot sets ZIP attachment headers, creates a zip.Writer, initializes a snapshot.json entry, calls writeSnapshot to stream JSON content, and closes the ZIP with error logging.
Snapshot Content Orchestration
pkg/scheduler/plugins/snapshot/snapshot.go
writeSnapshot(ctx, writer) constructs the top-level JSON structure, emitting configuration, scheduler parameters, raw Kubernetes objects, and discovery snapshot via streaming field helpers.
Raw Objects & Discovery Serialization
pkg/scheduler/plugins/snapshot/snapshot.go
writeRawObjects(stream) lists resources from session cache using generic helpers, logs per-resource failures, and writes empty slices on error. writeDiscoverySnapshot(ctx, stream) and getDiscoverySnapshot(ctx) serialize discovery data with nil fallback for server version and resources on failure.
JSON Streaming Implementation
pkg/scheduler/plugins/snapshot/snapshot.go
Low-level JSON writers: newJSONStream constructor, writeRaw and writeValue primitives, writeObject for object wrappers, writeFieldName and writeField for key-value pairs, writeFields dispatcher, plus field composers (valueField, streamedField, listedSliceField, sliceField), and underlying implementations (writeListedSlice, writeSliceField, writeSlice) with special nil/empty slice handling.
In-Memory to Streaming Migration
pkg/scheduler/plugins/snapshot/snapshot.go
Complete replacement of the previous path (manual per-resource List* calls, explicit RawKubernetesObjects field population, json.Marshal of snapshot struct, and strings.NewReader copy) with lazy-listing streaming writers that avoid materializing the full JSON document before ZIP creation.
Dependency Cleanup
pkg/scheduler/plugins/snapshot/snapshot.go
Import statement removes strings dependency, aligning with the new streaming approach that no longer requires string marshaling.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 Hops through the snapshot stream,
No marshals piling high,
JSON flows to ZIP so clean,
Resource by resource fly.
Errors log, but onward go—
Streaming makes it light!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'perf(scheduler): stream snapshot JSON to avoid OOM on large clusters' clearly and specifically describes the main change: a performance optimization in the scheduler that streams JSON to prevent out-of-memory errors.
Description check ✅ Passed The description is comprehensive and well-structured, covering the problem statement, solution approach, before/after memory comparison, checklist completion, breaking changes clarification, and additional technical notes about JSON streaming behavior.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@enoodle
Copy link
Copy Markdown
Collaborator

enoodle commented May 7, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 7, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
pkg/scheduler/plugins/snapshot/snapshot.go (1)

100-118: ⚡ Quick win

Use defer for ZIP cleanup once the writer is created.

zipWriter.Close() is currently duplicated across the success and failure paths. Deferring the close right after Create() succeeds makes cleanup deterministic and reduces the chance of missing it on a future early return.

As per coding guidelines, "Use defer for cleanup and state restoration".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/scheduler/plugins/snapshot/snapshot.go` around lines 100 - 118, After
zipWriter is created and jsonWriter is assigned (after
zipWriter.Create(SnapshotFileName) succeeds), add a deferred cleanup that closes
zipWriter and logs any close error instead of calling zipWriter.Close() in both
success and error paths; update the code around zipWriter, jsonWriter,
SnapshotFileName and sp.writeSnapshot(...) to remove the duplicate explicit
Close() calls and rely on the deferred func() { if err := zipWriter.Close(); err
!= nil { log.InfraLogger.Errorf("Error closing snapshot zip: %v", err) } } to
ensure deterministic cleanup and error logging.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/scheduler/plugins/snapshot/snapshot.go`:
- Around line 285-290: The function writeListedSlice currently swallows listing
errors by turning them into empty slices; instead propagate the failure so
callers can detect it: in writeListedSlice (with signature writeListedSlice[T
any](object *jsonObjectWriter, fieldName string, list func() ([]T, error),
errorMessage string)) remove the line that sets values = []T{} and return the
original err (after logging) so the error bubbles up; alternatively, if you
intentionally want the JSON to show null, explicitly write a null field on
object (use the jsonObjectWriter null/SetNull/WriteNullField method) and then
return nil—do not silently replace errors with an empty array.
- Around line 188-191: The code currently clears discoverySnapshot.Resources on
any error from discoveryClient.ServerGroupsAndResources(), discarding partial
results; change this so you only set discoverySnapshot.Resources = nil if the
returned slice itself is nil—i.e., call
discoveryClient.ServerGroupsAndResources(), log the error as you do with
log.InfraLogger.V(2).Warnf, but do not overwrite discoverySnapshot.Resources
when it contains partial data (leave the returned slice intact), only set it to
nil when the function actually returned a nil slice.

---

Nitpick comments:
In `@pkg/scheduler/plugins/snapshot/snapshot.go`:
- Around line 100-118: After zipWriter is created and jsonWriter is assigned
(after zipWriter.Create(SnapshotFileName) succeeds), add a deferred cleanup that
closes zipWriter and logs any close error instead of calling zipWriter.Close()
in both success and error paths; update the code around zipWriter, jsonWriter,
SnapshotFileName and sp.writeSnapshot(...) to remove the duplicate explicit
Close() calls and rely on the deferred func() { if err := zipWriter.Close(); err
!= nil { log.InfraLogger.Errorf("Error closing snapshot zip: %v", err) } } to
ensure deterministic cleanup and error logging.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f93de662-8c62-4872-b058-2846d538b073

📥 Commits

Reviewing files that changed from the base of the PR and between ce3ba42 and d338e7d.

📒 Files selected for processing (1)
  • pkg/scheduler/plugins/snapshot/snapshot.go

Comment thread pkg/scheduler/plugins/snapshot/snapshot.go
Comment thread pkg/scheduler/plugins/snapshot/snapshot.go
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 7, 2026

📊 Performance Benchmark Results

Comparing PR (yusuf/upstream-stream-snapshot) vs main branch — click to expand
goos: linux
goarch: amd64
pkg: github.com/kai-scheduler/KAI-scheduler/pkg/scheduler/actions
cpu: AMD EPYC 7763 64-Core Processor                
                                    │ main-bench.txt │           pr-bench.txt            │
                                    │     sec/op     │   sec/op     vs base              │
AllocateAction_SmallCluster-4            108.4m ± 0%   108.2m ± 0%       ~ (p=0.310 n=6)
AllocateAction_MediumCluster-4           136.3m ± 1%   136.7m ± 1%       ~ (p=0.485 n=6)
AllocateAction_LargeCluster-4            212.9m ± 9%   211.9m ± 8%       ~ (p=0.310 n=6)
ReclaimAction_SmallCluster-4             103.0m ± 0%   103.0m ± 0%       ~ (p=0.485 n=6)
ReclaimAction_MediumCluster-4            106.1m ± 1%   106.1m ± 1%       ~ (p=1.000 n=6)
PreemptAction_SmallCluster-4             103.8m ± 0%   103.8m ± 0%       ~ (p=0.699 n=6)
PreemptAction_MediumCluster-4            112.7m ± 1%   112.5m ± 2%       ~ (p=0.699 n=6)
ConsolidationAction_SmallCluster-4       124.3m ± 1%   123.8m ± 2%       ~ (p=0.240 n=6)
ConsolidationAction_MediumCluster-4      290.6m ± 1%   289.4m ± 3%       ~ (p=0.818 n=6)
FullSchedulingCycle_SmallCluster-4       105.6m ± 0%   105.5m ± 0%       ~ (p=0.394 n=6)
FullSchedulingCycle_MediumCluster-4      122.3m ± 2%   120.7m ± 1%       ~ (p=0.093 n=6)
FullSchedulingCycle_LargeCluster-4       162.7m ± 0%   164.1m ± 1%  +0.87% (p=0.041 n=6)
ManyQueues_MediumCluster-4               138.6m ± 2%   138.3m ± 2%       ~ (p=0.394 n=6)
GangScheduling_MediumCluster-4           157.6m ± 2%   159.6m ± 1%       ~ (p=0.180 n=6)
geomean                                  135.0m        134.9m       -0.05%

                                    │ main-bench.txt │            pr-bench.txt            │
                                    │      B/op      │     B/op      vs base              │
AllocateAction_SmallCluster-4           2.226Mi ± 0%   2.226Mi ± 0%       ~ (p=0.818 n=6)
AllocateAction_MediumCluster-4          12.09Mi ± 0%   12.09Mi ± 0%       ~ (p=0.937 n=6)
AllocateAction_LargeCluster-4           41.70Mi ± 0%   41.70Mi ± 0%       ~ (p=0.180 n=6)
ReclaimAction_SmallCluster-4            910.8Ki ± 1%   912.8Ki ± 1%       ~ (p=0.589 n=6)
ReclaimAction_MediumCluster-4           2.997Mi ± 0%   3.000Mi ± 0%       ~ (p=0.180 n=6)
PreemptAction_SmallCluster-4            1.130Mi ± 0%   1.131Mi ± 0%       ~ (p=0.818 n=6)
PreemptAction_MediumCluster-4           4.599Mi ± 0%   4.601Mi ± 0%       ~ (p=0.589 n=6)
ConsolidationAction_SmallCluster-4      9.777Mi ± 0%   9.775Mi ± 0%       ~ (p=0.699 n=6)
ConsolidationAction_MediumCluster-4     88.72Mi ± 0%   88.72Mi ± 0%       ~ (p=0.818 n=6)
FullSchedulingCycle_SmallCluster-4      1.421Mi ± 1%   1.419Mi ± 0%       ~ (p=0.180 n=6)
FullSchedulingCycle_MediumCluster-4     7.042Mi ± 0%   7.042Mi ± 0%       ~ (p=0.937 n=6)
FullSchedulingCycle_LargeCluster-4      23.11Mi ± 0%   23.11Mi ± 0%       ~ (p=0.394 n=6)
ManyQueues_MediumCluster-4              16.54Mi ± 0%   16.54Mi ± 0%       ~ (p=0.485 n=6)
GangScheduling_MediumCluster-4          17.49Mi ± 0%   17.49Mi ± 0%       ~ (p=0.310 n=6)
geomean                                 7.144Mi        7.146Mi       +0.03%

                                    │ main-bench.txt │           pr-bench.txt            │
                                    │   allocs/op    │  allocs/op   vs base              │
AllocateAction_SmallCluster-4            35.02k ± 0%   35.02k ± 0%       ~ (p=0.186 n=6)
AllocateAction_MediumCluster-4           312.8k ± 0%   312.8k ± 0%       ~ (p=0.968 n=6)
AllocateAction_LargeCluster-4            1.338M ± 0%   1.338M ± 0%       ~ (p=0.457 n=6)
ReclaimAction_SmallCluster-4             8.204k ± 0%   8.204k ± 0%       ~ (p=0.567 n=6)
ReclaimAction_MediumCluster-4            26.15k ± 0%   26.15k ± 0%       ~ (p=0.645 n=6)
PreemptAction_SmallCluster-4             11.90k ± 0%   11.90k ± 0%       ~ (p=0.721 n=6)
PreemptAction_MediumCluster-4            41.94k ± 0%   41.94k ± 0%       ~ (p=0.519 n=6)
ConsolidationAction_SmallCluster-4       127.7k ± 0%   127.7k ± 0%       ~ (p=0.818 n=6)
ConsolidationAction_MediumCluster-4      1.298M ± 0%   1.298M ± 0%       ~ (p=0.394 n=6)
FullSchedulingCycle_SmallCluster-4       20.68k ± 0%   20.68k ± 0%       ~ (p=0.848 n=6)
FullSchedulingCycle_MediumCluster-4      168.3k ± 0%   168.3k ± 0%       ~ (p=0.604 n=6)
FullSchedulingCycle_LargeCluster-4       698.7k ± 0%   698.7k ± 0%       ~ (p=0.838 n=6)
ManyQueues_MediumCluster-4               350.7k ± 0%   350.7k ± 0%       ~ (p=0.859 n=6)
GangScheduling_MediumCluster-4           571.8k ± 0%   571.8k ± 0%       ~ (p=0.563 n=6)
geomean                                  119.9k        119.9k       -0.00%

pkg: github.com/kai-scheduler/KAI-scheduler/pkg/scheduler/actions/integration_tests/reclaim
                            │ main-bench.txt │           pr-bench.txt            │
                            │     sec/op     │   sec/op     vs base              │
ReclaimLargeJobs_10Node-4        105.4m ± 1%   105.4m ± 0%       ~ (p=0.699 n=6)
ReclaimLargeJobs_50Node-4        229.6m ± 1%   231.8m ± 1%       ~ (p=0.093 n=6)
ReclaimLargeJobs_100Node-4       385.4m ± 8%   380.8m ± 3%       ~ (p=0.589 n=6)
ReclaimLargeJobs_200Node-4       754.1m ± 3%   769.4m ± 5%       ~ (p=0.132 n=6)
ReclaimLargeJobs_500Node-4        2.418 ± 2%    2.439 ± 2%       ~ (p=0.240 n=6)
ReclaimLargeJobs_1000Node-4       7.258 ± 3%    7.368 ± 3%       ~ (p=0.310 n=6)
geomean                          705.6m        710.5m       +0.70%

                            │ main-bench.txt │            pr-bench.txt            │
                            │      B/op      │     B/op      vs base              │
ReclaimLargeJobs_10Node-4       1.979Mi ± 3%   1.990Mi ± 3%       ~ (p=0.310 n=6)
ReclaimLargeJobs_50Node-4       59.65Mi ± 0%   59.64Mi ± 0%       ~ (p=0.589 n=6)
ReclaimLargeJobs_100Node-4      119.4Mi ± 0%   119.4Mi ± 0%       ~ (p=0.818 n=6)
ReclaimLargeJobs_200Node-4      241.0Mi ± 0%   241.0Mi ± 0%       ~ (p=0.394 n=6)
ReclaimLargeJobs_500Node-4      618.3Mi ± 0%   618.2Mi ± 0%       ~ (p=0.818 n=6)
ReclaimLargeJobs_1000Node-4     1.262Gi ± 0%   1.262Gi ± 0%       ~ (p=0.240 n=6)
geomean                         118.1Mi        118.2Mi       +0.09%

                            │ main-bench.txt │           pr-bench.txt            │
                            │   allocs/op    │  allocs/op   vs base              │
ReclaimLargeJobs_10Node-4        21.96k ± 3%   21.97k ± 3%       ~ (p=0.288 n=6)
ReclaimLargeJobs_50Node-4        801.9k ± 0%   801.9k ± 0%       ~ (p=0.818 n=6)
ReclaimLargeJobs_100Node-4       1.596M ± 0%   1.596M ± 0%       ~ (p=0.937 n=6)
ReclaimLargeJobs_200Node-4       3.182M ± 0%   3.182M ± 0%       ~ (p=0.818 n=6)
ReclaimLargeJobs_500Node-4       7.963M ± 0%   7.963M ± 0%       ~ (p=0.589 n=6)
ReclaimLargeJobs_1000Node-4      16.01M ± 0%   16.01M ± 0%       ~ (p=0.223 n=6)
geomean                          1.500M        1.500M       +0.00%

pkg: github.com/kai-scheduler/KAI-scheduler/pkg/scheduler/actions/reclaim
                            │ main-bench.txt │         pr-bench.txt          │
                            │     sec/op     │    sec/op     vs base         │
ReclaimWithMissingPVCJobs-4     2.329m ± 10%   2.388m ± 11%  ~ (p=0.485 n=6)

                            │ main-bench.txt │         pr-bench.txt          │
                            │      B/op      │     B/op      vs base         │
ReclaimWithMissingPVCJobs-4     8.211Ki ± 2%   8.211Ki ± 2%  ~ (p=1.000 n=6)

                            │ main-bench.txt │        pr-bench.txt         │
                            │   allocs/op    │ allocs/op   vs base         │
ReclaimWithMissingPVCJobs-4       154.0 ± 1%   154.0 ± 1%  ~ (p=1.000 n=6)

Legend

  • 📉 Negative delta = Performance improvement (faster)
  • 📈 Positive delta = Performance regression (slower)
  • p-value < 0.05 indicates statistically significant change
Raw benchmark data

PR branch:

goos: linux
goarch: amd64
pkg: github.com/kai-scheduler/KAI-scheduler/pkg/scheduler/actions
cpu: AMD EPYC 7763 64-Core Processor                
BenchmarkAllocateAction_SmallCluster-4         	      10	 108247224 ns/op	 2331137 B/op	   35020 allocs/op
BenchmarkAllocateAction_SmallCluster-4         	      10	 108232783 ns/op	 2334684 B/op	   35026 allocs/op
BenchmarkAllocateAction_SmallCluster-4         	      10	 108352309 ns/op	 2342642 B/op	   35022 allocs/op
BenchmarkAllocateAction_SmallCluster-4         	      10	 108194668 ns/op	 2333893 B/op	   35023 allocs/op
BenchmarkAllocateAction_SmallCluster-4         	      10	 108216958 ns/op	 2345094 B/op	   35018 allocs/op
BenchmarkAllocateAction_SmallCluster-4         	      10	 108196160 ns/op	 2332059 B/op	   35020 allocs/op
BenchmarkAllocateAction_MediumCluster-4        	       8	 137623942 ns/op	12696288 B/op	  312816 allocs/op
BenchmarkAllocateAction_MediumCluster-4        	       8	 135328466 ns/op	12673871 B/op	  312808 allocs/op
BenchmarkAllocateAction_MediumCluster-4        	       8	 135886901 ns/op	12671539 B/op	  312795 allocs/op
BenchmarkAllocateAction_MediumCluster-4        	       8	 137111804 ns/op	12672979 B/op	  312802 allocs/op
BenchmarkAllocateAction_MediumCluster-4        	       8	 137097542 ns/op	12673417 B/op	  312802 allocs/op
BenchmarkAllocateAction_MediumCluster-4        	       8	 136335560 ns/op	12672292 B/op	  312798 allocs/op
BenchmarkAllocateAction_LargeCluster-4         	       5	 211787412 ns/op	43723483 B/op	 1338195 allocs/op
BenchmarkAllocateAction_LargeCluster-4         	       5	 211445786 ns/op	43724048 B/op	 1338193 allocs/op
BenchmarkAllocateAction_LargeCluster-4         	       5	 213781410 ns/op	43735824 B/op	 1338176 allocs/op
BenchmarkAllocateAction_LargeCluster-4         	       5	 211373795 ns/op	43721889 B/op	 1338184 allocs/op
BenchmarkAllocateAction_LargeCluster-4         	       5	 212034091 ns/op	43729246 B/op	 1338188 allocs/op
BenchmarkAllocateAction_LargeCluster-4         	       5	 229784424 ns/op	43720340 B/op	 1338174 allocs/op
BenchmarkReclaimAction_SmallCluster-4          	      10	 103067968 ns/op	  930448 B/op	    8182 allocs/op
BenchmarkReclaimAction_SmallCluster-4          	      10	 102985844 ns/op	  926021 B/op	    8190 allocs/op
BenchmarkReclaimAction_SmallCluster-4          	      10	 103012084 ns/op	  938269 B/op	    8203 allocs/op
BenchmarkReclaimAction_SmallCluster-4          	      10	 102969168 ns/op	  938725 B/op	    8206 allocs/op
BenchmarkReclaimAction_SmallCluster-4          	      10	 102998499 ns/op	  931242 B/op	    8206 allocs/op
BenchmarkReclaimAction_SmallCluster-4          	      10	 102963256 ns/op	  938650 B/op	    8206 allocs/op
BenchmarkReclaimAction_MediumCluster-4         	      10	 107309312 ns/op	 3150766 B/op	   26147 allocs/op
BenchmarkReclaimAction_MediumCluster-4         	      10	 106176424 ns/op	 3146096 B/op	   26149 allocs/op
BenchmarkReclaimAction_MediumCluster-4         	      10	 106401542 ns/op	 3142434 B/op	   26149 allocs/op
BenchmarkReclaimAction_MediumCluster-4         	      10	 106078495 ns/op	 3138128 B/op	   26145 allocs/op
BenchmarkReclaimAction_MediumCluster-4         	      10	 105928999 ns/op	 3146037 B/op	   26148 allocs/op
BenchmarkReclaimAction_MediumCluster-4         	      10	 105912412 ns/op	 3146088 B/op	   26149 allocs/op
BenchmarkPreemptAction_SmallCluster-4          	      10	 103778403 ns/op	 1188288 B/op	   11899 allocs/op
BenchmarkPreemptAction_SmallCluster-4          	      10	 103757561 ns/op	 1188284 B/op	   11898 allocs/op
BenchmarkPreemptAction_SmallCluster-4          	      10	 103791152 ns/op	 1184527 B/op	   11897 allocs/op
BenchmarkPreemptAction_SmallCluster-4          	      10	 103811414 ns/op	 1184736 B/op	   11898 allocs/op
BenchmarkPreemptAction_SmallCluster-4          	      10	 103849746 ns/op	 1184501 B/op	   11897 allocs/op
BenchmarkPreemptAction_SmallCluster-4          	      10	 103749379 ns/op	 1188135 B/op	   11897 allocs/op
BenchmarkPreemptAction_MediumCluster-4         	       9	 112659046 ns/op	 4816374 B/op	   41936 allocs/op
BenchmarkPreemptAction_MediumCluster-4         	       9	 111905735 ns/op	 4832235 B/op	   41940 allocs/op
BenchmarkPreemptAction_MediumCluster-4         	       9	 113258105 ns/op	 4816121 B/op	   41936 allocs/op
BenchmarkPreemptAction_MediumCluster-4         	       9	 114247144 ns/op	 4824873 B/op	   41939 allocs/op
BenchmarkPreemptAction_MediumCluster-4         	       9	 112333428 ns/op	 4824543 B/op	   41937 allocs/op
BenchmarkPreemptAction_MediumCluster-4         	       9	 112259723 ns/op	 4824291 B/op	   41936 allocs/op
BenchmarkConsolidationAction_SmallCluster-4    	       9	 123772410 ns/op	10252202 B/op	  127742 allocs/op
BenchmarkConsolidationAction_SmallCluster-4    	       9	 121933547 ns/op	10247814 B/op	  127718 allocs/op
BenchmarkConsolidationAction_SmallCluster-4    	       9	 123832388 ns/op	10249236 B/op	  127722 allocs/op
BenchmarkConsolidationAction_SmallCluster-4    	       9	 123752058 ns/op	10252109 B/op	  127755 allocs/op

Main branch:

goos: linux
goarch: amd64
pkg: github.com/kai-scheduler/KAI-scheduler/pkg/scheduler/actions
cpu: AMD EPYC 7763 64-Core Processor                
BenchmarkAllocateAction_SmallCluster-4         	      10	 108597202 ns/op	 2337044 B/op	   35034 allocs/op
BenchmarkAllocateAction_SmallCluster-4         	      10	 107985649 ns/op	 2332510 B/op	   35025 allocs/op
BenchmarkAllocateAction_SmallCluster-4         	      10	 108198677 ns/op	 2333756 B/op	   35025 allocs/op
BenchmarkAllocateAction_SmallCluster-4         	      10	 108471293 ns/op	 2333919 B/op	   35018 allocs/op
BenchmarkAllocateAction_SmallCluster-4         	      10	 108537594 ns/op	 2333245 B/op	   35024 allocs/op
BenchmarkAllocateAction_SmallCluster-4         	      10	 108288262 ns/op	 2333898 B/op	   35024 allocs/op
BenchmarkAllocateAction_MediumCluster-4        	       8	 136274675 ns/op	12673644 B/op	  312805 allocs/op
BenchmarkAllocateAction_MediumCluster-4        	       8	 136304618 ns/op	12673314 B/op	  312804 allocs/op
BenchmarkAllocateAction_MediumCluster-4        	       8	 135753013 ns/op	12674763 B/op	  312799 allocs/op
BenchmarkAllocateAction_MediumCluster-4        	       8	 136042117 ns/op	12673089 B/op	  312804 allocs/op
BenchmarkAllocateAction_MediumCluster-4        	       8	 136592271 ns/op	12672759 B/op	  312798 allocs/op
BenchmarkAllocateAction_MediumCluster-4        	       8	 137041984 ns/op	12672729 B/op	  312801 allocs/op
BenchmarkAllocateAction_LargeCluster-4         	       5	 212607584 ns/op	43722648 B/op	 1338185 allocs/op
BenchmarkAllocateAction_LargeCluster-4         	       5	 215904522 ns/op	43720598 B/op	 1338172 allocs/op
BenchmarkAllocateAction_LargeCluster-4         	       5	 231912735 ns/op	43722617 B/op	 1338182 allocs/op
BenchmarkAllocateAction_LargeCluster-4         	       5	 212034002 ns/op	43723171 B/op	 1338191 allocs/op
BenchmarkAllocateAction_LargeCluster-4         	       5	 213253831 ns/op	43721411 B/op	 1338177 allocs/op
BenchmarkAllocateAction_LargeCluster-4         	       5	 211852736 ns/op	43721512 B/op	 1338177 allocs/op
BenchmarkReclaimAction_SmallCluster-4          	      10	 103084453 ns/op	  929796 B/op	    8177 allocs/op
BenchmarkReclaimAction_SmallCluster-4          	      10	 102992581 ns/op	  930072 B/op	    8191 allocs/op
BenchmarkReclaimAction_SmallCluster-4          	      10	 103094834 ns/op	  938036 B/op	    8205 allocs/op
BenchmarkReclaimAction_SmallCluster-4          	      10	 103046421 ns/op	  934333 B/op	    8204 allocs/op
BenchmarkReclaimAction_SmallCluster-4          	      10	 102946315 ns/op	  930989 B/op	    8204 allocs/op
BenchmarkReclaimAction_SmallCluster-4          	      10	 102986504 ns/op	  938360 B/op	    8205 allocs/op
BenchmarkReclaimAction_MediumCluster-4         	      10	 107006150 ns/op	 3138374 B/op	   26146 allocs/op
BenchmarkReclaimAction_MediumCluster-4         	      10	 105956604 ns/op	 3142157 B/op	   26148 allocs/op
BenchmarkReclaimAction_MediumCluster-4         	      10	 106129660 ns/op	 3143207 B/op	   26147 allocs/op
BenchmarkReclaimAction_MediumCluster-4         	      10	 106075949 ns/op	 3142112 B/op	   26148 allocs/op
BenchmarkReclaimAction_MediumCluster-4         	      10	 106068394 ns/op	 3145916 B/op	   26148 allocs/op
BenchmarkReclaimAction_MediumCluster-4         	      10	 106186813 ns/op	 3146004 B/op	   26149 allocs/op
BenchmarkPreemptAction_SmallCluster-4          	      10	 104050246 ns/op	 1188317 B/op	   11898 allocs/op
BenchmarkPreemptAction_SmallCluster-4          	      10	 103790905 ns/op	 1184565 B/op	   11897 allocs/op
BenchmarkPreemptAction_SmallCluster-4          	      10	 103798266 ns/op	 1188527 B/op	   11899 allocs/op
BenchmarkPreemptAction_SmallCluster-4          	      10	 103806880 ns/op	 1184151 B/op	   11896 allocs/op
BenchmarkPreemptAction_SmallCluster-4          	      10	 103737224 ns/op	 1184677 B/op	   11898 allocs/op
BenchmarkPreemptAction_SmallCluster-4          	      10	 103810901 ns/op	 1180516 B/op	   11895 allocs/op
BenchmarkPreemptAction_MediumCluster-4         	       9	 113532603 ns/op	 4820217 B/op	   41935 allocs/op
BenchmarkPreemptAction_MediumCluster-4         	       9	 112814960 ns/op	 4824223 B/op	   41937 allocs/op
BenchmarkPreemptAction_MediumCluster-4         	       9	 113010577 ns/op	 4824628 B/op	   41938 allocs/op
BenchmarkPreemptAction_MediumCluster-4         	       9	 112272008 ns/op	 4820412 B/op	   41936 allocs/op
BenchmarkPreemptAction_MediumCluster-4         	       9	 112521328 ns/op	 4815502 B/op	   41932 allocs/op
BenchmarkPreemptAction_MediumCluster-4         	       9	 112392242 ns/op	 4824812 B/op	   41939 allocs/op
BenchmarkConsolidationAction_SmallCluster-4    	       9	 123539359 ns/op	10252723 B/op	  127756 allocs/op
BenchmarkConsolidationAction_SmallCluster-4    	       9	 124393476 ns/op	10247427 B/op	  127704 allocs/op
BenchmarkConsolidationAction_SmallCluster-4    	       9	 124771904 ns/op	10247372 B/op	  127715 allocs/op
BenchmarkConsolidationAction_SmallCluster-4    	       9	 124382923 ns/op	10253260 B/op	  127754 allocs/op

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 7, 2026

Merging this branch will increase overall coverage

Impacted Packages Coverage Δ 🤖
github.com/kai-scheduler/KAI-scheduler/pkg/scheduler/plugins/snapshot 66.36% (+13.73%) 🎉

Coverage by file

Changed files (no unit tests)

Changed File Coverage Δ Total Covered Missed 🤖
github.com/kai-scheduler/KAI-scheduler/pkg/scheduler/plugins/snapshot/snapshot.go 66.36% (+13.73%) 110 (-4) 73 (+13) 37 (-17) 🎉

Please note that the "Total", "Covered", and "Missed" counts above refer to code statements instead of lines of code. The value in brackets refers to the test coverage of that file in the old version of the code.

@enoodle
Copy link
Copy Markdown
Collaborator

enoodle commented May 7, 2026

@yysindi Thanks for the fix!
You need to sign the commit and optionally you can also add a changelog entry.

The /get-snapshot endpoint was buffering the entire snapshot as a JSON
[]byte via json.Marshal, then copying it again via string() conversion
before writing to the zip — 3 copies of multi-GB data in memory for
large clusters (682 nodes, 15K pods). This caused OOM kills even at
32Gi memory limit.

Stream JSON per-element directly into the zip writer using a jsonStream
helper that encodes one object at a time. Peak memory drops from ~3x
the JSON payload to just the raw Go objects plus trivial streaming
buffers (~1x).

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Signed-off-by: yysindi <[email protected]>
@yysindi yysindi force-pushed the yusuf/upstream-stream-snapshot branch from d338e7d to 40d7a4a Compare May 8, 2026 15:37
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 8, 2026

Merging this branch will increase overall coverage

Impacted Packages Coverage Δ 🤖
github.com/kai-scheduler/KAI-scheduler/pkg/scheduler/plugins/snapshot 69.16% (+16.53%) 🎉

Coverage by file

Changed files (no unit tests)

Changed File Coverage Δ Total Covered Missed 🤖
github.com/kai-scheduler/KAI-scheduler/pkg/scheduler/plugins/snapshot/snapshot.go 69.16% (+16.53%) 107 (-7) 74 (+14) 33 (-21) 🎉

Please note that the "Total", "Covered", and "Missed" counts above refer to code statements instead of lines of code. The value in brackets refers to the test coverage of that file in the old version of the code.

@itsomri itsomri added this pull request to the merge queue May 10, 2026
@itsomri itsomri removed this pull request from the merge queue due to a manual request May 10, 2026
@enoodle enoodle added this pull request to the merge queue May 11, 2026
@enoodle enoodle added the backport v0.14 PRs that need backport to v0.14 branch label May 11, 2026
Merged via the queue into kai-scheduler:main with commit 1b591f4 May 11, 2026
14 of 16 checks passed
@KaiPilotBot
Copy link
Copy Markdown

Successfully created backport PR for v0.14:

@yysindi yysindi deleted the yusuf/upstream-stream-snapshot branch May 11, 2026 11:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport v0.14 PRs that need backport to v0.14 branch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants