perf(cache): hot-path wins in CacheAndBufferLayer (2/3 of #991)#993
perf(cache): hot-path wins in CacheAndBufferLayer (2/3 of #991)#993SamTV12345 wants to merge 1 commit into
Conversation
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
Review Summary by QodoPerformance optimization of CacheAndBufferLayer with four compounding wins
WalkthroughsDescriptionPerformance optimization of CacheAndBufferLayer.ts with four compounding wins and comprehensive test coverage: • **Split clone() into cloneIn and cloneOut**: cloneIn preserves toJSON semantics for writes; cloneOut uses V8-native structuredClone for reads with fallback to cloneIn for non-serializable values • **Dirty-key Set tracking**: Replaced full LRU buffer scan in flush() with a Set of dirty keys, reducing iteration overhead from O(buffer size) to O(dirty entries) • **Lazy flush scheduling**: Replaced always-on setInterval with on-demand setTimeout armed only when entries go dirty; idle databases perform zero timer/scan work • **Lock-free fast path in get()**: Cache hits with no concurrent writer skip per-key lock acquisition/release, eliminating promise allocation and Map mutations • **Safety fallback**: cloneOut gracefully handles non-serializable values to prevent DataCloneError on read path • **Comprehensive test coverage**: New test files for dirty-key invariant, lazy flush scheduling, lock-free fast path, and toJSON fallback behavior • **Design documentation**: Includes implementation plan and design specification with correctness arguments and edge case analysis Diagramflowchart LR
A["Read Path"] -->|"uses structuredClone"| B["cloneOut"]
B -->|"fallback for non-serializable"| C["cloneIn"]
D["Write Path"] -->|"preserves toJSON"| C
E["set() call"] -->|"adds to"| F["_dirtyKeys Set"]
F -->|"replaces full scan"| G["flush()"]
H["First dirty entry"] -->|"arms"| I["_flushTimer"]
I -->|"cleared after"| G
J["get() cache hit"] -->|"no concurrent writer"| K["Lock-free fast path"]
K -->|"skips lock acquire/release"| L["Synchronous return"]
File Changes1. lib/CacheAndBufferLayer.ts
|
Code Review by Qodo
1. Timer armed during flush
|
8d62cbf to
db15b67
Compare
db15b67 to
cf97380
Compare
Squashed and rebased onto the post-oxfmt main; reformatted to match the repo's oxfmt style. Combines the following work: - perf(cache): use structuredClone on the read path - perf(cache): track dirty keys in a Set so flush() does not scan the LRU - perf(cache): replace setInterval with on-demand setTimeout - perf(cache): lock-free fast path for get() cache hits - fix(cache): cloneOut falls back to cloneIn for non-cloneable values - docs: cache/buffer layer performance design spec + implementation plan Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
cf97380 to
b24d072
Compare
perf(cache): hot-path wins in CacheAndBufferLayer
Part 2 of 3 from splitting #991. Scope:
lib/CacheAndBufferLayer.tsonly — no public API change, no default change, no behavioral regression (includingtoJSONsemantics). This layer sits on the hot path of every operation regardless of backend.Four compounding wins plus one safety fix:
1.
structuredCloneon the read pathclone()was a recursive deep-copy run on every read and write. Split into:cloneIn(the existingtoJSON-aware walker) for the write direction — keeps documentedtoJSONbehavior.cloneOutusing the V8-nativestructuredClonefor the read direction, where the cached value is already JSON-safe.2. Dirty-key Set
flush()scanned the entire LRU buffer (default cap 10 000) every interval just to find dirty entries. Now aSet<string>of dirty keys is maintained, soflush()iterates only what's actually dirty. Invariant +markDoneordering documented in code.3. Lazy flush scheduling
Replaces the always-on
setIntervalwith an on-demand,unref'dsetTimeoutarmed when the first entry goes dirty and cleared after flushing. Idle databases do zero timer/scan work.4. Lock-free fast path in
get()On a guaranteed cache hit with no in-flight writer,
get()now returns the cloned value without acquiring/releasing the per-key lock (saving a promise allocation + twoMapmutations per call). The fast path is fully synchronous; correctness argument is in the design doc and a code comment marks the no-awaitregion.Safety fix
cloneOutfalls back tocloneInfor valuesstructuredClonecan't handle (e.g. a stored{toJSON: fn}), so the read path never throwsDataCloneError.Tests
New, under
test/mock/:test_dirty_set,test_lazy_flush,test_lock_fast_path; plustest_metricsupdates and atest_tojsoncase for the fallback. All existing cache/metrics/LRU/bulk tests stay green.Verification
tsc --noEmitpasses.vitest run test/mock test/memory→ 185 passing.Independence
Touches only
CacheAndBufferLayer.ts+ tests + design docs — no overlap with the drivers PR, and the only overlap with the formatting PR is one test file (test_tojson.spec.ts). Can be reviewed in parallel.🤖 Generated with Claude Code