nedb-engine 2.8.4__tar.gz → 2.8.6__tar.gz
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.
- {nedb_engine-2.8.4 → nedb_engine-2.8.6}/.gitignore +4 -0
- {nedb_engine-2.8.4 → nedb_engine-2.8.6}/PKG-INFO +79 -1
- {nedb_engine-2.8.4 → nedb_engine-2.8.6}/README.md +78 -0
- {nedb_engine-2.8.4 → nedb_engine-2.8.6}/client/node/README.md +20 -0
- {nedb_engine-2.8.4 → nedb_engine-2.8.6}/client/python/README.md +20 -0
- {nedb_engine-2.8.4 → nedb_engine-2.8.6}/pyproject.toml +1 -1
- {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/__init__.py +1 -1
- nedb_engine-2.8.6/python/nedb/_native.abi3.so +0 -0
- nedb_engine-2.8.6/python/nedb/nedbd-v2 +0 -0
- nedb_engine-2.8.6/python/nedb/nedbd-v2-darwin-arm64 +0 -0
- nedb_engine-2.8.6/python/nedb/nedbd-v2-darwin-x64 +0 -0
- nedb_engine-2.8.6/python/nedb/nedbd-v2.exe +0 -0
- {nedb_engine-2.8.4 → nedb_engine-2.8.6}/rust/nedb-v2/README.md +77 -0
- nedb_engine-2.8.4/python/nedb/_native.abi3.so +0 -0
- nedb_engine-2.8.4/python/nedb/nedbd-v2 +0 -0
- nedb_engine-2.8.4/python/nedb/nedbd-v2-darwin-arm64 +0 -0
- nedb_engine-2.8.4/python/nedb/nedbd-v2-darwin-x64 +0 -0
- nedb_engine-2.8.4/python/nedb/nedbd-v2.exe +0 -0
- {nedb_engine-2.8.4 → nedb_engine-2.8.6}/LICENSE +0 -0
- {nedb_engine-2.8.4 → nedb_engine-2.8.6}/examples/agent-loop/README.md +0 -0
- {nedb_engine-2.8.4 → nedb_engine-2.8.6}/examples/mini-chain/README.md +0 -0
- {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/autoindex.py +0 -0
- {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/backends/__init__.py +0 -0
- {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/backends/redis_backend.py +0 -0
- {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/cascade.py +0 -0
- {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/client.py +0 -0
- {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/concurrent.py +0 -0
- {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/crypto.py +0 -0
- {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/engine.py +0 -0
- {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/index.py +0 -0
- {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/log.py +0 -0
- {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/merkle.py +0 -0
- {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/mongo.py +0 -0
- {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/proof.py +0 -0
- {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/query.py +0 -0
- {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/redis_compat.py +0 -0
- {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/relations.py +0 -0
- {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/resp2.py +0 -0
- {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/server.py +0 -0
- {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/snapshot.py +0 -0
- {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/sql.py +0 -0
- {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/store.py +0 -0
- {nedb_engine-2.8.4 → nedb_engine-2.8.6}/python/nedb/wrap_redis.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: nedb-engine
|
|
3
|
-
Version: 2.8.
|
|
3
|
+
Version: 2.8.6
|
|
4
4
|
Summary: NEDB — a versioned, self-compressing, time-traveling embedded database (replay-protected, idempotent, relational, searchable) with durable AOF persistence and a server daemon (nedbd).
|
|
5
5
|
Project-URL: Homepage, https://github.com/aiassistsecure/nedb
|
|
6
6
|
Project-URL: Repository, https://github.com/aiassistsecure/nedb
|
|
@@ -41,6 +41,83 @@ One Rust core → ships to **PyPI** and **npm** from a single source.
|
|
|
41
41
|
|
|
42
42
|
---
|
|
43
43
|
|
|
44
|
+
## ⚠️ New in 2.8.6 — Durability & Recovery (read this if you store anything you care about)
|
|
45
|
+
|
|
46
|
+
Three defects found by killing a real engine at every persistence boundary and by filling a real
|
|
47
|
+
filesystem to zero free blocks. All three are fixed. **If you are on 2.8.5 or earlier, upgrade.**
|
|
48
|
+
|
|
49
|
+
### 1. A failed flush silently discarded acknowledged writes
|
|
50
|
+
|
|
51
|
+
`IdIndex::flush_write_buf` cleared every buffered entry regardless of whether its disk write
|
|
52
|
+
succeeded. So a flush that hit `ENOSPC` threw the entry away, and no later flush retried it.
|
|
53
|
+
|
|
54
|
+
Reproduced on a full 22 MiB filesystem: **30 rows acknowledged by `put() -> Ok`, then `list()`
|
|
55
|
+
returned 0 after reopen — while `verify()` reported all 30 objects healthy.** The content-addressed
|
|
56
|
+
objects were durable; the id-index entries that make them findable were gone.
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
before: try_flush_all() -> (no return value) reopen -> 0 rows, verify() = 30 ok
|
|
60
|
+
after: try_flush_all() -> Err("id-index leaf rows/buf_25: No space left on device (os error 28)")
|
|
61
|
+
...free space, retry -> Ok reopen -> 30 rows
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Fixed:** an entry leaves the WAL only when its write actually landed. Failures stay buffered and
|
|
65
|
+
retry on the next flush.
|
|
66
|
+
|
|
67
|
+
### 2. Flush errors were unobservable — new `try_flush_all()`
|
|
68
|
+
|
|
69
|
+
`flush_all()` returns `()` and logged fsync failures to stderr, so a caller could not tell a durable
|
|
70
|
+
flush from a failed one. Anything that takes a destructive or externally-visible action on the
|
|
71
|
+
strength of a persisted record needs to know.
|
|
72
|
+
|
|
73
|
+
```rust
|
|
74
|
+
// Use this when the outcome matters:
|
|
75
|
+
db.try_flush_all()?; // Result<()> — id-index WAL + segment sync + MANIFEST
|
|
76
|
+
|
|
77
|
+
// Still available, still logs, nowhere to propagate (ticker / Drop):
|
|
78
|
+
db.flush_all();
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Also new: `Db::try_flush_manifest()` and `IdIndex::try_flush_write_buf()`.
|
|
82
|
+
|
|
83
|
+
### 3. `repair` could not repair, and `since()` claimed "caught up" while behind
|
|
84
|
+
|
|
85
|
+
The cold scan rebuilt `seq_index`, per-collection tips, the Merkle head and `MANIFEST` — but **never
|
|
86
|
+
the id index**. A database whose WAL never reached disk came back with every object verifying and
|
|
87
|
+
`list()` empty, and `nedb-cli repair` printed success without fixing it, because
|
|
88
|
+
`start_cold_scan()` is a deliberate no-op on a warm store.
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
nedb-cli repair ./data
|
|
92
|
+
# repaired: 203 id-index entr(ies) rebuilt, 203 node(s) verified, flushed
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
```rust
|
|
96
|
+
let restored = db.repair()?; // rebuild id index from objects; highest seq wins
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Every object carries its own `coll`, `id` and `seq`, so the id index is fully derivable — a lost WAL
|
|
100
|
+
is recoverable and nothing is invented. `repair()` also recomputes head and tips, so a repaired
|
|
101
|
+
database reopens **warm** instead of coming back up cold with an empty head.
|
|
102
|
+
|
|
103
|
+
Separately, `since()` set `has_more = hit_limit` alone. On a warm boot the seq index is empty **by
|
|
104
|
+
design** (that is why warm start is O(1)), so every lookup missed and `since()` returned zero nodes
|
|
105
|
+
with `has_more = false` — indistinguishable from genuinely up to date. A consumer following the
|
|
106
|
+
documented drain loop stopped one call in, with every record unread.
|
|
107
|
+
|
|
108
|
+
**Fixed:** `has_more` is true whenever the cursor is behind the log head. `ScanStatus` gains
|
|
109
|
+
**`seq_index_ready`** — replication consumers should gate on that, not on `scan_complete`, which is
|
|
110
|
+
true on a warm boot precisely because the scan was skipped.
|
|
111
|
+
|
|
112
|
+
### Known sharp edge (documented, not changed)
|
|
113
|
+
|
|
114
|
+
`since()`'s cursor is **exclusive** and seqs start at 0, so `since(0, _)` returns `(0, head]` and the
|
|
115
|
+
very first write in a database (seq 0) is unreachable through any cursor value. Ten writes drain as
|
|
116
|
+
nine records. Changing the convention would break existing consumers; a replica seeded from
|
|
117
|
+
`since()` alone starts one record short.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
44
121
|
## NEDB v2.8.0 — Production Stable
|
|
45
122
|
|
|
46
123
|
**Current stable: 2.8.0** — NEDB ships as **three version-aligned distributions** on one tag — `nedb-engine` (flagship), `crypto-database` (verifiable v2/v3 DAG), and `aof-db` (fast append-only) — across npm / PyPI / crates.io with full mac + linux + windows native addons (see [**Releasing**](#releasing) below). All native wheels (Linux + Windows on GitHub Actions; macOS arm64 + x86_64 on Codemagic M2 Mac Minis) **plus** the universal pure-Python wheel ship from a single `v*` tag, with the `nedbd-v2` binary bundled inside `pip install nedb-engine`.
|
|
@@ -68,6 +145,7 @@ Off by default — feature-gated at compile time, flag-gated at runtime, and `ex
|
|
|
68
145
|
**New in 2.5.x:**
|
|
69
146
|
|
|
70
147
|
- **Durable-mode auto-flush-on-exit** — a durable store flushes buffered writes on `Ctrl+C` / `SIGTERM`, not just on a clean `Drop`. Automatic in the Node and Python bindings; `Db::install_exit_flush(Arc<Db>)` for standalone Rust binaries. See [**docs/DURABILITY.md**](docs/DURABILITY.md).
|
|
148
|
+
- **2.8.5 — embedded bindings flush on a cadence.** `NedbCore.open()` (Node + Python) now runs the 1 s manifest ticker exactly as `nedbd` does, so a `SIGKILL` / OOM / power cut loses at most one tick of acknowledged writes instead of everything since open. `NEDB_FLUSH_MS` tunes or disables it. See [docs/DURABILITY.md](docs/DURABILITY.md).
|
|
71
149
|
- **`nedb-cli`** — operate on a store directory offline (`head` · `status` · `verify` · `get` · `scan` · `flush` · `repair` · `export`), and **`nedb-inspector`** — a deterministic (no-regex, no-LLM) checker that warns when a durable open lacks flush-on-exit wiring. See [**docs/CLI.md**](docs/CLI.md).
|
|
72
150
|
- **Replication contract** — `tip()` (the latest write), a bounded `since()` changefeed, and a `scan_status()` readiness gate. See [**docs/REPLICATION.md**](docs/REPLICATION.md).
|
|
73
151
|
|
|
@@ -20,6 +20,83 @@ One Rust core → ships to **PyPI** and **npm** from a single source.
|
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
23
|
+
## ⚠️ New in 2.8.6 — Durability & Recovery (read this if you store anything you care about)
|
|
24
|
+
|
|
25
|
+
Three defects found by killing a real engine at every persistence boundary and by filling a real
|
|
26
|
+
filesystem to zero free blocks. All three are fixed. **If you are on 2.8.5 or earlier, upgrade.**
|
|
27
|
+
|
|
28
|
+
### 1. A failed flush silently discarded acknowledged writes
|
|
29
|
+
|
|
30
|
+
`IdIndex::flush_write_buf` cleared every buffered entry regardless of whether its disk write
|
|
31
|
+
succeeded. So a flush that hit `ENOSPC` threw the entry away, and no later flush retried it.
|
|
32
|
+
|
|
33
|
+
Reproduced on a full 22 MiB filesystem: **30 rows acknowledged by `put() -> Ok`, then `list()`
|
|
34
|
+
returned 0 after reopen — while `verify()` reported all 30 objects healthy.** The content-addressed
|
|
35
|
+
objects were durable; the id-index entries that make them findable were gone.
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
before: try_flush_all() -> (no return value) reopen -> 0 rows, verify() = 30 ok
|
|
39
|
+
after: try_flush_all() -> Err("id-index leaf rows/buf_25: No space left on device (os error 28)")
|
|
40
|
+
...free space, retry -> Ok reopen -> 30 rows
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Fixed:** an entry leaves the WAL only when its write actually landed. Failures stay buffered and
|
|
44
|
+
retry on the next flush.
|
|
45
|
+
|
|
46
|
+
### 2. Flush errors were unobservable — new `try_flush_all()`
|
|
47
|
+
|
|
48
|
+
`flush_all()` returns `()` and logged fsync failures to stderr, so a caller could not tell a durable
|
|
49
|
+
flush from a failed one. Anything that takes a destructive or externally-visible action on the
|
|
50
|
+
strength of a persisted record needs to know.
|
|
51
|
+
|
|
52
|
+
```rust
|
|
53
|
+
// Use this when the outcome matters:
|
|
54
|
+
db.try_flush_all()?; // Result<()> — id-index WAL + segment sync + MANIFEST
|
|
55
|
+
|
|
56
|
+
// Still available, still logs, nowhere to propagate (ticker / Drop):
|
|
57
|
+
db.flush_all();
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Also new: `Db::try_flush_manifest()` and `IdIndex::try_flush_write_buf()`.
|
|
61
|
+
|
|
62
|
+
### 3. `repair` could not repair, and `since()` claimed "caught up" while behind
|
|
63
|
+
|
|
64
|
+
The cold scan rebuilt `seq_index`, per-collection tips, the Merkle head and `MANIFEST` — but **never
|
|
65
|
+
the id index**. A database whose WAL never reached disk came back with every object verifying and
|
|
66
|
+
`list()` empty, and `nedb-cli repair` printed success without fixing it, because
|
|
67
|
+
`start_cold_scan()` is a deliberate no-op on a warm store.
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
nedb-cli repair ./data
|
|
71
|
+
# repaired: 203 id-index entr(ies) rebuilt, 203 node(s) verified, flushed
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
```rust
|
|
75
|
+
let restored = db.repair()?; // rebuild id index from objects; highest seq wins
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Every object carries its own `coll`, `id` and `seq`, so the id index is fully derivable — a lost WAL
|
|
79
|
+
is recoverable and nothing is invented. `repair()` also recomputes head and tips, so a repaired
|
|
80
|
+
database reopens **warm** instead of coming back up cold with an empty head.
|
|
81
|
+
|
|
82
|
+
Separately, `since()` set `has_more = hit_limit` alone. On a warm boot the seq index is empty **by
|
|
83
|
+
design** (that is why warm start is O(1)), so every lookup missed and `since()` returned zero nodes
|
|
84
|
+
with `has_more = false` — indistinguishable from genuinely up to date. A consumer following the
|
|
85
|
+
documented drain loop stopped one call in, with every record unread.
|
|
86
|
+
|
|
87
|
+
**Fixed:** `has_more` is true whenever the cursor is behind the log head. `ScanStatus` gains
|
|
88
|
+
**`seq_index_ready`** — replication consumers should gate on that, not on `scan_complete`, which is
|
|
89
|
+
true on a warm boot precisely because the scan was skipped.
|
|
90
|
+
|
|
91
|
+
### Known sharp edge (documented, not changed)
|
|
92
|
+
|
|
93
|
+
`since()`'s cursor is **exclusive** and seqs start at 0, so `since(0, _)` returns `(0, head]` and the
|
|
94
|
+
very first write in a database (seq 0) is unreachable through any cursor value. Ten writes drain as
|
|
95
|
+
nine records. Changing the convention would break existing consumers; a replica seeded from
|
|
96
|
+
`since()` alone starts one record short.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
23
100
|
## NEDB v2.8.0 — Production Stable
|
|
24
101
|
|
|
25
102
|
**Current stable: 2.8.0** — NEDB ships as **three version-aligned distributions** on one tag — `nedb-engine` (flagship), `crypto-database` (verifiable v2/v3 DAG), and `aof-db` (fast append-only) — across npm / PyPI / crates.io with full mac + linux + windows native addons (see [**Releasing**](#releasing) below). All native wheels (Linux + Windows on GitHub Actions; macOS arm64 + x86_64 on Codemagic M2 Mac Minis) **plus** the universal pure-Python wheel ship from a single `v*` tag, with the `nedbd-v2` binary bundled inside `pip install nedb-engine`.
|
|
@@ -47,6 +124,7 @@ Off by default — feature-gated at compile time, flag-gated at runtime, and `ex
|
|
|
47
124
|
**New in 2.5.x:**
|
|
48
125
|
|
|
49
126
|
- **Durable-mode auto-flush-on-exit** — a durable store flushes buffered writes on `Ctrl+C` / `SIGTERM`, not just on a clean `Drop`. Automatic in the Node and Python bindings; `Db::install_exit_flush(Arc<Db>)` for standalone Rust binaries. See [**docs/DURABILITY.md**](docs/DURABILITY.md).
|
|
127
|
+
- **2.8.5 — embedded bindings flush on a cadence.** `NedbCore.open()` (Node + Python) now runs the 1 s manifest ticker exactly as `nedbd` does, so a `SIGKILL` / OOM / power cut loses at most one tick of acknowledged writes instead of everything since open. `NEDB_FLUSH_MS` tunes or disables it. See [docs/DURABILITY.md](docs/DURABILITY.md).
|
|
50
128
|
- **`nedb-cli`** — operate on a store directory offline (`head` · `status` · `verify` · `get` · `scan` · `flush` · `repair` · `export`), and **`nedb-inspector`** — a deterministic (no-regex, no-LLM) checker that warns when a durable open lacks flush-on-exit wiring. See [**docs/CLI.md**](docs/CLI.md).
|
|
51
129
|
- **Replication contract** — `tip()` (the latest write), a bounded `since()` changefeed, and a `scan_status()` readiness gate. See [**docs/REPLICATION.md**](docs/REPLICATION.md).
|
|
52
130
|
|
|
@@ -9,6 +9,26 @@ Connect to any running `nedbd` instance from Node.js or the browser. No engine e
|
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## ⚠️ Upgrade your server to nedbd 2.8.6
|
|
15
|
+
|
|
16
|
+
This client talks to `nedbd`, so its durability comes from the server. **2.8.6 fixes three defects in
|
|
17
|
+
2.8.5 and earlier** — a flush that hit a full disk silently discarded acknowledged writes, flush
|
|
18
|
+
failures were unobservable, and `nedb-cli repair` could not actually rebuild a damaged id index.
|
|
19
|
+
|
|
20
|
+
If a database ever returned rows and now returns none while `/verify` reports every object healthy,
|
|
21
|
+
that is the lost-id-index symptom and it is recoverable:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
nedb-cli repair ./data
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Replication consumers paging `/since`: gate historical catch-up on **`seq_index_ready`** from
|
|
28
|
+
`/status`, not on `scan_complete`. `scan_complete` is true on a warm boot precisely because the scan
|
|
29
|
+
was skipped, and before 2.8.6 `since` reported `has_more: false` in that state — which reads as
|
|
30
|
+
"caught up" on a database with every record unread.
|
|
31
|
+
|
|
12
32
|
## Install
|
|
13
33
|
|
|
14
34
|
```bash
|
|
@@ -10,6 +10,26 @@ Connect to any running `nedbd` instance — local or remote — with a clean asy
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## ⚠️ Upgrade your server to nedbd 2.8.6
|
|
16
|
+
|
|
17
|
+
This client talks to `nedbd`, so its durability comes from the server. **2.8.6 fixes three defects in
|
|
18
|
+
2.8.5 and earlier** — a flush that hit a full disk silently discarded acknowledged writes, flush
|
|
19
|
+
failures were unobservable, and `nedb-cli repair` could not actually rebuild a damaged id index.
|
|
20
|
+
|
|
21
|
+
If a database ever returned rows and now returns none while `/verify` reports every object healthy,
|
|
22
|
+
that is the lost-id-index symptom and it is recoverable:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
nedb-cli repair ./data
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Replication consumers paging `/since`: gate historical catch-up on **`seq_index_ready`** from
|
|
29
|
+
`/status`, not on `scan_complete`. `scan_complete` is true on a warm boot precisely because the scan
|
|
30
|
+
was skipped, and before 2.8.6 `since` reported `has_more: false` in that state — which reads as
|
|
31
|
+
"caught up" on a database with every record unread.
|
|
32
|
+
|
|
13
33
|
## Install
|
|
14
34
|
|
|
15
35
|
```bash
|
|
@@ -10,7 +10,7 @@ build-backend = "hatchling.build"
|
|
|
10
10
|
|
|
11
11
|
[project]
|
|
12
12
|
name = "nedb-engine"
|
|
13
|
-
version = "2.8.
|
|
13
|
+
version = "2.8.6"
|
|
14
14
|
description = "NEDB — a versioned, self-compressing, time-traveling embedded database (replay-protected, idempotent, relational, searchable) with durable AOF persistence and a server daemon (nedbd)."
|
|
15
15
|
readme = "README.md"
|
|
16
16
|
requires-python = ">=3.8"
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -16,6 +16,83 @@ nedbd --dag ./data # DAG engine (v2, content-addressed, recommended)
|
|
|
16
16
|
|
|
17
17
|
---
|
|
18
18
|
|
|
19
|
+
## ⚠️ New in 2.8.6 — Durability & Recovery (read this if you store anything you care about)
|
|
20
|
+
|
|
21
|
+
Three defects found by killing a real engine at every persistence boundary and by filling a real
|
|
22
|
+
filesystem to zero free blocks. All three are fixed. **If you are on 2.8.5 or earlier, upgrade.**
|
|
23
|
+
|
|
24
|
+
### 1. A failed flush silently discarded acknowledged writes
|
|
25
|
+
|
|
26
|
+
`IdIndex::flush_write_buf` cleared every buffered entry regardless of whether its disk write
|
|
27
|
+
succeeded. So a flush that hit `ENOSPC` threw the entry away, and no later flush retried it.
|
|
28
|
+
|
|
29
|
+
Reproduced on a full 22 MiB filesystem: **30 rows acknowledged by `put() -> Ok`, then `list()`
|
|
30
|
+
returned 0 after reopen — while `verify()` reported all 30 objects healthy.** The content-addressed
|
|
31
|
+
objects were durable; the id-index entries that make them findable were gone.
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
before: try_flush_all() -> (no return value) reopen -> 0 rows, verify() = 30 ok
|
|
35
|
+
after: try_flush_all() -> Err("id-index leaf rows/buf_25: No space left on device (os error 28)")
|
|
36
|
+
...free space, retry -> Ok reopen -> 30 rows
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Fixed:** an entry leaves the WAL only when its write actually landed. Failures stay buffered and
|
|
40
|
+
retry on the next flush.
|
|
41
|
+
|
|
42
|
+
### 2. Flush errors were unobservable — new `try_flush_all()`
|
|
43
|
+
|
|
44
|
+
`flush_all()` returns `()` and logged fsync failures to stderr, so a caller could not tell a durable
|
|
45
|
+
flush from a failed one. Anything that takes a destructive or externally-visible action on the
|
|
46
|
+
strength of a persisted record needs to know.
|
|
47
|
+
|
|
48
|
+
```rust
|
|
49
|
+
// Use this when the outcome matters:
|
|
50
|
+
db.try_flush_all()?; // Result<()> — id-index WAL + segment sync + MANIFEST
|
|
51
|
+
|
|
52
|
+
// Still available, still logs, nowhere to propagate (ticker / Drop):
|
|
53
|
+
db.flush_all();
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Also new: `Db::try_flush_manifest()` and `IdIndex::try_flush_write_buf()`.
|
|
57
|
+
|
|
58
|
+
### 3. `repair` could not repair, and `since()` claimed "caught up" while behind
|
|
59
|
+
|
|
60
|
+
The cold scan rebuilt `seq_index`, per-collection tips, the Merkle head and `MANIFEST` — but **never
|
|
61
|
+
the id index**. A database whose WAL never reached disk came back with every object verifying and
|
|
62
|
+
`list()` empty, and `nedb-cli repair` printed success without fixing it, because
|
|
63
|
+
`start_cold_scan()` is a deliberate no-op on a warm store.
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
nedb-cli repair ./data
|
|
67
|
+
# repaired: 203 id-index entr(ies) rebuilt, 203 node(s) verified, flushed
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```rust
|
|
71
|
+
let restored = db.repair()?; // rebuild id index from objects; highest seq wins
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Every object carries its own `coll`, `id` and `seq`, so the id index is fully derivable — a lost WAL
|
|
75
|
+
is recoverable and nothing is invented. `repair()` also recomputes head and tips, so a repaired
|
|
76
|
+
database reopens **warm** instead of coming back up cold with an empty head.
|
|
77
|
+
|
|
78
|
+
Separately, `since()` set `has_more = hit_limit` alone. On a warm boot the seq index is empty **by
|
|
79
|
+
design** (that is why warm start is O(1)), so every lookup missed and `since()` returned zero nodes
|
|
80
|
+
with `has_more = false` — indistinguishable from genuinely up to date. A consumer following the
|
|
81
|
+
documented drain loop stopped one call in, with every record unread.
|
|
82
|
+
|
|
83
|
+
**Fixed:** `has_more` is true whenever the cursor is behind the log head. `ScanStatus` gains
|
|
84
|
+
**`seq_index_ready`** — replication consumers should gate on that, not on `scan_complete`, which is
|
|
85
|
+
true on a warm boot precisely because the scan was skipped.
|
|
86
|
+
|
|
87
|
+
### Known sharp edge (documented, not changed)
|
|
88
|
+
|
|
89
|
+
`since()`'s cursor is **exclusive** and seqs start at 0, so `since(0, _)` returns `(0, head]` and the
|
|
90
|
+
very first write in a database (seq 0) is unreachable through any cursor value. Ten writes drain as
|
|
91
|
+
nine records. Changing the convention would break existing consumers; a replica seeded from
|
|
92
|
+
`since()` alone starts one record short.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
19
96
|
## What is NEDB v2?
|
|
20
97
|
|
|
21
98
|
NEDB v2 replaces the append-only log (AOF) with a **content-addressed Merkle DAG**:
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|