@push.rocks/smartdb 2.13.2 → 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.
- package/dist_rust/rustdb_linux_amd64 +0 -0
- package/dist_rust/rustdb_linux_arm64 +0 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/package.json +1 -1
- package/readme.hints.md +7 -0
- package/readme.md +27 -0
- package/readme.plan.md +99 -0
- package/ts/00_commitinfo_data.ts +1 -1
|
Binary file
|
|
Binary file
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@push.rocks/smartdb',
|
|
6
|
-
version: '2.
|
|
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
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
|
package/ts/00_commitinfo_data.ts
CHANGED