@push.rocks/smartdb 2.13.1 → 2.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Binary file
Binary file
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartdb',
6
- version: '2.13.1',
6
+ version: '2.14.0',
7
7
  description: 'A MongoDB-compatible embedded database server with wire protocol support, backed by a high-performance Rust engine.'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSxxQkFBcUI7SUFDM0IsT0FBTyxFQUFFLFFBQVE7SUFDakIsV0FBVyxFQUFFLHFIQUFxSDtDQUNuSSxDQUFBIn0=
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@push.rocks/smartdb",
3
- "version": "2.13.1",
3
+ "version": "2.14.0",
4
4
  "private": false,
5
5
  "description": "A MongoDB-compatible embedded database server with wire protocol support, backed by a high-performance Rust engine.",
6
6
  "exports": {
@@ -20,11 +20,11 @@
20
20
  "@git.zone/tsrust": "^1.6.0",
21
21
  "@git.zone/tstest": "^3.6.6",
22
22
  "@types/node": "^26.1.1",
23
- "mongodb": "^7.1.1"
23
+ "mongodb": "^7.5.0"
24
24
  },
25
25
  "dependencies": {
26
- "@push.rocks/smartrust": "^1.3.2",
27
- "bson": "^7.2.0"
26
+ "@push.rocks/smartrust": "^1.4.0",
27
+ "bson": "^7.3.1"
28
28
  },
