fix: O(1) positional lookup in the web streaming reducers - #99
Conversation
applyMessage and applyDelta scanned the session buffer with findIndex from index 0 on every event. The #73/#75 fix made the EXISTENCE check O(1), but the positional lookup still walked all N store-proxied entries inside produce() — O(N) per streaming delta, O(N²) over a session, so long transcripts got progressively laggier. The per-session existence Set is now a messageId → index Map, kept in sync on insert (buffer is append-only) and rebuilt wholesale by replaceScrollback from the dedupe pass it already runs. Both reducers use it for the positional lookup. 2000 tail deltas on a 5000-message session: 46 µs/delta → 1 µs/delta (and the old cost kept growing with transcript length). Fixes #90 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughReplaces the per-session existence Set (idsBySession) with an O(1) positional index (indexBySession: messageId → array index) in the message store. applyMessage, applyDelta, replaceScrollback, hasMessage, clearSessionMessages, and the test reset helper are updated to use the new index; new tests validate positional lookup correctness. ChangesPositional message index optimization
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #99 +/- ##
=======================================
Coverage 73.06% 73.06%
=======================================
Files 65 65
Lines 11032 11032
=======================================
Hits 8060 8060
Misses 2972 2972
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
…log (#117) The 0.2.0 entry only covered the protocol/packages train (#100-#116) and missed ten PRs that also ship in this release: the untrusted-content sanitization and cross-tenant memory fixes (#91, #93 — now under a proper Security heading), the performance run (#94-#99), and the model catalog work (#78, #79). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Problem (#90)
The web streaming reducers scanned the message array with
buf.findIndex(...)from index 0 on every message and every delta. The #73/#75 fix added an O(1)idsBySessionexistence Set, but only for the existence check — the positional lookup stayed linear, and it runs over store-proxied entries insideproduce(), which is far more expensive per element than a plain array walk. Deltas almost always target the tail message, so a 5000-message session paid the full O(N) scan per streaming chunk: O(N²) over the session, compounding the markdown re-parse cost (#87, fixed separately in #98).Fix
The per-session existence Set becomes a
messageId → indexMap (indexBySession):applyMessageuses it for the upsert position and records the index on append — valid forever, since the buffer is append-only.applyDeltareplaces both thehasMessagepre-check and thefindIndexwith one map lookup.replaceScrollbackalready computes exactly this map during its dedupe pass (posById) — it's now installed wholesale instead of being collapsed into a Set.hasMessage/clearSessionMessages/ the test-reset hook read the same structure; semantics unchanged.Micro-bench
Same script (public reducer API), run against
mainand this branch — 2000 tail deltas on a 5000-message session:The old per-delta cost also grew linearly with transcript length; the new one is constant.
Tests
All 17 existing reducer tests pass unchanged (upsert semantics, delta patching, dedupe-on-replay, epoch/version bumps, session clearing). Two new cases pin the positional index specifically: deltas land on the correct non-tail targets in a 2000-message buffer, and the index survives re-broadcast upserts plus
replaceScrollbackrebuilds with duplicate messageIds.tsc -b✓ · eslint identical warning set tomain✓ ·vitest run154/154 ✓Note: web coverage is not uploaded to codecov, so
codecov/patchis unaffected by this PR.Fixes #90
🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
Performance