You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
keep a v8.serialize sidecar next to ParsedFile and parse-cache JSON shards — JSON.parse + reviver is ~5× slower than v8.deserialize on the same object graph #3089
Both on-disk stores that feed scope-resolution round-trip through JSON with a custom reviver: ParsedFile shards (parsedfile-store.ts, makeInterningReviver — string interning plus per-shard def dedup) and parse-cache chunk results (parse-cache.ts, mapReviver for Scope.bindings / typeBindings). Every warm run pays the full deserialization cost again.
Measured on a ~6.3k-file Java-dominant monorepo (same setup as #3086):
ParsedFile store, ~480 MiB of JSON: JSON.parse + reviver 32.8 s vs v8.deserialize of the same object graph 6.35 s.
parse-cache, 17 worker-result shards, ~218 MiB: JSON.parse + mapReviver 9.36 s vs v8.deserialize 1.79 s.
End-to-end on our warm-path matrix (other local cache changes applied): removing the ParsedFile sidecar costs +27.4 s (63.3 s → 90.7 s); removing the parse-cache sidecar +4.7 s (63.3 s → 68.1 s). Graph output identical in both cases.
Proposed solution
Best-effort binary sidecar, JSON stays canonical:
After a shard's object graph exists (worker write for ParsedFiles; saveParseCache for chunk results), also write <shard>.json.v8 = v8.serialize(graph) with a small header carrying the Node major / process.versions.v8.
Readers try the sidecar first; on a missing file, header mismatch, size mismatch or any deserialization error they silently fall back to the JSON + reviver path (the current code, unchanged).
The durable ParsedFile restore copies sidecars together with the JSON; parse-cache save/prune carry them in lockstep with the surviving keys, so a sidecar can never outlive its JSON.
Because the interning reviver also dedups strings, the v8 path should serialize the revived graph (post-intern), so retained memory after load is the same as today.
Any failure in the binary path only loses speed; cache hits, content addressing and graph semantics are unaffected.
Alternatives considered
MessagePack / CBOR: adds a dependency and still needs custom Map/Set handling; v8 is built in and already understands Map/Set.
Replacing JSON entirely with v8: loses cross-Node-version compatibility and the human-readable recovery format; the sidecar keeps JSON as the source of truth.
Byte-identical graph output with and without sidecars (we verified node/edge hashes on our matrix).
Warm run with sidecars present measurably faster on a large repo; sidecar missing/corrupt/mismatched → same result as today, at most a debug log line.
Sidecars pruned/copied exactly alongside their JSON; no orphan .v8 files after prune.
Constraints
Disk: roughly doubles the size of the two stores (the v8 payload is about the size of the JSON).
v8.serialize wire format is not stable across Node majors — the header check is mandatory, and a mismatch must be a silent fallback rather than an error.
Contribution
I am willing to open a PR for this (may need design discussion first).
Area
gitnexus (CLI / core / indexing / MCP server)
Problem or opportunity
Both on-disk stores that feed scope-resolution round-trip through JSON with a custom reviver: ParsedFile shards (
parsedfile-store.ts,makeInterningReviver— string interning plus per-shard def dedup) and parse-cache chunk results (parse-cache.ts,mapReviverforScope.bindings/typeBindings). Every warm run pays the full deserialization cost again.Measured on a ~6.3k-file Java-dominant monorepo (same setup as #3086):
JSON.parse+ reviver 32.8 s vsv8.deserializeof the same object graph 6.35 s.JSON.parse+mapReviver9.36 s vsv8.deserialize1.79 s.End-to-end on our warm-path matrix (other local cache changes applied): removing the ParsedFile sidecar costs +27.4 s (63.3 s → 90.7 s); removing the parse-cache sidecar +4.7 s (63.3 s → 68.1 s). Graph output identical in both cases.
Proposed solution
Best-effort binary sidecar, JSON stays canonical:
saveParseCachefor chunk results), also write<shard>.json.v8=v8.serialize(graph)with a small header carrying the Node major /process.versions.v8.save/prunecarry them in lockstep with the surviving keys, so a sidecar can never outlive its JSON.Any failure in the binary path only loses speed; cache hits, content addressing and graph semantics are unaffected.
Alternatives considered
v8is built in and already understands Map/Set.Acceptance criteria
.v8files afterprune.Constraints
v8.serializewire format is not stable across Node majors — the header check is mandatory, and a mismatch must be a silent fallback rather than an error.Contribution