M4: KV indexes with pattern trie, atomic and non-atomic write paths, read-repair, range findBy

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Qxbkp4tA7G5SCWjzWuxmE
This commit is contained in:
Claude
2026-10-23 07:04:19 +00:00
parent 8f8c880efd
commit 8bc1c27c40
3 changed files with 271 additions and 1 deletions
+25
View File
@@ -434,6 +434,31 @@ Decisions taken where the brief was silent. Each is the simplest option consiste
- **Exposed low-level properties** on every node: `path`, `selfKey`, `prefix`, `end` (subtree upper
bound), `driver`, `separator`, `isRoot`. The event and document layers build on these.
### M4 — KV indexes
- **Pattern syntax**: segments separated by the storage separator; `*` matches one segment; `''`
names the root. A literal `*` key can't be targeted by a pattern.
- **Overlapping patterns** that declare the same index name throw at construction.
- **Index trie state is stepped per `child()` call** (O(1) in patterns), so each node resolves its
indexes in O(depth) overall. A node holds two bound index sets: one over its children (for child
writes) and its parent's (for self-form writes such as `users.child('1').set(v)`).
- **Index function results**: `undefined` → not indexed; arrays are multi-entry and de-duplicated
per child; anything that is not `string | number | boolean | null` (or `NaN`) throws a `TypeError`
at write time. Index functions are not called for missing (`undefined`) values.
- **`findBy` results** are `[key, value]` pairs (the value is re-read anyway for verification).
Range queries accept `gt`/`gte`/`lt`/`lte`. Results are in index-value order, then child-key order,
and range queries de-duplicate multi-entry hits.
- **`findBy` batching**: entry keys are read in chunks of 128 (or of the remaining `limit`, if
smaller) and values fetched with one `getMany` per chunk. Stale entries are deleted with one
`deleteMany` per chunk.
- **Read-repair race (non-atomic drivers only)**: a reader can see a concurrent writer's fresh
index add before that writer's value write lands, judge it stale, and delete it. With multiple
writers on a driver without `batch`, this can lose an index entry. Mitigation: `readRepair: false`
on `KVStorage` (stale entries are then only skipped, never deleted). With `batch`, entries never go
stale, so the race doesn't arise.
- **`set` of an indexed child with duplicate keys in one `setMany`**: last write wins, and index
diffs are computed once per key.
<!-- decisions appended per milestone -->
## 13. Benchmark baselines