Skip to content

Oom/combined zod patch lazy loading#264342

Closed
sdesalas wants to merge 6 commits intoelastic:mainfrom
sdesalas:oom/combined-zod-patch-lazy-loading
Closed

Oom/combined zod patch lazy loading#264342
sdesalas wants to merge 6 commits intoelastic:mainfrom
sdesalas:oom/combined-zod-patch-lazy-loading

Conversation

@sdesalas
Copy link
Copy Markdown
Member

@sdesalas sdesalas commented Apr 20, 2026

Summary

Combination of 2 PRs for testing local memory impact:

Local testing results

main vs combined

Method

  • Source: kibana-memory-profile-*.csv in .vscode/mem-stats (column heap_used, bytes).
  • Warm-up exclusion: only rows at least 30 seconds after the first row’s timestamp_ms are included (first 30s of each run discarded).
  • Per-run metrics: peak heap_used (max after cutoff), average heap_used (mean after cutoff).
  • Sampling interval in these captures is 500 ms (~555–560 samples per run after the 30s cutoff).

Test conditions (local)

  • Kibana was started in --dev mode.
  • NODE_OPTIONS="--max_old_space_size=1400" — the 1400 in folder names refers to this V8 heap limit (MiB), not measured heap_used.

Datasets (folder names)

Dataset Run folders
main main-1400-round1, main-1400-round2, main-1400-round3
combined zod-patch + lazy loading combined-zod-patch-lazy-loading-1400-round1, combined-zod-patch-lazy-loading-1400-round2, combined-zod-patch-lazy-loading-1400-round3

Results (heapUsed, after first 30s)

main

Run folder Peak heapUsed Avg heapUsed
main-1400-round1 1235.7 MiB 1121.4 MiB
main-1400-round2 1257.1 MiB 1100.3 MiB
main-1400-round3 1242.6 MiB 1120.5 MiB
  • Mean of per-run peaks: ~1245.3 MiB
  • Mean of per-run averages: ~1114.0 MiB
  • Highest peak (any run): 1257.1 MiB (main-1400-round2)

combined zod-patch-lazy-loading

Run folder Peak heapUsed Avg heapUsed
combined-zod-patch-lazy-loading-1400-round1 1179.3 MiB 990.9 MiB
combined-zod-patch-lazy-loading-1400-round2 1194.2 MiB 1005.3 MiB
combined-zod-patch-lazy-loading-1400-round3 1189.2 MiB 1001.6 MiB
  • Mean of per-run peaks: ~1187.4 MiB
  • Mean of per-run averages: ~999.0 MiB
  • Highest peak (any run): 1194.2 MiB (combined-zod-patch-lazy-loading-1400-round2)

Which set is more memory efficient?

The combined zod-patch-lazy-loading branch is more memory efficient on these runs: lower peak and lower average heap_used after the 30s warm-up in every run, and lower means across the three runs (~58 MiB lower mean peak, ~115 MiB lower mean average vs main).

Logs

Bundled kibana.output.*.txt files in the same folders show no FATAL, OOM, or “JavaScript heap out of memory” lines in this sample.

@elasticmachine
Copy link
Copy Markdown
Contributor

🤖 Jobs for this PR can be triggered through checkboxes. 🚧

ℹ️ To trigger the CI, please tick the checkbox below 👇

  • Click to trigger kibana-pull-request for this PR!
  • Click to trigger kibana-deploy-project-from-pr for this PR!
  • Click to trigger kibana-deploy-cloud-from-pr for this PR!
  • Click to trigger kibana-entity-store-performance-from-pr for this PR!
  • Click to trigger kibana-storybooks-from-pr for this PR!

Comment on lines +44 to +57
return new Proxy({} as T, {
get(_target, prop) {
const real = materialize() as unknown as Record<PropertyKey, unknown>;
const value = real[prop];
if (typeof value === 'function') {
return (value as (...args: unknown[]) => unknown).bind(real);
}
return value;
},
has(_target, prop) {
return prop in (materialize() as unknown as object);
},
});
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Low src/lazy_gcable_object.ts:44

The Proxy lacks a set trap, so assignments like lazyThing.prop = value write to the empty {} target rather than the materialized object. The value is stored on the proxy target but never propagated to the real object; subsequent reads return the unmodified value from the materialized object, causing the assignment to be silently lost.

-  return new Proxy({} as T, {
-    get(_target, prop) {
-      const real = materialize() as unknown as Record<PropertyKey, unknown>;
-      const value = real[prop];
-      if (typeof value === 'function') {
-        return (value as (...args: unknown[]) => unknown).bind(real);
-      }
-      return value;
-    },
-    has(_target, prop) {
-      return prop in (materialize() as unknown as object);
-    },
-  });
🤖 Copy this AI Prompt to have your agent fix this:
In file src/platform/packages/shared/kbn-lazy-object/src/lazy_gcable_object.ts around lines 44-57:

The `Proxy` lacks a `set` trap, so assignments like `lazyThing.prop = value` write to the empty `{}` target rather than the materialized object. The value is stored on the proxy target but never propagated to the real object; subsequent reads return the unmodified value from the materialized object, causing the assignment to be silently lost.

Evidence trail:
src/platform/packages/shared/kbn-lazy-object/src/lazy_gcable_object.ts lines 44-53 show the Proxy with only `get` and `has` traps, no `set` trap. JavaScript Proxy spec: without a `set` trap, assignments go to the target (empty `{}`) while the `get` trap reads from `materialize()`.

@sdesalas sdesalas closed this Apr 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants