perf(db): skip unchanged index updates#1691
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughIndex updates now compare normalized indexed values, skip unchanged writes, and update bucket membership incrementally when values change. Both index implementations share bucket helpers and SameValueZero comparison semantics, while evaluator compilation is cached and tests cover edge cases, failures, and randomized invariants. ChangesIndex update optimization
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant IndexUpdate
participant Comparison
participant IndexBuckets
participant EvaluatorCache
IndexUpdate->>EvaluatorCache: evaluate indexed expression
EvaluatorCache-->>IndexUpdate: normalized old and new values
IndexUpdate->>Comparison: compare normalized values
Comparison-->>IndexUpdate: equality result
IndexUpdate->>IndexBuckets: preserve bucket or move key membership
IndexBuckets-->>IndexUpdate: updated index state
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
packages/db/src/utils/comparison.ts (1)
232-233: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAvoid propagating
anythrough the new index-value boundaries.Use
unknownfor arbitrary normalized values; equality checks andMapaccess do not requireany.
packages/db/src/utils/comparison.ts#L232-L233: change equality parameters tounknown.packages/db/src/indexes/basic-index.ts#L102-L103: changenormalizedValuetounknown.packages/db/src/indexes/basic-index.ts#L146-L147: changenormalizedValuetounknown.packages/db/src/indexes/basic-index.ts#L163-L164: change normalized update locals tounknown.packages/db/src/indexes/btree-index.ts#L104-L105: changenormalizedValuetounknown.packages/db/src/indexes/btree-index.ts#L141-L142: changenormalizedValuetounknown.packages/db/src/indexes/btree-index.ts#L160-L161: change normalized update locals tounknown.As per coding guidelines, “Avoid using
anytypes; useunknowninstead when the type is truly unknown.”🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/db/src/utils/comparison.ts` around lines 232 - 233, Replace any with unknown at all normalized index-value boundaries: update areSameValueZeroEqual parameters in packages/db/src/utils/comparison.ts, normalizedValue parameters and normalized update locals in packages/db/src/indexes/basic-index.ts (lines 102-103, 146-147, 163-164), and the corresponding parameters and locals in packages/db/src/indexes/btree-index.ts (lines 104-105, 141-142, 160-161). Preserve the existing equality checks and Map access behavior.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/db/tests/index-update-short-circuit.test.ts`:
- Around line 40-54: Add an `undefined` to `null` case to the `it.each` table in
the “keeps the existing bucket” test area, asserting the update moves the key to
the `null` bucket rather than reusing or conflating the `undefined` sentinel
bucket; preserve the existing bucket-reuse assertions for genuinely equal
values.
---
Nitpick comments:
In `@packages/db/src/utils/comparison.ts`:
- Around line 232-233: Replace any with unknown at all normalized index-value
boundaries: update areSameValueZeroEqual parameters in
packages/db/src/utils/comparison.ts, normalizedValue parameters and normalized
update locals in packages/db/src/indexes/basic-index.ts (lines 102-103, 146-147,
163-164), and the corresponding parameters and locals in
packages/db/src/indexes/btree-index.ts (lines 104-105, 141-142, 160-161).
Preserve the existing equality checks and Map access behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: ef2caabc-db57-4e0c-85f4-e1f1a1170788
📒 Files selected for processing (5)
.changeset/quick-trees-rest.mdpackages/db/src/indexes/basic-index.tspackages/db/src/indexes/btree-index.tspackages/db/src/utils/comparison.tspackages/db/tests/index-update-short-circuit.test.ts
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/db/src/utils/comparison.ts`:
- Around line 232-233: Remove the local areSameValueZeroEqual implementations
from the BasicIndex and BTreeIndex classes, and import the shared
areSameValueZeroEqual utility from packages/db/src/utils/comparison.ts instead.
Update all references in both index implementations to use the imported function
while preserving existing SameValueZero equality behavior.
- Around line 232-233: Update areSameValueZeroEqual to replace the rejected
self-comparison NaN check with Number.isNaN(a) && Number.isNaN(b), while
preserving strict equality behavior and SameValueZero semantics.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 1008310c-dccd-4ddd-ac8f-5e44838f9321
📒 Files selected for processing (4)
packages/db/src/indexes/basic-index.tspackages/db/src/indexes/btree-index.tspackages/db/src/utils/comparison.tspackages/db/tests/index-update-short-circuit.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- packages/db/tests/index-update-short-circuit.test.ts
- packages/db/src/indexes/btree-index.ts
- packages/db/src/indexes/basic-index.ts
More templates
@tanstack/angular-db
@tanstack/browser-db-sqlite-persistence
@tanstack/capacitor-db-sqlite-persistence
@tanstack/cloudflare-durable-objects-db-sqlite-persistence
@tanstack/db
@tanstack/db-ivm
@tanstack/db-sqlite-persistence-core
@tanstack/electric-db-collection
@tanstack/electron-db-sqlite-persistence
@tanstack/expo-db-sqlite-persistence
@tanstack/node-db-sqlite-persistence
@tanstack/offline-transactions
@tanstack/powersync-db-collection
@tanstack/query-db-collection
@tanstack/react-db
@tanstack/react-native-db-sqlite-persistence
@tanstack/rxdb-db-collection
@tanstack/solid-db
@tanstack/svelte-db
@tanstack/tauri-db-sqlite-persistence
@tanstack/trailbase-db-collection
@tanstack/vue-db
commit: |
Summary
Index updates now skip bucket, tree, and timestamp writes when the indexed value is unchanged. The combined change also caches compiled index evaluators, avoids object-normalization work for primitive values, repairs a
BasicIndexbookkeeping edge case, and adds model-based property coverage for both index types.Root cause
Every update previously called the public remove/add paths for every index, even when the indexed value stayed in the same normalized bucket. That repeated expression compilation, normalization, map/set or B+ tree writes, and timestamp updates for changes to unrelated row fields.
The first no-op guard also exposed an existing partial-state case in
BasicIndex: if removal expression evaluation failed, the value bucket could retain the key whileindexedKeysdropped it. Checking only bucket membership could then return early without repairingkeyCount.Approach
Mapkeys for values such asNaNand signed zero.BasicIndexalso confirms membership inindexedKeys.normalizeValue()before object-specific checks.Key invariants
undefined,null,NaN, signed zero, dates, byte arrays, object identity, and custom comparators retain their prior lookup semantics.keyCount, indexed keys, exact buckets, ordered entries, equality lookups, range queries, and rebuilt state agree with the reference model.Non-goals
oldItemvalues are handled. fix(db): a key in BasicIndex/BTreeIndex lives in at most one bucket #1517 owns the one-key/one-bucket invariant and its extra reverse-map memory cost.Trade-offs
The no-op path performs cheap membership checks before returning. This is slightly more work than comparing values alone, but prevents an optimization from hiding inconsistent bookkeeping.
A reverse key-to-bucket map would also solve stale
oldItemvalues, but adds one map entry per indexed row per index and broadens this performance change into a separate correctness invariant. That work remains isolated in #1517.Benchmark
The original #1691 microbenchmark used 10,000 unique indexed rows, four indexes, 5,000 updates, and five runs; medians are shown:
mainThis is a focused index microbenchmark, not an end-to-end application result. The gain depends on index count, bucket shape, and how often indexed values change. Evaluator caching and the primitive normalization fast path were added after this measurement.
Verification
@tanstack/dbsuite: 2,483 tests passed, 5 skipped.Files changed
base-index.ts: cache the compiled expression evaluator.basic-index.ts: short-circuit unchanged updates, use direct bucket moves, and guard key bookkeeping.btree-index.ts: short-circuit unchanged updates and use direct bucket moves.comparison.ts: add SameValueZero equality and a primitive normalization fast path.index-update-short-circuit.test.ts: cover no-op updates, real moves, edge values, comparators, and evaluation failures.index-update.property.test.ts: compare randomized mutation sequences with a reference model.quick-trees-rest.md: provide one combined patch changeset.Related: #1517, #1645
Summary by CodeRabbit
NaN), while keepingundefinedandnulldistinct.