fix(zod): reduce per-schema heap cost via shared method references#263121
fix(zod): reduce per-schema heap cost via shared method references#263121gsoldevila merged 10 commits intoelastic:mainfrom
Conversation
5826139 to
b7e99a8
Compare
|
/ci |
## Problem Zod v4 assigns every schema method (`parse`, `check`, `optional`, `email`, etc.) as an own-property arrow function on each schema instance. Because arrow functions close over `inst`, each instance gets ~50 unique function objects. For applications that define many schemas this causes significant heap pressure. Rough measurements (Node.js, `--expose-gc`): | Schema | Before | After | |--------|--------|-------| | `z.string()` | ~12.8 KB | ~8.3 KB | | (relative) | baseline | **–35%** | ## Fix Builder-style methods (`check`, `optional`, `nullable`, `clone`, `describe`, string/number/date validators, object methods, etc.) are now defined once as named `function` declarations at module scope and use `this` for context. All instances share the same ~50 function objects for these methods. **Not changed:** parse/safeParse/parseAsync and encode/decode variants are kept as per-instance closures because they are commonly called in a detached manner (e.g. `arr.map(schema.parse)`, `const p = schema.parse; p(data)`). The "support prototype modifications" loop in `$constructor` (core.ts) is preserved. It remains useful for runtime prototype extensions and is now only activated for genuinely new keys (not for the shared methods, since `k in inst` will be true for all shared own properties). ## Notes - The `as any` casts on shared function assignments are implementation-only; the declared interface types remain authoritative for TypeScript consumers. - All 3575 existing tests pass unchanged, including prototype extension tests and the "this binding" test. - This was validated by applying the compiled-output equivalent as a `patch-package` patch in the Kibana monorepo (elastic/kibana#263121). Made-with: Cursor
9e43965 to
9da0bd2
Compare
|
Pinging @elastic/kibana-core (Team:Core) |
73a6bcf to
660d174
Compare
| * This is also called automatically during `yarn kbn bootstrap` (after yarn install). | ||
| */ | ||
|
|
||
| import('../src/dev/node_modules_patches/zod_prototype_memory_optimization.mjs') |
There was a problem hiding this comment.
../src/dev/node_modules_patches/zod_prototype_memory_optimization.mjs was removed when the approach switched to patch-package. The file is dead and will throw if anyone tries to run it manually.
It's also causing the lint error that's blocking CI 😉
TinaHeiligers
left a comment
There was a problem hiding this comment.
The shadow-proto architecture is clean. It correctly preserves user prototype augmentation while sharing library methods, and keeping parse/safeParse as per-instance closures for detached-call semantics (arr.map(schema.parse)) is the right call.
Zod v4 assigns every method (parse, check, optional, email, etc.) as own properties on each schema instance, resulting in ~91 own properties per instance. V8 switches objects to slow "dictionary mode" when they exceed ~27 own properties, increasing heap usage to ~13KB per schema instance. This patch: - Moves all shared methods from instance own properties to the prototype chain via a _setupZodPrototypes() call added to zod's schemas.cjs - Removes the $constructor "support prototype modifications" loop in core.cjs that was re-copying prototype methods back to instances - Establishes a proper inheritance chain: ZodString -> _ZodString -> ZodType, each level adding type-specific methods Result: own property count drops from ~91 to ~5-8, keeping instances in V8's fast-property mode. Heap cost drops from ~13KB to ~2KB per schema instance (~85% reduction). The patch is applied automatically during `yarn kbn bootstrap` (after yarn install) via src/dev/node_modules_patches/. The patch is idempotent and version-gated: it will warn and skip if the Zod version changes. Upstream issue: colinhacks/zod#5760 Made-with: Cursor
…el savings) Replaces the shared-method-references patch with the more complete shadow-proto strategy: - Introduces a hidden `internalProto` layer between `_.prototype` (user space) and the parent prototype. All library methods are placed on `internalProto` via `_initProto`, making them prototype-inherited rather than own properties. - Own-property count per schema instance drops from ~91 → ~22, keeping all instances firmly in V8's fast-property mode (threshold: ~27). - Methods are shared across instances (prototype-inherited), eliminating the ~50-closure per-instance allocation from the original design. - Backward-compatible: user-added prototype extensions continue to work because `_.prototype` (user space) shadows `internalProto`. - Covers classic and mini Zod API (6 files: schemas.cjs/js + core.cjs/js + mini/schemas.cjs/js). Upstream PR: colinhacks/zod#5870 Made-with: Cursor
Co-authored-by: macroscopeapp[bot] <170038800+macroscopeapp[bot]@users.noreply.github.com>
The script imported zod_prototype_memory_optimization.mjs which was removed when the patch mechanism switched to patch-package. The script is now dead code: patch-package is invoked automatically during `yarn kbn bootstrap` and covers all patching needs. Fixes lint failure blocking CI. Made-with: Cursor
db30da7 to
7f3d62e
Compare
💛 Build succeeded, but was flaky
Failed CI Steps
Test Failures
Metrics [docs]Async chunks
Page load bundle
History
|
kc13greiner
left a comment
There was a problem hiding this comment.
++ LGTM! thanks for filling out the dep justification!
maximpn
left a comment
There was a problem hiding this comment.
Thanks for making this improvement @gsoldevila 🙏
|
Starting backport for target branches: 9.4 https://github.com/elastic/kibana/actions/runs/24780551958 |
💚 All backports created successfully
Note: Successful backport PRs will be merged automatically after passing CI. Questions ?Please refer to the Backport tool documentation |
…ces (#263121) (#265044) # Backport This will backport the following commits from `main` to `9.4`: - [fix(zod): reduce per-schema heap cost via shared method references (#263121)](#263121) <!--- Backport version: 9.6.6 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Gerard Soldevila","email":"gerard.soldevila@elastic.co"},"sourceCommit":{"committedDate":"2026-04-22T13:19:07Z","message":"fix(zod): reduce per-schema heap cost via shared method references (#263121)\n\n## Summary\n\nApplies a memory optimization patch to Zod v4 to address significant\nheap cost regression introduced with the upgrade to Zod 4.x (see\nupstream issue [#5760](https://github.com/colinhacks/zod/issues/5760)).\n\n### Problem\n\nZod v4 assigns every method (`parse`, `check`, `optional`, `email`,\netc.) as an **own-property arrow function** on each schema instance.\nBecause each arrow function closes over `inst`, each instance allocates\n~91 unique function objects. V8 switches from fast-property mode to slow\n\"dictionary mode\" when an object accumulates more than ~27 own\nproperties, causing an extra heap tax on top of the closure overhead. In\npractice this costs **~12.8 KB of heap per schema** (vs ~1.5 KB with Zod\n3).\n\n### Fix — Shadow-Proto Architecture\n\nIntroduces a hidden intermediate prototype layer (`internalProto`)\nbetween `_.prototype` (the user-visible class prototype) and the parent.\nLibrary methods are placed on `internalProto` via `_initProto` (once per\nconcrete type, lazily), so they become **inherited** rather than own\nproperties.\n\n```\ninstance → _.prototype (user space, empty by default)\n → internalProto (library space: 69 shared methods)\n → Parent\n```\n\nKey properties:\n- **Own-property count**: ~91 → **~22** (well below V8's dictionary-mode\nthreshold of ~27)\n- **Shared methods**: all 69 builder/parse methods are\nprototype-inherited; instances share the same function objects\n(`s1.optional === s2.optional`)\n- **Backward-compatible**: user-added prototype extensions on\n`_.prototype` still shadow `internalProto` transparently\n- **Covers both variants**: classic and mini Zod APIs (6 files patched)\n\nSix Zod files are patched (both CJS and ESM variants):\n\n- `zod/v4/classic/schemas.cjs` / `schemas.js` — `_initProto` helper +\nmoves all builder methods to `internalProto`\n- `zod/v4/mini/schemas.cjs` / `schemas.js` — same for the mini API\nsurface\n- `zod/v4/core/core.cjs` / `core.js` — wires `_.prototype →\ninternalProto`, exposes `$internalProto` symbol; copy-loop left as no-op\nfor library methods\n\nThe patch is managed by `patch-package` and lives in\n`patches/zod+4.3.6.patch`. An upstream PR is being prepared:\n[colinhacks/zod#5870](https://github.com/colinhacks/zod/pull/5870).\n\n### Result\n\n| Metric | Before (Zod v4, unpatched) | After (shadow-proto patch) |\n| ------------------------------- | -------------------------- |\n-------------------------- |\n| Own properties per instance | ~91 | **~22** |\n| V8 property storage mode | Dictionary (slow) | **Fast** |\n| Heap cost per `z.string()` | ~12.8 KB | **~2.5 KB** |\n| Shared method references | ✗ (per-instance closures) | **✓**\n(prototype-inherited)|\n| **Memory reduction** | — | **~80%** |\n| `z.iso.datetime().optional()` | ✅ | ✅ |\n| Prototype augmentation | ✅ | ✅ |\n| All parse/validate/chain APIs | ✅ | ✅ |\n\n> Validated by full Kibana heap snapshot comparison: ~113 MB reduction\nat startup.\n\n### Patch persistence\n\nThe patch is applied automatically during `yarn kbn bootstrap` (after\n`yarn install`) via `patch-package --error-on-fail`. The `patches/`\ndirectory is committed and version-controlled.\n\n**Removal criteria**: remove `patches/zod+4.3.6.patch` (and the\n`patch-package` devDependency) once the upstream Zod issue is resolved\nand Kibana upgrades to the patched version.\n\n### New dependency: `patch-package`\n\n| | |\n|---|---|\n| **Purpose** | Applies and maintains the shadow-proto patch to\n`node_modules/zod` across `yarn install` runs. Invoked as `patch-package\n--error-on-fail` in the bootstrap step. |\n| **Justification** | The optimization requires modifying Zod's compiled\nJavaScript internals (`$constructor` wiring, `_initProto` helper,\nbuilder method placement). These changes cannot be applied at the\nTypeScript/import level, so a post-install patch is the correct\nmechanism. `patch-package` is the industry-standard tool for this\npattern, with a simple invocation model and deterministic patch\napplication. |\n| **Alternatives explored** | (1) **Custom patching script** — Kibana\nused a bespoke `src/dev/node_modules_patches/` mechanism in an earlier\niteration of this PR; `patch-package` is strictly simpler and more\nmaintainable. (2) **`@kbn/zod` wrapper** — the wrapper re-exports\n`zod/v4` but cannot intercept the compiled `$constructor` and prototype\nwiring needed for this optimization. (3) **Private Elastic\nfork/registry** — viable but significantly heavier: requires maintaining\na fork, publishing to a registry, and updating consumers;\ndisproportionate effort for a temporary vendor patch. |\n| **Existing dependencies** | Kibana has no existing dependency\nproviding `patch-package`-equivalent functionality. `yarn patch` (a\nbuilt-in Yarn Berry feature) is not available since Kibana uses Yarn\nClassic. |\n\n### Test plan\n\n- `npx patch-package --error-on-fail` applies cleanly from scratch\n- `z.iso.datetime().optional()` works\n- `z.string().email().optional()`, `obj.pick()`, `enum.extract()` all\nwork\n- `z.httpUrl()` correctly rejects `ftp://`, `file:///` URLs (only\n`http`/`https` accepted)\n- Shared references confirmed: `z.string().optional ===\nz.string().optional` → `true`\n- Own-property count confirmed:\n`Object.getOwnPropertyNames(z.string()).length` → `22`\n- Memory benchmark: ~2.5 KB per `z.string()` (down from ~12.8 KB)\n- `node scripts/check_changes.ts` passes\n- Heap snapshot comparison: ~113 MB reduction at Kibana startup\n\n---------\n\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>\nCo-authored-by: macroscopeapp[bot] <170038800+macroscopeapp[bot]@users.noreply.github.com>","sha":"ae70b77d9551d54563537c088b92f643f43e96fa","branchLabelMapping":{"^v9.5.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Team:Core","release_note:skip","ci:build-cloud-image","backport:version","v9.4.0","v9.5.0"],"title":"fix(zod): reduce per-schema heap cost via shared method references","number":263121,"url":"https://github.com/elastic/kibana/pull/263121","mergeCommit":{"message":"fix(zod): reduce per-schema heap cost via shared method references (#263121)\n\n## Summary\n\nApplies a memory optimization patch to Zod v4 to address significant\nheap cost regression introduced with the upgrade to Zod 4.x (see\nupstream issue [#5760](https://github.com/colinhacks/zod/issues/5760)).\n\n### Problem\n\nZod v4 assigns every method (`parse`, `check`, `optional`, `email`,\netc.) as an **own-property arrow function** on each schema instance.\nBecause each arrow function closes over `inst`, each instance allocates\n~91 unique function objects. V8 switches from fast-property mode to slow\n\"dictionary mode\" when an object accumulates more than ~27 own\nproperties, causing an extra heap tax on top of the closure overhead. In\npractice this costs **~12.8 KB of heap per schema** (vs ~1.5 KB with Zod\n3).\n\n### Fix — Shadow-Proto Architecture\n\nIntroduces a hidden intermediate prototype layer (`internalProto`)\nbetween `_.prototype` (the user-visible class prototype) and the parent.\nLibrary methods are placed on `internalProto` via `_initProto` (once per\nconcrete type, lazily), so they become **inherited** rather than own\nproperties.\n\n```\ninstance → _.prototype (user space, empty by default)\n → internalProto (library space: 69 shared methods)\n → Parent\n```\n\nKey properties:\n- **Own-property count**: ~91 → **~22** (well below V8's dictionary-mode\nthreshold of ~27)\n- **Shared methods**: all 69 builder/parse methods are\nprototype-inherited; instances share the same function objects\n(`s1.optional === s2.optional`)\n- **Backward-compatible**: user-added prototype extensions on\n`_.prototype` still shadow `internalProto` transparently\n- **Covers both variants**: classic and mini Zod APIs (6 files patched)\n\nSix Zod files are patched (both CJS and ESM variants):\n\n- `zod/v4/classic/schemas.cjs` / `schemas.js` — `_initProto` helper +\nmoves all builder methods to `internalProto`\n- `zod/v4/mini/schemas.cjs` / `schemas.js` — same for the mini API\nsurface\n- `zod/v4/core/core.cjs` / `core.js` — wires `_.prototype →\ninternalProto`, exposes `$internalProto` symbol; copy-loop left as no-op\nfor library methods\n\nThe patch is managed by `patch-package` and lives in\n`patches/zod+4.3.6.patch`. An upstream PR is being prepared:\n[colinhacks/zod#5870](https://github.com/colinhacks/zod/pull/5870).\n\n### Result\n\n| Metric | Before (Zod v4, unpatched) | After (shadow-proto patch) |\n| ------------------------------- | -------------------------- |\n-------------------------- |\n| Own properties per instance | ~91 | **~22** |\n| V8 property storage mode | Dictionary (slow) | **Fast** |\n| Heap cost per `z.string()` | ~12.8 KB | **~2.5 KB** |\n| Shared method references | ✗ (per-instance closures) | **✓**\n(prototype-inherited)|\n| **Memory reduction** | — | **~80%** |\n| `z.iso.datetime().optional()` | ✅ | ✅ |\n| Prototype augmentation | ✅ | ✅ |\n| All parse/validate/chain APIs | ✅ | ✅ |\n\n> Validated by full Kibana heap snapshot comparison: ~113 MB reduction\nat startup.\n\n### Patch persistence\n\nThe patch is applied automatically during `yarn kbn bootstrap` (after\n`yarn install`) via `patch-package --error-on-fail`. The `patches/`\ndirectory is committed and version-controlled.\n\n**Removal criteria**: remove `patches/zod+4.3.6.patch` (and the\n`patch-package` devDependency) once the upstream Zod issue is resolved\nand Kibana upgrades to the patched version.\n\n### New dependency: `patch-package`\n\n| | |\n|---|---|\n| **Purpose** | Applies and maintains the shadow-proto patch to\n`node_modules/zod` across `yarn install` runs. Invoked as `patch-package\n--error-on-fail` in the bootstrap step. |\n| **Justification** | The optimization requires modifying Zod's compiled\nJavaScript internals (`$constructor` wiring, `_initProto` helper,\nbuilder method placement). These changes cannot be applied at the\nTypeScript/import level, so a post-install patch is the correct\nmechanism. `patch-package` is the industry-standard tool for this\npattern, with a simple invocation model and deterministic patch\napplication. |\n| **Alternatives explored** | (1) **Custom patching script** — Kibana\nused a bespoke `src/dev/node_modules_patches/` mechanism in an earlier\niteration of this PR; `patch-package` is strictly simpler and more\nmaintainable. (2) **`@kbn/zod` wrapper** — the wrapper re-exports\n`zod/v4` but cannot intercept the compiled `$constructor` and prototype\nwiring needed for this optimization. (3) **Private Elastic\nfork/registry** — viable but significantly heavier: requires maintaining\na fork, publishing to a registry, and updating consumers;\ndisproportionate effort for a temporary vendor patch. |\n| **Existing dependencies** | Kibana has no existing dependency\nproviding `patch-package`-equivalent functionality. `yarn patch` (a\nbuilt-in Yarn Berry feature) is not available since Kibana uses Yarn\nClassic. |\n\n### Test plan\n\n- `npx patch-package --error-on-fail` applies cleanly from scratch\n- `z.iso.datetime().optional()` works\n- `z.string().email().optional()`, `obj.pick()`, `enum.extract()` all\nwork\n- `z.httpUrl()` correctly rejects `ftp://`, `file:///` URLs (only\n`http`/`https` accepted)\n- Shared references confirmed: `z.string().optional ===\nz.string().optional` → `true`\n- Own-property count confirmed:\n`Object.getOwnPropertyNames(z.string()).length` → `22`\n- Memory benchmark: ~2.5 KB per `z.string()` (down from ~12.8 KB)\n- `node scripts/check_changes.ts` passes\n- Heap snapshot comparison: ~113 MB reduction at Kibana startup\n\n---------\n\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>\nCo-authored-by: macroscopeapp[bot] <170038800+macroscopeapp[bot]@users.noreply.github.com>","sha":"ae70b77d9551d54563537c088b92f643f43e96fa"}},"sourceBranch":"main","suggestedTargetBranches":["9.4"],"targetPullRequestStates":[{"branch":"9.4","label":"v9.4.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v9.5.0","branchLabelMappingKey":"^v9.5.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/263121","number":263121,"mergeCommit":{"message":"fix(zod): reduce per-schema heap cost via shared method references (#263121)\n\n## Summary\n\nApplies a memory optimization patch to Zod v4 to address significant\nheap cost regression introduced with the upgrade to Zod 4.x (see\nupstream issue [#5760](https://github.com/colinhacks/zod/issues/5760)).\n\n### Problem\n\nZod v4 assigns every method (`parse`, `check`, `optional`, `email`,\netc.) as an **own-property arrow function** on each schema instance.\nBecause each arrow function closes over `inst`, each instance allocates\n~91 unique function objects. V8 switches from fast-property mode to slow\n\"dictionary mode\" when an object accumulates more than ~27 own\nproperties, causing an extra heap tax on top of the closure overhead. In\npractice this costs **~12.8 KB of heap per schema** (vs ~1.5 KB with Zod\n3).\n\n### Fix — Shadow-Proto Architecture\n\nIntroduces a hidden intermediate prototype layer (`internalProto`)\nbetween `_.prototype` (the user-visible class prototype) and the parent.\nLibrary methods are placed on `internalProto` via `_initProto` (once per\nconcrete type, lazily), so they become **inherited** rather than own\nproperties.\n\n```\ninstance → _.prototype (user space, empty by default)\n → internalProto (library space: 69 shared methods)\n → Parent\n```\n\nKey properties:\n- **Own-property count**: ~91 → **~22** (well below V8's dictionary-mode\nthreshold of ~27)\n- **Shared methods**: all 69 builder/parse methods are\nprototype-inherited; instances share the same function objects\n(`s1.optional === s2.optional`)\n- **Backward-compatible**: user-added prototype extensions on\n`_.prototype` still shadow `internalProto` transparently\n- **Covers both variants**: classic and mini Zod APIs (6 files patched)\n\nSix Zod files are patched (both CJS and ESM variants):\n\n- `zod/v4/classic/schemas.cjs` / `schemas.js` — `_initProto` helper +\nmoves all builder methods to `internalProto`\n- `zod/v4/mini/schemas.cjs` / `schemas.js` — same for the mini API\nsurface\n- `zod/v4/core/core.cjs` / `core.js` — wires `_.prototype →\ninternalProto`, exposes `$internalProto` symbol; copy-loop left as no-op\nfor library methods\n\nThe patch is managed by `patch-package` and lives in\n`patches/zod+4.3.6.patch`. An upstream PR is being prepared:\n[colinhacks/zod#5870](https://github.com/colinhacks/zod/pull/5870).\n\n### Result\n\n| Metric | Before (Zod v4, unpatched) | After (shadow-proto patch) |\n| ------------------------------- | -------------------------- |\n-------------------------- |\n| Own properties per instance | ~91 | **~22** |\n| V8 property storage mode | Dictionary (slow) | **Fast** |\n| Heap cost per `z.string()` | ~12.8 KB | **~2.5 KB** |\n| Shared method references | ✗ (per-instance closures) | **✓**\n(prototype-inherited)|\n| **Memory reduction** | — | **~80%** |\n| `z.iso.datetime().optional()` | ✅ | ✅ |\n| Prototype augmentation | ✅ | ✅ |\n| All parse/validate/chain APIs | ✅ | ✅ |\n\n> Validated by full Kibana heap snapshot comparison: ~113 MB reduction\nat startup.\n\n### Patch persistence\n\nThe patch is applied automatically during `yarn kbn bootstrap` (after\n`yarn install`) via `patch-package --error-on-fail`. The `patches/`\ndirectory is committed and version-controlled.\n\n**Removal criteria**: remove `patches/zod+4.3.6.patch` (and the\n`patch-package` devDependency) once the upstream Zod issue is resolved\nand Kibana upgrades to the patched version.\n\n### New dependency: `patch-package`\n\n| | |\n|---|---|\n| **Purpose** | Applies and maintains the shadow-proto patch to\n`node_modules/zod` across `yarn install` runs. Invoked as `patch-package\n--error-on-fail` in the bootstrap step. |\n| **Justification** | The optimization requires modifying Zod's compiled\nJavaScript internals (`$constructor` wiring, `_initProto` helper,\nbuilder method placement). These changes cannot be applied at the\nTypeScript/import level, so a post-install patch is the correct\nmechanism. `patch-package` is the industry-standard tool for this\npattern, with a simple invocation model and deterministic patch\napplication. |\n| **Alternatives explored** | (1) **Custom patching script** — Kibana\nused a bespoke `src/dev/node_modules_patches/` mechanism in an earlier\niteration of this PR; `patch-package` is strictly simpler and more\nmaintainable. (2) **`@kbn/zod` wrapper** — the wrapper re-exports\n`zod/v4` but cannot intercept the compiled `$constructor` and prototype\nwiring needed for this optimization. (3) **Private Elastic\nfork/registry** — viable but significantly heavier: requires maintaining\na fork, publishing to a registry, and updating consumers;\ndisproportionate effort for a temporary vendor patch. |\n| **Existing dependencies** | Kibana has no existing dependency\nproviding `patch-package`-equivalent functionality. `yarn patch` (a\nbuilt-in Yarn Berry feature) is not available since Kibana uses Yarn\nClassic. |\n\n### Test plan\n\n- `npx patch-package --error-on-fail` applies cleanly from scratch\n- `z.iso.datetime().optional()` works\n- `z.string().email().optional()`, `obj.pick()`, `enum.extract()` all\nwork\n- `z.httpUrl()` correctly rejects `ftp://`, `file:///` URLs (only\n`http`/`https` accepted)\n- Shared references confirmed: `z.string().optional ===\nz.string().optional` → `true`\n- Own-property count confirmed:\n`Object.getOwnPropertyNames(z.string()).length` → `22`\n- Memory benchmark: ~2.5 KB per `z.string()` (down from ~12.8 KB)\n- `node scripts/check_changes.ts` passes\n- Heap snapshot comparison: ~113 MB reduction at Kibana startup\n\n---------\n\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>\nCo-authored-by: macroscopeapp[bot] <170038800+macroscopeapp[bot]@users.noreply.github.com>","sha":"ae70b77d9551d54563537c088b92f643f43e96fa"}}]}] BACKPORT--> Co-authored-by: Gerard Soldevila <gerard.soldevila@elastic.co> Co-authored-by: macroscopeapp[bot] <170038800+macroscopeapp[bot]@users.noreply.github.com>
Follow up to #263121, which also needs to run on the build. https://buildkite.com/elastic/kibana-artifacts-snapshot/builds/8279 ``` jon@mbp % tar xzf kibana-9.4.0-SNAPSHOT-linux-x86_64.tar.gz --to-stdout kibana-9.4.0-SNAPSHOT/node_modules/zod/v4/classic/schemas.cjs | head -160 | tail -35 const checks = __importStar(require("./checks.cjs")); const iso = __importStar(require("./iso.cjs")); const parse = __importStar(require("./parse.cjs")); // Maps (internalProto, key) pairs already initialized — avoids repeated prototype setup const _protoInitMap = new WeakMap(); /** * Sets shared methods on the *internal* prototype layer of inst's concrete constructor * (once per concrete type per key). The internal prototype sits one level below the * user-visible `_.prototype`, keeping `Object.keys(_.prototype)` empty by default * and preventing V8 dictionary-mode degradation. * * Falls back to `Object.getPrototypeOf(inst)` for constructors not created with * `$constructor` (e.g., in tests or third-party code). */ function _initProto(inst, key, methods, defineProps) { const proto = inst._zod?.constr?.[core.$internalProto] ?? Object.getPrototypeOf(inst); ```
…lastic#263121) ## Summary Applies a memory optimization patch to Zod v4 to address significant heap cost regression introduced with the upgrade to Zod 4.x (see upstream issue [elastic#5760](colinhacks/zod#5760)). ### Problem Zod v4 assigns every method (`parse`, `check`, `optional`, `email`, etc.) as an **own-property arrow function** on each schema instance. Because each arrow function closes over `inst`, each instance allocates ~91 unique function objects. V8 switches from fast-property mode to slow "dictionary mode" when an object accumulates more than ~27 own properties, causing an extra heap tax on top of the closure overhead. In practice this costs **~12.8 KB of heap per schema** (vs ~1.5 KB with Zod 3). ### Fix — Shadow-Proto Architecture Introduces a hidden intermediate prototype layer (`internalProto`) between `_.prototype` (the user-visible class prototype) and the parent. Library methods are placed on `internalProto` via `_initProto` (once per concrete type, lazily), so they become **inherited** rather than own properties. ``` instance → _.prototype (user space, empty by default) → internalProto (library space: 69 shared methods) → Parent ``` Key properties: - **Own-property count**: ~91 → **~22** (well below V8's dictionary-mode threshold of ~27) - **Shared methods**: all 69 builder/parse methods are prototype-inherited; instances share the same function objects (`s1.optional === s2.optional`) - **Backward-compatible**: user-added prototype extensions on `_.prototype` still shadow `internalProto` transparently - **Covers both variants**: classic and mini Zod APIs (6 files patched) Six Zod files are patched (both CJS and ESM variants): - `zod/v4/classic/schemas.cjs` / `schemas.js` — `_initProto` helper + moves all builder methods to `internalProto` - `zod/v4/mini/schemas.cjs` / `schemas.js` — same for the mini API surface - `zod/v4/core/core.cjs` / `core.js` — wires `_.prototype → internalProto`, exposes `$internalProto` symbol; copy-loop left as no-op for library methods The patch is managed by `patch-package` and lives in `patches/zod+4.3.6.patch`. An upstream PR is being prepared: [colinhacks/zod#5870](colinhacks/zod#5870). ### Result | Metric | Before (Zod v4, unpatched) | After (shadow-proto patch) | | ------------------------------- | -------------------------- | -------------------------- | | Own properties per instance | ~91 | **~22** | | V8 property storage mode | Dictionary (slow) | **Fast** | | Heap cost per `z.string()` | ~12.8 KB | **~2.5 KB** | | Shared method references | ✗ (per-instance closures) | **✓** (prototype-inherited)| | **Memory reduction** | — | **~80%** | | `z.iso.datetime().optional()` | ✅ | ✅ | | Prototype augmentation | ✅ | ✅ | | All parse/validate/chain APIs | ✅ | ✅ | > Validated by full Kibana heap snapshot comparison: ~113 MB reduction at startup. ### Patch persistence The patch is applied automatically during `yarn kbn bootstrap` (after `yarn install`) via `patch-package --error-on-fail`. The `patches/` directory is committed and version-controlled. **Removal criteria**: remove `patches/zod+4.3.6.patch` (and the `patch-package` devDependency) once the upstream Zod issue is resolved and Kibana upgrades to the patched version. ### New dependency: `patch-package` | | | |---|---| | **Purpose** | Applies and maintains the shadow-proto patch to `node_modules/zod` across `yarn install` runs. Invoked as `patch-package --error-on-fail` in the bootstrap step. | | **Justification** | The optimization requires modifying Zod's compiled JavaScript internals (`$constructor` wiring, `_initProto` helper, builder method placement). These changes cannot be applied at the TypeScript/import level, so a post-install patch is the correct mechanism. `patch-package` is the industry-standard tool for this pattern, with a simple invocation model and deterministic patch application. | | **Alternatives explored** | (1) **Custom patching script** — Kibana used a bespoke `src/dev/node_modules_patches/` mechanism in an earlier iteration of this PR; `patch-package` is strictly simpler and more maintainable. (2) **`@kbn/zod` wrapper** — the wrapper re-exports `zod/v4` but cannot intercept the compiled `$constructor` and prototype wiring needed for this optimization. (3) **Private Elastic fork/registry** — viable but significantly heavier: requires maintaining a fork, publishing to a registry, and updating consumers; disproportionate effort for a temporary vendor patch. | | **Existing dependencies** | Kibana has no existing dependency providing `patch-package`-equivalent functionality. `yarn patch` (a built-in Yarn Berry feature) is not available since Kibana uses Yarn Classic. | ### Test plan - `npx patch-package --error-on-fail` applies cleanly from scratch - `z.iso.datetime().optional()` works - `z.string().email().optional()`, `obj.pick()`, `enum.extract()` all work - `z.httpUrl()` correctly rejects `ftp://`, `file:///` URLs (only `http`/`https` accepted) - Shared references confirmed: `z.string().optional === z.string().optional` → `true` - Own-property count confirmed: `Object.getOwnPropertyNames(z.string()).length` → `22` - Memory benchmark: ~2.5 KB per `z.string()` (down from ~12.8 KB) - `node scripts/check_changes.ts` passes - Heap snapshot comparison: ~113 MB reduction at Kibana startup --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: macroscopeapp[bot] <170038800+macroscopeapp[bot]@users.noreply.github.com>
Follow up to elastic#263121, which also needs to run on the build. https://buildkite.com/elastic/kibana-artifacts-snapshot/builds/8279 ``` jon@mbp % tar xzf kibana-9.4.0-SNAPSHOT-linux-x86_64.tar.gz --to-stdout kibana-9.4.0-SNAPSHOT/node_modules/zod/v4/classic/schemas.cjs | head -160 | tail -35 const checks = __importStar(require("./checks.cjs")); const iso = __importStar(require("./iso.cjs")); const parse = __importStar(require("./parse.cjs")); // Maps (internalProto, key) pairs already initialized — avoids repeated prototype setup const _protoInitMap = new WeakMap(); /** * Sets shared methods on the *internal* prototype layer of inst's concrete constructor * (once per concrete type per key). The internal prototype sits one level below the * user-visible `_.prototype`, keeping `Object.keys(_.prototype)` empty by default * and preventing V8 dictionary-mode degradation. * * Falls back to `Object.getPrototypeOf(inst)` for constructors not created with * `$constructor` (e.g., in tests or third-party code). */ function _initProto(inst, key, methods, defineProps) { const proto = inst._zod?.constr?.[core.$internalProto] ?? Object.getPrototypeOf(inst); ```
Follow up to elastic#263121, which also needs to run on the build. https://buildkite.com/elastic/kibana-artifacts-snapshot/builds/8279 ``` jon@mbp % tar xzf kibana-9.4.0-SNAPSHOT-linux-x86_64.tar.gz --to-stdout kibana-9.4.0-SNAPSHOT/node_modules/zod/v4/classic/schemas.cjs | head -160 | tail -35 const checks = __importStar(require("./checks.cjs")); const iso = __importStar(require("./iso.cjs")); const parse = __importStar(require("./parse.cjs")); // Maps (internalProto, key) pairs already initialized — avoids repeated prototype setup const _protoInitMap = new WeakMap(); /** * Sets shared methods on the *internal* prototype layer of inst's concrete constructor * (once per concrete type per key). The internal prototype sits one level below the * user-visible `_.prototype`, keeping `Object.keys(_.prototype)` empty by default * and preventing V8 dictionary-mode degradation. * * Falls back to `Object.getPrototypeOf(inst)` for constructors not created with * `$constructor` (e.g., in tests or third-party code). */ function _initProto(inst, key, methods, defineProps) { const proto = inst._zod?.constr?.[core.$internalProto] ?? Object.getPrototypeOf(inst); ```
…lastic#263121) ## Summary Applies a memory optimization patch to Zod v4 to address significant heap cost regression introduced with the upgrade to Zod 4.x (see upstream issue [elastic#5760](colinhacks/zod#5760)). ### Problem Zod v4 assigns every method (`parse`, `check`, `optional`, `email`, etc.) as an **own-property arrow function** on each schema instance. Because each arrow function closes over `inst`, each instance allocates ~91 unique function objects. V8 switches from fast-property mode to slow "dictionary mode" when an object accumulates more than ~27 own properties, causing an extra heap tax on top of the closure overhead. In practice this costs **~12.8 KB of heap per schema** (vs ~1.5 KB with Zod 3). ### Fix — Shadow-Proto Architecture Introduces a hidden intermediate prototype layer (`internalProto`) between `_.prototype` (the user-visible class prototype) and the parent. Library methods are placed on `internalProto` via `_initProto` (once per concrete type, lazily), so they become **inherited** rather than own properties. ``` instance → _.prototype (user space, empty by default) → internalProto (library space: 69 shared methods) → Parent ``` Key properties: - **Own-property count**: ~91 → **~22** (well below V8's dictionary-mode threshold of ~27) - **Shared methods**: all 69 builder/parse methods are prototype-inherited; instances share the same function objects (`s1.optional === s2.optional`) - **Backward-compatible**: user-added prototype extensions on `_.prototype` still shadow `internalProto` transparently - **Covers both variants**: classic and mini Zod APIs (6 files patched) Six Zod files are patched (both CJS and ESM variants): - `zod/v4/classic/schemas.cjs` / `schemas.js` — `_initProto` helper + moves all builder methods to `internalProto` - `zod/v4/mini/schemas.cjs` / `schemas.js` — same for the mini API surface - `zod/v4/core/core.cjs` / `core.js` — wires `_.prototype → internalProto`, exposes `$internalProto` symbol; copy-loop left as no-op for library methods The patch is managed by `patch-package` and lives in `patches/zod+4.3.6.patch`. An upstream PR is being prepared: [colinhacks/zod#5870](colinhacks/zod#5870). ### Result | Metric | Before (Zod v4, unpatched) | After (shadow-proto patch) | | ------------------------------- | -------------------------- | -------------------------- | | Own properties per instance | ~91 | **~22** | | V8 property storage mode | Dictionary (slow) | **Fast** | | Heap cost per `z.string()` | ~12.8 KB | **~2.5 KB** | | Shared method references | ✗ (per-instance closures) | **✓** (prototype-inherited)| | **Memory reduction** | — | **~80%** | | `z.iso.datetime().optional()` | ✅ | ✅ | | Prototype augmentation | ✅ | ✅ | | All parse/validate/chain APIs | ✅ | ✅ | > Validated by full Kibana heap snapshot comparison: ~113 MB reduction at startup. ### Patch persistence The patch is applied automatically during `yarn kbn bootstrap` (after `yarn install`) via `patch-package --error-on-fail`. The `patches/` directory is committed and version-controlled. **Removal criteria**: remove `patches/zod+4.3.6.patch` (and the `patch-package` devDependency) once the upstream Zod issue is resolved and Kibana upgrades to the patched version. ### New dependency: `patch-package` | | | |---|---| | **Purpose** | Applies and maintains the shadow-proto patch to `node_modules/zod` across `yarn install` runs. Invoked as `patch-package --error-on-fail` in the bootstrap step. | | **Justification** | The optimization requires modifying Zod's compiled JavaScript internals (`$constructor` wiring, `_initProto` helper, builder method placement). These changes cannot be applied at the TypeScript/import level, so a post-install patch is the correct mechanism. `patch-package` is the industry-standard tool for this pattern, with a simple invocation model and deterministic patch application. | | **Alternatives explored** | (1) **Custom patching script** — Kibana used a bespoke `src/dev/node_modules_patches/` mechanism in an earlier iteration of this PR; `patch-package` is strictly simpler and more maintainable. (2) **`@kbn/zod` wrapper** — the wrapper re-exports `zod/v4` but cannot intercept the compiled `$constructor` and prototype wiring needed for this optimization. (3) **Private Elastic fork/registry** — viable but significantly heavier: requires maintaining a fork, publishing to a registry, and updating consumers; disproportionate effort for a temporary vendor patch. | | **Existing dependencies** | Kibana has no existing dependency providing `patch-package`-equivalent functionality. `yarn patch` (a built-in Yarn Berry feature) is not available since Kibana uses Yarn Classic. | ### Test plan - `npx patch-package --error-on-fail` applies cleanly from scratch - `z.iso.datetime().optional()` works - `z.string().email().optional()`, `obj.pick()`, `enum.extract()` all work - `z.httpUrl()` correctly rejects `ftp://`, `file:///` URLs (only `http`/`https` accepted) - Shared references confirmed: `z.string().optional === z.string().optional` → `true` - Own-property count confirmed: `Object.getOwnPropertyNames(z.string()).length` → `22` - Memory benchmark: ~2.5 KB per `z.string()` (down from ~12.8 KB) - `node scripts/check_changes.ts` passes - Heap snapshot comparison: ~113 MB reduction at Kibana startup --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: macroscopeapp[bot] <170038800+macroscopeapp[bot]@users.noreply.github.com>
Follow up to elastic#263121, which also needs to run on the build. https://buildkite.com/elastic/kibana-artifacts-snapshot/builds/8279 ``` jon@mbp % tar xzf kibana-9.4.0-SNAPSHOT-linux-x86_64.tar.gz --to-stdout kibana-9.4.0-SNAPSHOT/node_modules/zod/v4/classic/schemas.cjs | head -160 | tail -35 const checks = __importStar(require("./checks.cjs")); const iso = __importStar(require("./iso.cjs")); const parse = __importStar(require("./parse.cjs")); // Maps (internalProto, key) pairs already initialized — avoids repeated prototype setup const _protoInitMap = new WeakMap(); /** * Sets shared methods on the *internal* prototype layer of inst's concrete constructor * (once per concrete type per key). The internal prototype sits one level below the * user-visible `_.prototype`, keeping `Object.keys(_.prototype)` empty by default * and preventing V8 dictionary-mode degradation. * * Falls back to `Object.getPrototypeOf(inst)` for constructors not created with * `$constructor` (e.g., in tests or third-party code). */ function _initProto(inst, key, methods, defineProps) { const proto = inst._zod?.constr?.[core.$internalProto] ?? Object.getPrototypeOf(inst); ```
…d#5897) (#266343) ## Summary Adopts the memory optimization implemented by Colin McDonnell in [colinhacks/zod#5897](colinhacks/zod#5897) as a `patch-package` patch, ahead of the next official zod release. Also fixes [colinhacks/zod#5760](colinhacks/zod#5760). ### Background The previous patch (#263121) introduced a **shadow-proto** architecture that shared builder methods on a hidden prototype layer. While it reduced per-schema heap cost by ~80%, it introduced a breaking change: **detached method calls** stopped working. ```ts const opt = schema.optional; // extracts method reference opt(); // ❌ previously threw — `this` was undefined ``` The root cause was that `_initProto` used `Object.assign` to copy methods as plain value properties onto the prototype. Detaching such a property loses the `this` context. ### This PR Replaces `_initProto` with Colin's `_installLazyMethods` approach from [colinhacks/zod#5897](colinhacks/zod#5897): ```js function _installLazyMethods(inst, group, methods) { // ... one-time setup per (proto, group) ... for (const key in methods) { const fn = methods[key]; Object.defineProperty(proto, key, { configurable: true, enumerable: false, get() { const bound = fn.bind(this); // Cache on the instance — subsequent accesses skip the getter Object.defineProperty(this, key, { configurable: true, writable: true, enumerable: true, value: bound, }); return bound; }, set(v) { Object.defineProperty(this, key, { configurable: true, writable: true, enumerable: true, value: v, }); }, }); } } ``` **How it works:** - Builder methods live as non-enumerable getters on each concrete schema's prototype - On first access per instance, the getter allocates `fn.bind(this)` and caches it as an own property — subsequent accesses skip the getter entirely - Detached calls (`const m = schema.optional; m()`) work because `m` is a bound function with `this` already resolved - Parse-family methods (`parse`, `safeParse`, etc.) stay as eager per-instance closures — they're the hot path and most-detached methods **Memory characteristics are unchanged:** builder methods are not own properties until first accessed, so V8 keeps schema instances in fast-property mode. Per Colin's numbers: -40% heap per instance, -25% construction time vs Zod 4.3.6 baseline. ## Why `patch-package` Upstream zod CI has been broken for months and there is no scheduled release. `patch-package` allows us to ship the fix now. Once a new zod version including [colinhacks/zod#5897](colinhacks/zod#5897) is released, this patch can be dropped. ## Changes - `patches/zod+4.3.6.patch` — replaces the previous shadow-proto patch with Colin's `_installLazyMethods` approach (only touches `v4/classic/schemas.cjs` and `v4/classic/schemas.js`) - `src/platform/packages/shared/kbn-zod/v4/detached_methods.test.ts` — behavioural unit tests proving detached calls work and that the lazy-cache correctly returns the same bound function on repeated access ## Test plan - [x] `node scripts/jest src/platform/packages/shared/kbn-zod/v4/detached_methods.test.ts` — 5/5 pass - [ ] Full CI green --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
…d#5897) (elastic#266343) ## Summary Adopts the memory optimization implemented by Colin McDonnell in [colinhacks/zod#5897](colinhacks/zod#5897) as a `patch-package` patch, ahead of the next official zod release. Also fixes [colinhacks/zod#5760](colinhacks/zod#5760). ### Background The previous patch (elastic#263121) introduced a **shadow-proto** architecture that shared builder methods on a hidden prototype layer. While it reduced per-schema heap cost by ~80%, it introduced a breaking change: **detached method calls** stopped working. ```ts const opt = schema.optional; // extracts method reference opt(); // ❌ previously threw — `this` was undefined ``` The root cause was that `_initProto` used `Object.assign` to copy methods as plain value properties onto the prototype. Detaching such a property loses the `this` context. ### This PR Replaces `_initProto` with Colin's `_installLazyMethods` approach from [colinhacks/zod#5897](colinhacks/zod#5897): ```js function _installLazyMethods(inst, group, methods) { // ... one-time setup per (proto, group) ... for (const key in methods) { const fn = methods[key]; Object.defineProperty(proto, key, { configurable: true, enumerable: false, get() { const bound = fn.bind(this); // Cache on the instance — subsequent accesses skip the getter Object.defineProperty(this, key, { configurable: true, writable: true, enumerable: true, value: bound, }); return bound; }, set(v) { Object.defineProperty(this, key, { configurable: true, writable: true, enumerable: true, value: v, }); }, }); } } ``` **How it works:** - Builder methods live as non-enumerable getters on each concrete schema's prototype - On first access per instance, the getter allocates `fn.bind(this)` and caches it as an own property — subsequent accesses skip the getter entirely - Detached calls (`const m = schema.optional; m()`) work because `m` is a bound function with `this` already resolved - Parse-family methods (`parse`, `safeParse`, etc.) stay as eager per-instance closures — they're the hot path and most-detached methods **Memory characteristics are unchanged:** builder methods are not own properties until first accessed, so V8 keeps schema instances in fast-property mode. Per Colin's numbers: -40% heap per instance, -25% construction time vs Zod 4.3.6 baseline. ## Why `patch-package` Upstream zod CI has been broken for months and there is no scheduled release. `patch-package` allows us to ship the fix now. Once a new zod version including [colinhacks/zod#5897](colinhacks/zod#5897) is released, this patch can be dropped. ## Changes - `patches/zod+4.3.6.patch` — replaces the previous shadow-proto patch with Colin's `_installLazyMethods` approach (only touches `v4/classic/schemas.cjs` and `v4/classic/schemas.js`) - `src/platform/packages/shared/kbn-zod/v4/detached_methods.test.ts` — behavioural unit tests proving detached calls work and that the lazy-cache correctly returns the same bound function on repeated access ## Test plan - [x] `node scripts/jest src/platform/packages/shared/kbn-zod/v4/detached_methods.test.ts` — 5/5 pass - [ ] Full CI green --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> (cherry picked from commit a9ecabf)
Part of #264170
Summary
Applies a memory optimization patch to Zod v4 to address significant heap cost regression introduced with the upgrade to Zod 4.x (see upstream issue #5760).
Problem
Zod v4 assigns every method (
parse,check,optional,email, etc.) as an own-property arrow function on each schema instance. Because each arrow function closes overinst, each instance allocates ~91 unique function objects. V8 switches from fast-property mode to slow "dictionary mode" when an object accumulates more than ~27 own properties, causing an extra heap tax on top of the closure overhead. In practice this costs ~12.8 KB of heap per schema (vs ~1.5 KB with Zod 3).Fix — Shadow-Proto Architecture
Introduces a hidden intermediate prototype layer (
internalProto) between_.prototype(the user-visible class prototype) and the parent. Library methods are placed oninternalProtovia_initProto(once per concrete type, lazily), so they become inherited rather than own properties.Key properties:
s1.optional === s2.optional)_.prototypestill shadowinternalPrototransparentlySix Zod files are patched (both CJS and ESM variants):
zod/v4/classic/schemas.cjs/schemas.js—_initProtohelper + moves all builder methods tointernalProtozod/v4/mini/schemas.cjs/schemas.js— same for the mini API surfacezod/v4/core/core.cjs/core.js— wires_.prototype → internalProto, exposes$internalProtosymbol; copy-loop left as no-op for library methodsThe patch is managed by
patch-packageand lives inpatches/zod+4.3.6.patch. An upstream PR is being prepared: colinhacks/zod#5870.Result
z.string()z.iso.datetime().optional()Patch persistence
The patch is applied automatically during
yarn kbn bootstrap(afteryarn install) viapatch-package --error-on-fail. Thepatches/directory is committed and version-controlled.Removal criteria: remove
patches/zod+4.3.6.patch(and thepatch-packagedevDependency) once the upstream Zod issue is resolved and Kibana upgrades to the patched version.New dependency:
patch-packagenode_modules/zodacrossyarn installruns. Invoked aspatch-package --error-on-failin the bootstrap step.$constructorwiring,_initProtohelper, builder method placement). These changes cannot be applied at the TypeScript/import level, so a post-install patch is the correct mechanism.patch-packageis the industry-standard tool for this pattern, with a simple invocation model and deterministic patch application.src/dev/node_modules_patches/mechanism in an earlier iteration of this PR;patch-packageis strictly simpler and more maintainable. (2)@kbn/zodwrapper — the wrapper re-exportszod/v4but cannot intercept the compiled$constructorand prototype wiring needed for this optimization. (3) Private Elastic fork/registry — viable but significantly heavier: requires maintaining a fork, publishing to a registry, and updating consumers; disproportionate effort for a temporary vendor patch.patch-package-equivalent functionality.yarn patch(a built-in Yarn Berry feature) is not available since Kibana uses Yarn Classic.Test plan
npx patch-package --error-on-failapplies cleanly from scratchz.iso.datetime().optional()worksz.string().email().optional(),obj.pick(),enum.extract()all workz.httpUrl()correctly rejectsftp://,file:///URLs (onlyhttp/httpsaccepted)z.string().optional === z.string().optional→trueObject.getOwnPropertyNames(z.string()).length→22z.string()(down from ~12.8 KB)node scripts/check_changes.tspasses