29
29
  "browserslist": [
30
30
  "last 1 chrome versions"
package/readme.hints.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # smartdb hints
2
2
 
3
+ ## No-Op Write Detection (2.14.0)
4
+
5
+ - `update`/`findAndModify` post-images that equal the stored document skip storage, WAL, index staging, and oplog entirely; volatile-only rewrites (top-level `_updatedAt` differs, everything else equal) are skipped the same way and keep the stored `_updatedAt`. Both report `matched=1, modified=0`.
6
+ - Classification lives in `rustdb-commands/src/handlers/update_handler.rs` (`classify_document_rewrite`); the volatile field list is the `VOLATILE_METADATA_FIELDS` const. The comparison is key-order-insensitive (replacement paths re-append `_id`) and bails to "changed" on duplicate keys.
7
+ - Counters: `serverStatus.writes.{updatesWritten, noopSkipped.identical, noopSkipped.volatileOnly}` backed by `WriteMetrics` on `CommandContext`. Wire-level coverage in `test/test.noop-writes.ts`.
8
+ - Consequence for consumers: a save that changes nothing no longer bumps `_updatedAt` and emits no oplog event. To force a touch, change a non-volatile field.
9
+
3
10
  ## Static Rust Binaries
4
11
 
5
12
  - The Rust binaries in `dist_rust/` are statically linked (static-pie) via `"static": true` in the `@git.zone/tsrust` block of `.smartconfig.json` (tsrust >= 1.4.1). They run on both glibc (Debian/Ubuntu) and musl (Alpine) systems.
package/readme.md CHANGED
@@ -227,6 +227,33 @@ Each entry contains:
227
227
 
228
228
  ---
229
229
 
230
+ ## ✋ No-Op Write Detection
231
+
232
+ The engine detects document rewrites that change nothing and skips them entirely — no storage write, no WAL append, no index update, and no oplog entry. A skipped rewrite still counts as matched (`matchedCount: 1`) but reports `modifiedCount: 0`.
233
+
234
+ Two classes are skipped:
235
+
236
+ - **Identical** — the post-image equals the stored document byte for byte.
237
+ - **Volatile-only** — the post-image differs only in top-level volatile metadata fields (currently `_updatedAt`, the field ODM layers such as `@push.rocks/smartdata` restamp on every save). The stored document is kept as-is, including its existing `_updatedAt`, so the timestamp means "last real change" rather than "last save call".
238
+
239
+ This makes periodic reconcile loops that re-save unchanged documents cost nothing at the engine level. A caller that needs a document to actually change must change a non-volatile field.
240
+
241
+ Counters are exposed through `serverStatus`:
242
+
243
+ ```javascript
244
+ const status = await db.command({ serverStatus: 1 });
245
+ console.log(status.writes);
246
+ // {
247
+ // updatesWritten: 42, // rewrites that reached storage, index, and oplog
248
+ // noopSkipped: {
249
+ // identical: 1337, // post-image equal to the stored document
250
+ // volatileOnly: 271, // only volatile metadata differed
251
+ // },
252
+ // }
253
+ ```
254
+
255
+ ---
256
+
230
257
  ## API Reference
231
258
 
232
259
  ### SmartdbServer
package/readme.plan.md ADDED
@@ -0,0 +1,99 @@
1
+ # smartdb advancement plan
2
+
3
+ Status: canonical direction, approved 2026-07-21. Reread before starting any engine work.
4
+ Baseline: @push.rocks/smartdb 2.13.2 (TypeScript API + rustdb Rust engine, ~36k Rust LOC, 255 test fns).
5
+
6
+ ## North star
7
+
8
+ Make smartdb a boring database:
9
+
10
+ - **provably crash-consistent** — recovery behavior is a written contract, enforced by a harness, not an aspiration
11
+ - **cost proportional to change** — a write that changes nothing costs nothing; a small change costs a small amount of WAL, oplog, index, and CPU
12
+ - **bounded** — memory, cursors, sessions, scans, and on-disk growth all have enforced ceilings
13
+ - **self-describing** — the engine can explain its own resource usage and slow operations without external tooling
14
+
15
+ ## Evidence base (incident ledger)
16
+
17
+ Every track below traces to a production incident on the dcrouter hub:
18
+
19
+ 1. **Immortal cursors / unreaped sessions** — cursors and transaction sessions accumulated without bound. Fixed in 2.13.x (`CursorOwner`, per-cursor and global byte caps in `rustdb-commands/src/context.rs`, `remove_connection_cursors`, `take_expired_sessions`). The class stays closed only if bounded-lifecycle remains a tested invariant.
20
+ 2. **Query-vs-storage divergence** — an empty `_id_` query index was created at restart while the storage KeyDir still held documents. Queries said "not found"; inserts hit the storage-layer duplicate check (`rustdb-storage/src/memory.rs` / `file.rs` `AlreadyExists`) and threw "already exists: document '<sha>'". Mitigated in 2.13.x (index rebuild at startup, quarantine of invalid legacy unique indexes). The invariant — query layer and storage layer must never disagree after recovery — is not yet machine-checked.
21
+ 3. **Write amplification on unchanged saves** — callers re-saving identical documents caused full document rewrites plus full before/after images in the oplog (10k entries / 64MB), driving rustdb to 45–82% CPU. Currently mitigated caller-side only (change-gated persistence in the main consumer). The engine still trusts callers not to churn.
22
+ 4. **Bulk-operation ceilings** — deleteMany batch limits and command-size caps made a large startup migration exceed its deadline and forced a rollback. Bulk work is client-paced, non-resumable, and dies with its connection.
23
+ 5. **Memory disproportion** — steady-state PSS reached 40–100× live data bytes at points (dual tree+hash indexes per field, allocator retention, oplog images). No ceiling is asserted anywhere.
24
+
25
+ ## Track 1 — Provable durability (foundation, non-negotiable)
26
+
27
+ - Write the **recovery contract**: for a process kill at any byte offset — during WAL append, compaction, or hint write — state exactly what is durable, what may be lost, and what is rebuilt.
28
+ - Build a **crash-fuzz harness**: property-based runner that executes randomized workloads, kills the engine at random points, restarts, and asserts the invariants:
29
+ 1. every acknowledged write is present
30
+ 2. no phantom documents
31
+ 3. query indexes agree exactly with the storage KeyDir (incident 2 becomes a permanent regression class)
32
+ 4. unique constraints hold
33
+ 5. cursor/session tables are empty after recovery
34
+ - Make the harness a **CI gate**: no release ships without it green.
35
+
36
+ Provable property: "kill -9 at any moment loses at most unacknowledged writes, and recovery always converges to a consistent index/storage view."
37
+
38
+ ## Track 2 — Write-path economics: cost proportional to change
39
+
40
+ - **Engine-side no-op write detection** — SHIPPED in 2.14.0: update/findAndModify post-images that equal the stored document (or differ only in the volatile `_updatedAt` metadata field) skip storage, WAL, index, and oplog entirely and report `matched=1, modified=0`. Counters in `serverStatus.writes`. Caller-side gating remains defense in depth, not the architecture.
41
+ - **Oplog diet**: replace full before/after images with `{ns, id, opType, generation, optional patch}`. Consumers that need images read storage at the generation.
42
+ - **Background compaction** (bitcask merge) with cooldown and rate limit, bounding `data.rdb` growth under update churn.
43
+ - **Group commit**: coalesce WAL fsyncs across concurrent writers.
44
+
45
+ Provable property: "N identical saves cost O(1) WAL bytes and O(1) oplog bytes after the first."
46
+
47
+ ## Track 3 — Read-path economics
48
+
49
+ - **Index-assisted top-k**: extend the bounded sorted-scan work (2.13.2) so the planner walks an index in sort order instead of scanning and heap-selecting.
50
+ - **Maintained aggregates**: per-collection counts and sizes served from maintained state, never full scans.
51
+ - **Reads never schedule writes**: engine principle — no read path may enqueue hint rewrites, stat persistence, or any other write work.
52
+
53
+ Provable property: "a monitoring read loop at any frequency produces zero engine writes."
54
+
55
+ ## Track 4 — Server-side bulk and migration primitives
56
+
57
+ - **Uncapped bulk operations**: deleteMany/updateMany execute engine-side with internal checkpointing (by generation / seek offset), not client-paced batches under command-size caps.
58
+ - **Streaming IPC frames** for large results, retiring the command-size failure class.
59
+ - **Re-attachable maintenance sessions**: a migration survives client disconnect; the client re-attaches by operation id and polls progress. The incident-4 rollback class becomes impossible.
60
+
61
+ Provable property: "a 10M-row migration survives a client restart and completes exactly once."
62
+
63
+ ## Track 5 — Memory model: bounded and measured
64
+
65
+ - **k-factor target**: steady-state PSS ≤ 3× live data bytes (observed today: 40–100× under some workloads).
66
+ - **Single index representation** per field where one suffices; lazy-build the second form on demand.
67
+ - KeyDir compaction / shrink-to-fit after mass deletes.
68
+ - **Soak-test CI gate**: 24h synthetic churn workload asserting PSS plateaus within the k-factor.
69
+ - Verify mimalloc purge/decommit settings under the soak test.
70
+
71
+ Provable property: "PSS is a function of live data, not of history."
72
+
73
+ ## Track 6 — Operability and compatibility
74
+
75
+ - **serverStatus counters**: no-op writes skipped, WAL bytes written, compaction stats, live cursors/sessions, per-collection sizes — the next incident gets diagnosed from the database, not from strace.
76
+ - **Slow-op log** with thresholds.
77
+ - **Offline toolbox**: `rustdb verify | dump | compact | repair-doc | backup` against a data directory.
78
+ - **On-disk format manifest** with versioning and an N-1 rollback contract: any release can downgrade one release; format changes require a migration note.
79
+
80
+ Provable property: "any resource question an operator asks during an incident is answerable from serverStatus or the toolbox."
81
+
82
+ ## Fork in the road (decision rule, written now)
83
+
84
+ Keep hardening the owned engine **while the Track 1 harness stays green**. If, after Tracks 1–2 land, the harness keeps finding structural durability holes, evaluate rebasing the storage layer on a proven embedded core (redb or fjall) behind the unchanged command surface. Revisit this rule once Track 1 is merged — not before, not on gut feeling.
85
+
86
+ ## Sequencing
87
+
88
+ - **Done**: Track 2 no-op write detection + its serverStatus counters (2.14.0).
89
+ - **Now** (after the current consumer release wave is verified in production): Track 1 harness + recovery contract.
90
+ - **Next**: Track 2 oplog diet + compaction; Track 4 bulk primitives.
91
+ - **Mid-term**: Track 3 read economics; Track 5 k-factor + soak gate; Track 6 toolbox + format manifest.
92
+
93
+ Format-affecting changes (oplog diet, manifest) target a major version; everything else lands in 2.14+.
94
+
95
+ ## Non-goals
96
+
97
+ - clustering or replication beyond the current oplog consumers
98
+ - MongoDB API parity
99
+ - multi-process writers
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartdb',
6
- version: '2.13.1',
6
+ version: '2.14.0',
7
7
  description: 'A MongoDB-compatible embedded database server with wire protocol support, backed by a high-performance Rust engine.'
8
8
  }