@harperfast/rocksdb-js 2.7.1 → 2.9.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/README.md +624 -45
- package/dist/index.cjs +730 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +752 -41
- package/dist/index.d.mts +752 -41
- package/dist/index.mjs +726 -47
- package/dist/index.mjs.map +1 -1
- package/docs/stats.md +355 -269
- package/docs/transaction-log.md +18 -5
- package/docs/transaction-timestamp-integrity-design.md +123 -0
- package/package.json +25 -26
package/README.md
CHANGED
|
@@ -11,6 +11,8 @@ A Node.js binding for the RocksDB library.
|
|
|
11
11
|
- Custom stores provide ability to override default database interactions
|
|
12
12
|
- Efficient binary key and value encoding
|
|
13
13
|
- Configurable block/blob compression (LZ4, Zstd, Zlib, and more)
|
|
14
|
+
- Observable background errors via the `'error'` event or `db.getLastError()`, with in-process
|
|
15
|
+
recovery (`db.resume()`)
|
|
14
16
|
- Access to internal RocksDB statistics
|
|
15
17
|
- Designed for Node.js and Bun on Linux, macOS, and Windows
|
|
16
18
|
|
|
@@ -53,6 +55,16 @@ Creates a new database instance.
|
|
|
53
55
|
when linked, else no compression). See [Compression](#compression). Throws if the algorithm is
|
|
54
56
|
not compiled into the native build — check [`supportedCompression`](#supportedcompression) for
|
|
55
57
|
the available list.
|
|
58
|
+
- `compressionForAllColumnFamilies: boolean` When `true`, applies `compression` to every column
|
|
59
|
+
family opened for the database rather than only the column family specified by `name`. Requires an explicit
|
|
60
|
+
`compression`. Defaults to `false`. See [Compression](#compression).
|
|
61
|
+
- `dbWriteBufferSize: number` The total memtable memory budget in bytes shared across all of the
|
|
62
|
+
database's column families. When the combined size of all memtables reaches this value, RocksDB
|
|
63
|
+
flushes the largest one. `0` (the default) disables this global trigger, so per-column-family
|
|
64
|
+
`writeBufferSize` alone drives flushing. This is distinct from the process-wide
|
|
65
|
+
[`writeBufferManagerSize`](#dbconfigoptions) config option. Database-wide, so it binds when the
|
|
66
|
+
path is first opened in this process: a later open of the same path — including from another
|
|
67
|
+
worker thread — keeps the first opener's value rather than overriding or rejecting it.
|
|
56
68
|
- `disableWAL: boolean` Whether to disable the RocksDB write ahead log. Defaults to `false`.
|
|
57
69
|
- `enableStats: boolean` When `true` and the database is open, RocksDB will captures stats that
|
|
58
70
|
are retrieved by calling `db.getStats()`. Enabling statistics imposes 5-10% in overhead.
|
|
@@ -76,6 +88,17 @@ Creates a new database instance.
|
|
|
76
88
|
compaction falls behind under sustained ingest); a positive `int32` is an explicit cap. Reads
|
|
77
89
|
only pay a reopen cost when the number of live table files exceeds the budget, so raise the
|
|
78
90
|
process fd limit (and with it the derived budget) for very large databases.
|
|
91
|
+
- `maxWriteBufferNumber: number` The maximum number of memtables that can be queued per column
|
|
92
|
+
family before writes stall. Higher values absorb write bursts while flushes catch up, at the
|
|
93
|
+
cost of memory (roughly `maxWriteBufferNumber * writeBufferSize` per column family). Defaults to
|
|
94
|
+
`16`.
|
|
95
|
+
- `maxWriteBufferSizeToMaintain: number` The number of bytes of recent memtable history to keep in
|
|
96
|
+
memory for transaction conflict checking. `-1` (the default) derives the value from
|
|
97
|
+
`maxWriteBufferNumber * writeBufferSize` (the RocksDB-recommended default for optimistic
|
|
98
|
+
transactions) — except when a stalling [`writeBufferManager`](#dbconfigoptions) is configured
|
|
99
|
+
(`writeBufferManagerSize > 0` with `writeBufferManagerAllowStall`), in which case it resolves to
|
|
100
|
+
`0` to avoid retaining memtable history the manager will never release. An explicit non-negative
|
|
101
|
+
value is always honored as-is.
|
|
79
102
|
- `name: string` The column family name. Defaults to `"default"`.
|
|
80
103
|
- `noBlockCache: boolean` When `true`, disables the block cache. Block caching is enabled by
|
|
81
104
|
default and the cache is shared across all database instances.
|
|
@@ -85,7 +108,44 @@ Creates a new database instance.
|
|
|
85
108
|
until commit. Defaults to `false`.
|
|
86
109
|
- `readOnly: boolean` When `true`, the database is opened in read-only mode. Read operations are
|
|
87
110
|
permitted. Write operations will throw an error with code `ERR_DATABASE_READONLY`. Transactions
|
|
88
|
-
are a no-op in read-only mode.
|
|
111
|
+
are a no-op in read-only mode. [`flush()`](#dbflushoptions-promisevoid),
|
|
112
|
+
[`flushSync()`](#dbflushsyncoptions-void), [`compact()`](#dbcompactoptions-promisevoid) and
|
|
113
|
+
[`compactSync()`](#dbcompactsyncoptions-void) are the exception: they have nothing to do when
|
|
114
|
+
there are no writes, so they succeed as no-ops rather than throwing.
|
|
115
|
+
|
|
116
|
+
A read-only open is a **point-in-time snapshot**: it never sees later writes. It is safe
|
|
117
|
+
against a quiescent database, but **unsafe against a live writer**: the open holds no reference
|
|
118
|
+
on the files it is about to read, so a concurrent compaction, blob GC, or flush in the writing
|
|
119
|
+
process can delete one mid-open and the open throws with code `ERR_CONCURRENT_COMPACTION` — the
|
|
120
|
+
database is not corrupt; the reader lost a race, and retrying can succeed. Even a successful
|
|
121
|
+
open can fail _later reads_ the same way, because files the snapshot references are opened
|
|
122
|
+
lazily while the writer keeps deleting obsolete ones. To follow a database another process is
|
|
123
|
+
actively writing, open a secondary instead (`secondaryPath` below) — that is the supported mode
|
|
124
|
+
for a live follower.
|
|
125
|
+
|
|
126
|
+
- `secondaryPath: string` Opens the database as a **secondary instance**: a read-only follower of
|
|
127
|
+
a live primary that tolerates the primary deleting files and sees new writes on each
|
|
128
|
+
[`catchUpWithPrimary()`](#dbcatchupwithprimary-promisevoid) call. This is the supported way to
|
|
129
|
+
read a database another process is actively writing. The value is the secondary instance's own
|
|
130
|
+
workspace directory (created if missing) where RocksDB keeps the secondary's private state — it
|
|
131
|
+
must be outside `path` (enforced) and exclusive to one secondary instance. Exclusivity is
|
|
132
|
+
enforced: reusing a workspace in-process for a different database is rejected at open (the same
|
|
133
|
+
database + workspace share the one follower instance), and a kernel advisory lock on
|
|
134
|
+
`<secondaryPath>/.secondary.lock` (same discipline and caveats as the
|
|
135
|
+
[backup directory lock](#backups)) excludes other processes — with the same limits: on
|
|
136
|
+
filesystems without advisory locking (`flock` unsupported — e.g. the FUSE/9p mounts behind
|
|
137
|
+
Docker Desktop bind mounts) the lock degrades to a no-op, and on network filesystems with
|
|
138
|
+
node-local `flock` (NFS `local_lock`, CIFS, 9p) it does not exclude across hosts, so workspace
|
|
139
|
+
exclusivity is the caller's job there. Implies
|
|
140
|
+
`readOnly: true` (an explicit `readOnly: false` throws), so all read-only behavior above
|
|
141
|
+
applies. Forces `maxOpenFiles: -1`: every table and blob file is opened and held for the life
|
|
142
|
+
of each version, which is what makes the primary's deletions safe — budget file descriptors
|
|
143
|
+
accordingly on large databases (an explicit `maxOpenFiles` other than `-1` is rejected). Column
|
|
144
|
+
families created by the primary after the secondary opens are invisible until the secondary
|
|
145
|
+
reopens. Note: RocksDB upstream documents secondary instances as unsupported in combination
|
|
146
|
+
with integrated BlobDB (which this library enables for values ≥ 2KB); the pinned RocksDB build
|
|
147
|
+
handles blob files in secondary mode — covered by native regression tests — but upstream does
|
|
148
|
+
not guarantee the combination.
|
|
89
149
|
- `statsLevel: StatsLevel` Controls which type of statistics to skip and reduce statistic
|
|
90
150
|
overhead. Defaults to `StatsLevel.ExceptDetailedTimers`.
|
|
91
151
|
- `store: Store` A custom store that handles all interaction between the `RocksDatabase` or
|
|
@@ -96,10 +156,9 @@ Creates a new database instance.
|
|
|
96
156
|
modified time to be older than the retention period before it is rotated to the next sequence
|
|
97
157
|
number. Value must be between `0.0` and `1.0`. A threshold of `0.0` means ignore age check.
|
|
98
158
|
Defaults to `0.75`.
|
|
99
|
-
- `transactionLogMaxSize: number` The maximum size of a transaction log file.
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
rotated to the next sequence number. Defaults to 16 MB.
|
|
159
|
+
- `transactionLogMaxSize: number` The target maximum size of a transaction log file. Transactions
|
|
160
|
+
are never split across files: if the complete transaction does not fit, the log rotates before
|
|
161
|
+
writing it. A transaction written to an empty file may exceed the target. Defaults to 16 MB.
|
|
103
162
|
- `transactionLogRetention: string | number` The number of minutes to retain transaction logs
|
|
104
163
|
before purging. Defaults to `'3d'` (3 days).
|
|
105
164
|
- `transactionLogsPath: string` The path to store transaction logs. Defaults to
|
|
@@ -109,6 +168,9 @@ Creates a new database instance.
|
|
|
109
168
|
the verification slot for each written key. Enable this only for column families whose records
|
|
110
169
|
are cached (e.g. the primary column family of a table). Defaults to `false`. Requires
|
|
111
170
|
`verificationTableEntries` to be configured before the first database is opened.
|
|
171
|
+
- `writeBufferSize: number` The per-column-family memtable size in bytes at which the memtable is
|
|
172
|
+
sealed and flushed to an SST file. Smaller values produce more frequent, faster flushes; larger
|
|
173
|
+
values batch more writes per SST file at the cost of memory. Defaults to `16777216` (16 MB).
|
|
112
174
|
|
|
113
175
|
### `db.close()`
|
|
114
176
|
|
|
@@ -128,7 +190,7 @@ Returns the list of column families in the RocksDB database.
|
|
|
128
190
|
const db = RocksDatabase.open('path/to/db');
|
|
129
191
|
console.log(db.columns); // ['default']
|
|
130
192
|
|
|
131
|
-
|
|
193
|
+
db.use('users');
|
|
132
194
|
console.log(db.columns); // ['default', 'users']
|
|
133
195
|
```
|
|
134
196
|
|
|
@@ -146,18 +208,6 @@ const db = RocksDatabase.open('path/to/db', {
|
|
|
146
208
|
console.log(db.compression); // { algorithm: 'zstd', level: 3 }
|
|
147
209
|
```
|
|
148
210
|
|
|
149
|
-
### `db.logOptions: { maxLogFileSize: number, infoLogLevel: number }`
|
|
150
|
-
|
|
151
|
-
Returns the informational-log settings currently in effect for this database, read live from
|
|
152
|
-
RocksDB. These are database-wide settings (not per-column-family). `maxLogFileSize` is the
|
|
153
|
-
per-file size cap for informational log files (`LOG` / `LOG.old.*`); `infoLogLevel` is the logging
|
|
154
|
-
verbosity. The database must be open.
|
|
155
|
-
|
|
156
|
-
```typescript
|
|
157
|
-
const db = RocksDatabase.open('path/to/db', { maxLogFileSize: 4 * 1024 * 1024 });
|
|
158
|
-
console.log(db.logOptions); // { maxLogFileSize: 4194304, infoLogLevel: 1 }
|
|
159
|
-
```
|
|
160
|
-
|
|
161
211
|
### `db.config(options)`
|
|
162
212
|
|
|
163
213
|
Sets global database settings.
|
|
@@ -175,7 +225,11 @@ Sets global database settings.
|
|
|
175
225
|
- `writeBufferManagerAllowStall: boolean` When `true`, writes are stalled once the manager's
|
|
176
226
|
`buffer_size` is exceeded, providing a hard cap on memtable memory. When `false`, memtables are
|
|
177
227
|
allowed to grow past the limit and flushes are simply scheduled more aggressively. Off by
|
|
178
|
-
default to favor write throughput over hard memory bounding. Defaults to `false`.
|
|
228
|
+
default to favor write throughput over hard memory bounding. Defaults to `false`. A stall here
|
|
229
|
+
is invisible to RocksDB's own stall counters and to
|
|
230
|
+
[`db.isWriteStalled()`](#dbiswritestalled-boolean) — see
|
|
231
|
+
[`getWriteBufferManagerStats()`](#getwritebuffermanagerstats-writebuffermanagerstats),
|
|
232
|
+
which also describes the watchdog that logs a sustained stall.
|
|
179
233
|
- `writeBufferManagerCostToCache: boolean` When `true`, memtable memory is "charged" against the
|
|
180
234
|
shared block cache so the block cache and write buffers draw from a single pool. During write
|
|
181
235
|
bursts the cache shrinks to make room for memtables; once memtables flush, the cache can grow
|
|
@@ -195,6 +249,80 @@ RocksDatabase.config({
|
|
|
195
249
|
});
|
|
196
250
|
```
|
|
197
251
|
|
|
252
|
+
### `getWriteBufferManagerStats(): WriteBufferManagerStats`
|
|
253
|
+
|
|
254
|
+
Reads the live state of the `WriteBufferManager`.
|
|
255
|
+
|
|
256
|
+
**Process-wide, not per-database.** The manager is a singleton shared by every database opened in
|
|
257
|
+
this process, `worker_threads` included, so these values describe the whole process regardless of
|
|
258
|
+
where the call was made.
|
|
259
|
+
|
|
260
|
+
- `enabled: boolean` Whether a manager has been created. `bufferSize`, `memoryUsage`,
|
|
261
|
+
`mutableMemoryUsage`, `stallActive`, `stallActiveMs`, `watchdogRunning` and `columnFamilies` are
|
|
262
|
+
`0`/`false` when not; `allowStall` and `costToCache` still reflect the configured setting (a
|
|
263
|
+
manager is only created once `writeBufferManagerSize` is also set), and `inventoryAvailable` is
|
|
264
|
+
`true` (there is nothing to fail to collect).
|
|
265
|
+
- `bufferSize: number` The budget in bytes (`writeBufferManagerSize`, read live).
|
|
266
|
+
- `memoryUsage: number` Total memtable memory in bytes charged against the manager.
|
|
267
|
+
- `mutableMemoryUsage: number` The share of `memoryUsage` held by active (mutable) memtables; the
|
|
268
|
+
rest is memtables awaiting flush plus retained write history.
|
|
269
|
+
- `allowStall: boolean`, `costToCache: boolean` The manager's configuration.
|
|
270
|
+
- `stallActive: boolean` Whether the manager is currently stalling writes.
|
|
271
|
+
- `stallActiveMs: number` How long the current stall has been active; `0` when not stalled.
|
|
272
|
+
Sampled once a second by the watchdog, so `0` for a stall's first second and whenever
|
|
273
|
+
`watchdogRunning` is `false`.
|
|
274
|
+
- `watchdogRunning: boolean` Whether the stall watchdog thread is running.
|
|
275
|
+
- `columnFamilies: number` Live column families across every database attached to this manager. A
|
|
276
|
+
dropped column family keeps charging the manager until its last handle closes, so it is counted
|
|
277
|
+
until then.
|
|
278
|
+
- `inventoryAvailable: boolean` `false` when the inventory could not be collected because the
|
|
279
|
+
database registry or a column-family inventory was locked. The two inventory fields are then
|
|
280
|
+
empty and everything else is still live; the call never blocks on database work that may itself
|
|
281
|
+
be stalled.
|
|
282
|
+
- `maxWriteBufferSizeToMaintain: Record<string, number>` Effective per-column-family retained-history
|
|
283
|
+
target (as a decimal string) to how many of those column families carry it. Effective, not
|
|
284
|
+
requested: RocksDB rewrites a requested `0` for a transaction database.
|
|
285
|
+
|
|
286
|
+
`bufferSize`, `memoryUsage`, `mutableMemoryUsage`, `stallActive` and `stallActiveMs` are also in
|
|
287
|
+
[`db.getStats()`](#dbgetstatsall-boolean-rocksdbstats) and `db.getStat()` under the same
|
|
288
|
+
`writeBufferManager.` prefix, for scraping; `enabled`, `allowStall`, `costToCache`,
|
|
289
|
+
`watchdogRunning`, and the column-family inventory are only here, because collecting them either
|
|
290
|
+
walks the database registry or isn't scrape-shaped.
|
|
291
|
+
|
|
292
|
+
`stallActive` is the signal that distinguishes a stalled process from an idle one.
|
|
293
|
+
[`db.isWriteStalled()`](#dbiswritestalled-boolean), the [`'writeStall'` event](#event-writestall)
|
|
294
|
+
and `rocksdb.stall.micros` all track RocksDB's `WriteController`, which a `WriteBufferManager`
|
|
295
|
+
stall never goes through.
|
|
296
|
+
|
|
297
|
+
#### The stall watchdog
|
|
298
|
+
|
|
299
|
+
While a manager exists with `writeBufferManagerAllowStall`, one low-frequency thread samples the
|
|
300
|
+
manager once a second. When a stall has been continuously active past
|
|
301
|
+
`ROCKSDB_JS_WBM_STALL_WARN_MS` (default `5000`; `0` disables the watchdog) it writes **one** line
|
|
302
|
+
to `stderr` and emits it as a process-wide `'log.warn'` event — once per stall episode, not per
|
|
303
|
+
blocked writer and not per sample:
|
|
304
|
+
|
|
305
|
+
```
|
|
306
|
+
[rocksdb-js] WriteBufferManager write stall active for 5.0s - no write can complete until memtable
|
|
307
|
+
memory drops below the budget. budget=661.2MB usage=662.2MB (100.2%) mutable=12.4MB (1.9%)
|
|
308
|
+
allowStall=true costToCache=true columnFamilies=28 maxWriteBufferSizeToMaintain={268435456:28}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
It needs its own thread because every other candidate is blocked by the very condition it reports:
|
|
312
|
+
a writer is parked inside RocksDB, RocksDB's stall callbacks are `WriteController`-only, and a JS
|
|
313
|
+
timer cannot fire on a thread parked in a synchronous write. The line goes to `stderr` as well as
|
|
314
|
+
the event so it is not lost when nothing has registered a `'log.warn'` listener; route one and
|
|
315
|
+
ignore `stderr` if you would rather have it in your own log.
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
RocksDatabase.on('log.warn', (message) => logger.warn(message));
|
|
319
|
+
|
|
320
|
+
const wbm = getWriteBufferManagerStats();
|
|
321
|
+
if (wbm.stallActive) {
|
|
322
|
+
logger.warn(`writes stalled for ${wbm.stallActiveMs}ms: ${wbm.memoryUsage}/${wbm.bufferSize}`);
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
198
326
|
### `db.isOpen(): boolean`
|
|
199
327
|
|
|
200
328
|
Returns `true` if the database is open, otherwise false.
|
|
@@ -203,6 +331,18 @@ Returns `true` if the database is open, otherwise false.
|
|
|
203
331
|
console.log(db.isOpen()); // true or false
|
|
204
332
|
```
|
|
205
333
|
|
|
334
|
+
### `db.logOptions: { maxLogFileSize: number, infoLogLevel: number }`
|
|
335
|
+
|
|
336
|
+
Returns the informational-log settings currently in effect for this database, read live from
|
|
337
|
+
RocksDB. These are database-wide settings (not per-column-family). `maxLogFileSize` is the
|
|
338
|
+
per-file size cap for informational log files (`LOG` / `LOG.old.*`); `infoLogLevel` is the logging
|
|
339
|
+
verbosity. The database must be open.
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
const db = RocksDatabase.open('path/to/db', { maxLogFileSize: 4 * 1024 * 1024 });
|
|
343
|
+
console.log(db.logOptions); // { maxLogFileSize: 4194304, infoLogLevel: 1 }
|
|
344
|
+
```
|
|
345
|
+
|
|
206
346
|
### `db.name: string`
|
|
207
347
|
|
|
208
348
|
Returns the database column family's name.
|
|
@@ -232,6 +372,20 @@ There's also a static `open()` method for convenience that performs the same thi
|
|
|
232
372
|
const db = RocksDatabase.open('path/to/db');
|
|
233
373
|
```
|
|
234
374
|
|
|
375
|
+
### `db.secondaryPath: string | undefined`
|
|
376
|
+
|
|
377
|
+
The secondary instance's workspace directory when the database was opened as a
|
|
378
|
+
[secondary](#new-rocksdatabasepath-options) (a read-only follower of a live primary), or
|
|
379
|
+
`undefined` for a regular or plain read-only open.
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
const follower = RocksDatabase.open('/path/to/database', {
|
|
383
|
+
secondaryPath: '/path/to/follower-workspace',
|
|
384
|
+
});
|
|
385
|
+
console.log(follower.secondaryPath); // '/path/to/follower-workspace'
|
|
386
|
+
console.log(follower.readOnly); // true
|
|
387
|
+
```
|
|
388
|
+
|
|
235
389
|
### `db.status: 'opened' | 'closed'`
|
|
236
390
|
|
|
237
391
|
Returns a string `'opened'` or `'closed'` indicating if the database is opened or closed.
|
|
@@ -240,8 +394,95 @@ Returns a string `'opened'` or `'closed'` indicating if the database is opened o
|
|
|
240
394
|
console.log(db.status);
|
|
241
395
|
```
|
|
242
396
|
|
|
397
|
+
### `db.use(name, options?): RocksDatabase`
|
|
398
|
+
|
|
399
|
+
Returns a `RocksDatabase` bound to the `name` column family of this same database, opening — and
|
|
400
|
+
creating, if it does not exist — the column family on first use. This is a factory (like
|
|
401
|
+
[`useLog`](#dbuselogname-transactionlog)), not a stateful switch: the returned view is an
|
|
402
|
+
independent instance whose own reads and writes target its column family, while sharing the same
|
|
403
|
+
underlying database. Because a single RocksDB database backs every column family, a transaction,
|
|
404
|
+
backup, or checkpoint still spans all of them.
|
|
405
|
+
|
|
406
|
+
- `name: string` The column family name.
|
|
407
|
+
- `options?: object` Options for the column family (same shape as the constructor's, minus `name`),
|
|
408
|
+
overriding the options inherited from this database. Options only take effect when the view is
|
|
409
|
+
(re)opened.
|
|
410
|
+
|
|
411
|
+
`use()` is get-or-create, backed by a weak cache: `db.use('events') === db.use('events')` while the
|
|
412
|
+
view is still referenced **and open**, and calling with this database's own column-family name
|
|
413
|
+
returns `this`. A view that has been closed (or garbage-collected — the cache does not pin it) is
|
|
414
|
+
transparently recreated on the next `use()`. Views are independent handles: closing this database
|
|
415
|
+
does not close them (and vice versa); the underlying database stays open until every handle is closed
|
|
416
|
+
or collected.
|
|
417
|
+
|
|
418
|
+
The view's store is derived by `Store#createColumnFamilyStore(name, options)`, which builds an
|
|
419
|
+
independent store of the same class with **its own codec state** — views never share a mutable
|
|
420
|
+
encoder/decoder. Consequently a database configured with a _pre-constructed_ encoder/decoder instance
|
|
421
|
+
cannot derive views (its `name`/`structures` would be shared and corrupt both column families); use an
|
|
422
|
+
encoder factory (`{ Encoder }`) or a named `encoding` instead. A custom `Store` whose constructor
|
|
423
|
+
can't be recreated from `(path, options)` (e.g. it takes injected dependencies) should override
|
|
424
|
+
`createColumnFamilyStore` to build its views.
|
|
425
|
+
|
|
426
|
+
```typescript
|
|
427
|
+
const db = RocksDatabase.open('path/to/db');
|
|
428
|
+
|
|
429
|
+
const events = db.use('events');
|
|
430
|
+
await events.put('e1', payload);
|
|
431
|
+
|
|
432
|
+
await db.put('k', 'v'); // default column family, unaffected
|
|
433
|
+
console.log(events.get('e1')); // payload
|
|
434
|
+
console.log(db.get('e1')); // undefined — different column family
|
|
435
|
+
|
|
436
|
+
events.close();
|
|
437
|
+
db.close();
|
|
438
|
+
```
|
|
439
|
+
|
|
243
440
|
## Data Operations
|
|
244
441
|
|
|
442
|
+
### `db.catchUpWithPrimary(): Promise<void>`
|
|
443
|
+
|
|
444
|
+
Advances a [secondary instance](#new-rocksdatabasepath-options) to the primary's current state by
|
|
445
|
+
tailing and replaying the primary's MANIFEST and WAL. A secondary does not see the primary's
|
|
446
|
+
writes — flushed or not — until it catches up. Reads through the handle remain safe while the
|
|
447
|
+
catch-up runs; catch-ups on the same database are serialized internally.
|
|
448
|
+
|
|
449
|
+
Throws with code `ERR_NOT_SECONDARY` on a database that was not opened with `secondaryPath`.
|
|
450
|
+
Column families the primary created after the secondary opened stay invisible until the secondary
|
|
451
|
+
reopens; ones the primary dropped remain readable until then. Catch-up advances the **database**
|
|
452
|
+
view only: transaction-log reads through a secondary serve the stores discovered at open, and a
|
|
453
|
+
store the primary creates afterward becomes visible only on reopen. Whether entries appended to an
|
|
454
|
+
already-open store are visible depends on where the writer is: a cross-process primary's appends
|
|
455
|
+
are not (the reader's view of the file extent is fixed at open), while a writer in the _same_
|
|
456
|
+
process shares the store object, so its appends are. Either way a log entry can describe data the
|
|
457
|
+
database view does not have yet — the log write completes before the RocksDB commit for every
|
|
458
|
+
writer, so log-leads-database is the normal direction and a consumer has to tolerate it. Serialize
|
|
459
|
+
catch-up calls per database. Each async call holds a libuv worker for the whole replay, and
|
|
460
|
+
concurrent calls queue on an internal per-database mutex while holding theirs, so a handful of
|
|
461
|
+
overlapping catch-ups on a backlogged follower can exhaust the default four-thread pool and stall
|
|
462
|
+
unrelated `fs`/`dns`/`crypto` work in the process. Await one before starting the next rather than
|
|
463
|
+
firing one per timer tick.
|
|
464
|
+
|
|
465
|
+
```typescript
|
|
466
|
+
const primary = RocksDatabase.open('/path/to/database');
|
|
467
|
+
const follower = RocksDatabase.open('/path/to/database', {
|
|
468
|
+
secondaryPath: '/path/to/follower-workspace',
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
primary.putSync('foo', 'bar');
|
|
472
|
+
console.log(follower.getSync('foo')); // undefined — not caught up yet
|
|
473
|
+
await follower.catchUpWithPrimary();
|
|
474
|
+
console.log(follower.getSync('foo')); // 'bar'
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### `db.catchUpWithPrimarySync(): void`
|
|
478
|
+
|
|
479
|
+
Synchronous version of `catchUpWithPrimary()`. The replay runs on the JS thread, so prefer the
|
|
480
|
+
async form unless the caller is already blocking.
|
|
481
|
+
|
|
482
|
+
```typescript
|
|
483
|
+
follower.catchUpWithPrimarySync();
|
|
484
|
+
```
|
|
485
|
+
|
|
245
486
|
### `db.clear(options?): Promise<number>`
|
|
246
487
|
|
|
247
488
|
Asychronously removes all data in the current database.
|
|
@@ -302,9 +543,13 @@ await db.compact({ start: 'a', end: 'z' });
|
|
|
302
543
|
await db.compact({ bottommost: true });
|
|
303
544
|
```
|
|
304
545
|
|
|
546
|
+
On a [read-only](#new-rocksdatabasepath-options) database this is a no-op: arguments are still
|
|
547
|
+
validated, but the returned promise resolves without compacting rather than rejecting.
|
|
548
|
+
|
|
305
549
|
### `db.compactSync(options?): void`
|
|
306
550
|
|
|
307
|
-
Synchronous version of `compact()`.
|
|
551
|
+
Synchronous version of `compact()`. On a [read-only](#new-rocksdatabasepath-options) database it
|
|
552
|
+
validates its arguments and then returns without compacting, rather than throwing.
|
|
308
553
|
|
|
309
554
|
```typescript
|
|
310
555
|
db.compactSync();
|
|
@@ -345,23 +590,63 @@ db.dropSync();
|
|
|
345
590
|
db.close();
|
|
346
591
|
```
|
|
347
592
|
|
|
348
|
-
### `db.flush(): Promise<void>`
|
|
593
|
+
### `db.flush(options?): Promise<void>`
|
|
349
594
|
|
|
350
595
|
Flushes all in-memory data to disk asynchronously.
|
|
351
596
|
|
|
597
|
+
- `options: object`
|
|
598
|
+
- `allowWriteStall?: boolean` Whether the flush may proceed even though it will stall writes for
|
|
599
|
+
its duration. Defaults to `false`, which — despite how that reads — means the flush **waits**
|
|
600
|
+
until it can run without causing a stall, with no timeout: on a database stuck in a stall
|
|
601
|
+
condition (immutable-memtable backlog, L0 stop trigger, an exhausted `WriteBufferManager`
|
|
602
|
+
budget), the returned promise never settles, and since `flush()` runs on the libuv threadpool
|
|
603
|
+
(default size 4), a handful of concurrently stalled flushes can exhaust the pool and stall
|
|
604
|
+
every unrelated `fs`/`dns`/`crypto` call and cold-cache `get()` in the process, not only this
|
|
605
|
+
database's. Pass `true` up front, before issuing a flush you expect might stall, when it's a
|
|
606
|
+
durability gate you'd rather have stall writers than wait indefinitely — there's no way to
|
|
607
|
+
cancel an in-flight `flush()`, so this isn't a rescue for one already hung, and forcing the
|
|
608
|
+
memtable switch can itself prolong an existing L0 stop-trigger condition rather than clear it.
|
|
609
|
+
That cost is database-wide, covering every column family on the (process-global,
|
|
610
|
+
`worker_threads`-shared) database handle, not just the caller's — and it relocates the hang
|
|
611
|
+
rather than removing it: a stalled write blocks the database's single commit thread, which
|
|
612
|
+
dispatches every `Transaction.commit()` in order, so every commit behind it queues up too,
|
|
613
|
+
including ones from callers that never touched flush. This is a different knob from
|
|
614
|
+
`writeBufferManagerAllowStall` (see [`new RocksDatabase()`](#new-rocksdatabasepath-options)
|
|
615
|
+
options), with nearly opposite polarity: that one governs whether the `WriteBufferManager` may
|
|
616
|
+
stall writers at all, this one governs whether one manual flush is willing to cause a stall
|
|
617
|
+
rather than wait one out.
|
|
618
|
+
|
|
352
619
|
```typescript
|
|
353
620
|
await db.flush();
|
|
621
|
+
|
|
622
|
+
// Chosen up front, as a durability gate willing to pay the stall cost
|
|
623
|
+
await db.flush({ allowWriteStall: true });
|
|
354
624
|
```
|
|
355
625
|
|
|
356
|
-
|
|
626
|
+
On a [read-only](#new-rocksdatabasepath-options) database this is a no-op: the options bag is still
|
|
627
|
+
validated, but the returned promise resolves without flushing rather than rejecting.
|
|
628
|
+
|
|
629
|
+
### `db.flushSync(options?): void`
|
|
357
630
|
|
|
358
631
|
Flushes all in-memory data to disk synchronously. Note that this can be an expensive operation, so
|
|
359
632
|
it is recommended to use `flush()` if you want to keep the event loop free.
|
|
360
633
|
|
|
634
|
+
- `options: object` Same as [`flush()`](#dbflushoptions-promisevoid). The default
|
|
635
|
+
`allowWriteStall: false` wait is taken on the calling thread, which here is the JS thread — so
|
|
636
|
+
the hazard described there is strictly larger on this entry point, not smaller: instead of
|
|
637
|
+
parking one libuv pool worker it freezes the event loop outright, and it holds the in-flight
|
|
638
|
+
operation claim that `close()` waits on, so the database cannot be closed out from under it
|
|
639
|
+
either. Prefer `flush()` if there is any chance the database is in a stall condition.
|
|
640
|
+
|
|
361
641
|
```typescript
|
|
362
642
|
db.flushSync();
|
|
643
|
+
|
|
644
|
+
db.flushSync({ allowWriteStall: true });
|
|
363
645
|
```
|
|
364
646
|
|
|
647
|
+
On a [read-only](#new-rocksdatabasepath-options) database it validates its options and then returns
|
|
648
|
+
without flushing, rather than throwing.
|
|
649
|
+
|
|
365
650
|
### `db.get(key: Key, options?: GetOptions): MaybePromise<any>`
|
|
366
651
|
|
|
367
652
|
Retreives the value for a given key. If the key does not exist, it will resolve `undefined`.
|
|
@@ -408,13 +693,75 @@ the `expectedVersion` option is used.
|
|
|
408
693
|
### `db.getEstimatedKeyCount(): number`
|
|
409
694
|
|
|
410
695
|
Retrieves the estimated number of keys in the database. This is an alias for
|
|
411
|
-
`db.getDBIntProperty('rocksdb.estimate-num-keys')
|
|
696
|
+
`db.getDBIntProperty('rocksdb.estimate-num-keys')`; use `estimateCount()` for range support and a
|
|
697
|
+
confidence indicator.
|
|
412
698
|
|
|
413
699
|
```typescript
|
|
414
700
|
const estimated = db.getEstimatedKeyCount();
|
|
415
701
|
console.log(estimated);
|
|
416
702
|
```
|
|
417
703
|
|
|
704
|
+
### `db.estimateCount(options?: CountEstimateOptions): CountEstimate`
|
|
705
|
+
|
|
706
|
+
Estimates the number of keys in the database, or within a key range, returning
|
|
707
|
+
`{ count, confidence }`. Unlike `getKeysCount()`, this never iterates: the estimate is derived
|
|
708
|
+
from RocksDB statistics (memtable stats plus approximate SST sizes converted through the entry
|
|
709
|
+
density of the SSTs overlapping the range), so its cost scales with the number of SSTs overlapping
|
|
710
|
+
the range rather than the number of keys. Reading cold table properties can do I/O through the
|
|
711
|
+
table cache, so narrow ranges are preferable. A start-only range is computed as the
|
|
712
|
+
whole-database estimate minus the complement, so it does the work of the range _below_ `start`.
|
|
713
|
+
Accuracy improves with range size. Resolution is bounded by SST data-block granularity, so a range
|
|
714
|
+
narrower than a block is unreliable in either direction: it may over-report or report 0 for present
|
|
715
|
+
keys, and its low `confidence` is the signal. Recently deleted or overwritten entries may be counted
|
|
716
|
+
until compaction.
|
|
717
|
+
Estimates always reflect committed state; writes pending in a transaction are not included. Set
|
|
718
|
+
`reverse: true` to use `getRange()`'s reverse convention (`start` is the upper bound and `end` is
|
|
719
|
+
the lower bound). An inverted range (`start` ≥ `end`) returns
|
|
720
|
+
`{ count: 0, confidence: 1 }`.
|
|
721
|
+
|
|
722
|
+
`confidence` is a heuristic 0–1 indicator of how trustworthy `count` is — exactly 1 only when the
|
|
723
|
+
count is exact. It is derived from the estimate's resolution (data-block/memtable-sampling
|
|
724
|
+
granularity relative to the count), the tombstone fraction of the overlapping SSTs, and — for
|
|
725
|
+
start-only ranges — the error compounded by complement subtraction. Treat it as an ordering
|
|
726
|
+
signal (e.g. when to trust an estimate for query planning vs fall back to a heuristic), not a
|
|
727
|
+
statistical bound.
|
|
728
|
+
|
|
729
|
+
```typescript
|
|
730
|
+
const { count, confidence } = db.estimateCount({ start: 'a', end: 'z' });
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
### `db.createCountEstimator(options?: CountEstimatorOptions): CountEstimator`
|
|
734
|
+
|
|
735
|
+
Creates an estimator that progressively refines a range count estimate while the range is being
|
|
736
|
+
iterated — useful for reporting a total alongside a page of results without scanning the full
|
|
737
|
+
range. Before any traversal, `estimate()` returns the pure statistical estimate (same as
|
|
738
|
+
`estimateCount(range)`). As the caller reports progress with `advance(lastKey, count)` (e.g. once
|
|
739
|
+
per page), `estimate()` returns the exact traversed count plus a statistical estimate of the
|
|
740
|
+
remainder, calibrated by the observed ratio of actual-to-estimated entries over the portion
|
|
741
|
+
already traversed — so the count converges toward the exact total. `confidence` is the
|
|
742
|
+
exactness-weighted blend of the traversed portion and the remainder's confidence, so it approaches 1
|
|
743
|
+
as the exact portion grows, although a checkpoint may decrease when calibration makes a large
|
|
744
|
+
correction.
|
|
745
|
+
Each checkpoint reads committed state, so a traversal performed against a transaction snapshot may
|
|
746
|
+
be calibrated against data committed after that snapshot.
|
|
747
|
+
When traversal completes, call `finish()` and `estimate()` returns the exact count with
|
|
748
|
+
confidence 1. Reverse ranges follow `getRange`: set `start` to the upper bound and `end` to the
|
|
749
|
+
lower bound, then set `reverse: true`. The caller owns the progress contract: cursors must move
|
|
750
|
+
monotonically through the range and each entry must be reported exactly once.
|
|
751
|
+
|
|
752
|
+
```typescript
|
|
753
|
+
const range = { start: 'a', end: 'z' };
|
|
754
|
+
const estimator = db.createCountEstimator(range);
|
|
755
|
+
let lastKey;
|
|
756
|
+
let pageSize = 0;
|
|
757
|
+
for (const { key } of db.getRange({ ...range, limit: 25 })) {
|
|
758
|
+
lastKey = key;
|
|
759
|
+
pageSize++;
|
|
760
|
+
}
|
|
761
|
+
estimator.advance(lastKey, pageSize);
|
|
762
|
+
const { count, confidence } = estimator.estimate();
|
|
763
|
+
```
|
|
764
|
+
|
|
418
765
|
### `db.getKeys(options?: IteratorOptions): ExtendedIterable`
|
|
419
766
|
|
|
420
767
|
Retrieves all keys within a range.
|
|
@@ -437,7 +784,7 @@ const range = db.getKeysCount({ start: 'a', end: 'z' }); // exact number of keys
|
|
|
437
784
|
### `db.getMonotonicTimestamp(): number`
|
|
438
785
|
|
|
439
786
|
Returns the current timestamp as a monotonically increasing timestamp in milliseconds represented as
|
|
440
|
-
a decimal number.
|
|
787
|
+
a decimal number. This process-wide clock also supplies each transaction's initial timestamp.
|
|
441
788
|
|
|
442
789
|
```typescript
|
|
443
790
|
const ts = db.getMonotonicTimestamp();
|
|
@@ -497,6 +844,25 @@ const blobFiles = db.getDBIntProperty('rocksdb.num-blob-files');
|
|
|
497
844
|
const numKeys = db.getDBIntProperty('rocksdb.estimate-num-keys');
|
|
498
845
|
```
|
|
499
846
|
|
|
847
|
+
### `db.isWriteStalled(): boolean`
|
|
848
|
+
|
|
849
|
+
Whether RocksDB is currently applying write backpressure to this database —
|
|
850
|
+
delaying (rate-limiting) or fully stopping writes. Reads the live
|
|
851
|
+
`rocksdb.is-write-stopped` and `rocksdb.actual-delayed-write-rate` properties.
|
|
852
|
+
|
|
853
|
+
This is the authoritative, live pull counterpart to the
|
|
854
|
+
[`'writeStall'` event](#event-writestall): the event pushes per-column-family
|
|
855
|
+
entries into a stall (rate-limited), while this reports the current state on
|
|
856
|
+
demand and can never be stale. It is **database-wide** — the write controller is
|
|
857
|
+
shared across every column family, so it answers "are writes stalled anywhere"
|
|
858
|
+
rather than for one column family.
|
|
859
|
+
|
|
860
|
+
```typescript
|
|
861
|
+
if (db.isWriteStalled()) {
|
|
862
|
+
console.warn('writes are currently throttled or blocked');
|
|
863
|
+
}
|
|
864
|
+
```
|
|
865
|
+
|
|
500
866
|
### `db.getRange(options?: IteratorOptions): ExtendedIterable`
|
|
501
867
|
|
|
502
868
|
Retrieves a range of keys and their values. Supports both synchronous and asynchronous iteration.
|
|
@@ -518,6 +884,23 @@ for (const { key, value } of db.getRange({ start: 'a', end: 'z' })) {
|
|
|
518
884
|
}
|
|
519
885
|
```
|
|
520
886
|
|
|
887
|
+
Pass `transaction: txn` to iterate this column family through that transaction, exactly as
|
|
888
|
+
`get()` does with the same option: the iterator sees the transaction's staged writes and reads on its
|
|
889
|
+
snapshot, and range bounds apply to staged keys too. `getKeys()` and `getKeysCount()` accept it as
|
|
890
|
+
well. On a transaction (`txn.getRange()`) the transaction itself is the context and takes precedence
|
|
891
|
+
over a `transaction` option, as it does for `txn.get()`. Such an iterator is closed when the
|
|
892
|
+
transaction commits or aborts: a later `next()` throws, while `return()` stays a no-op. Opening a
|
|
893
|
+
range or counting through a transaction that has already started committing throws as well.
|
|
894
|
+
|
|
895
|
+
```typescript
|
|
896
|
+
await db.transaction(async (txn) => {
|
|
897
|
+
await txn.put('c', 'staged');
|
|
898
|
+
for (const { key, value } of db.getRange({ start: 'a', end: 'z', transaction: txn })) {
|
|
899
|
+
console.log({ key, value }); // includes { key: "c", value: "staged" }
|
|
900
|
+
}
|
|
901
|
+
});
|
|
902
|
+
```
|
|
903
|
+
|
|
521
904
|
### `db.getUserSharedBuffer(key: Key, defaultBuffer: ArrayBuffer, options?)`
|
|
522
905
|
|
|
523
906
|
Creates a new buffer with the contents of `defaultBuffer` that can be accessed across threads. This
|
|
@@ -692,6 +1075,15 @@ not committed after the configured number of coordinated retries, the transactio
|
|
|
692
1075
|
an `ERR_TRANSACTION_ABANDONED` error. Coordinated retry requires the column family to be opened with
|
|
693
1076
|
`verificationTable: true`.
|
|
694
1077
|
|
|
1078
|
+
That wait is bounded so a conflicting transaction that is never committed or aborted cannot block
|
|
1079
|
+
the commit forever: if the write intent has not been released after `ROCKSDB_JS_PARK_TIMEOUT_MS`
|
|
1080
|
+
(default `5000`), the commit resolves anyway and consumes a retry attempt exactly as a real release
|
|
1081
|
+
would. A conflicting transaction held for longer than roughly `maxRetries` times that timeout
|
|
1082
|
+
therefore ends in `ERR_TRANSACTION_ABANDONED` rather than waiting indefinitely. Deployments where
|
|
1083
|
+
waiting is preferable to failing should raise the timeout; there is no way to disable the bound.
|
|
1084
|
+
In particular `0` does not disable it: `0`, negative, and unparseable values all fall back to the
|
|
1085
|
+
`5000` default, and a value between `1` and `49` is clamped up to `50`.
|
|
1086
|
+
|
|
695
1087
|
### Class: `Transaction`
|
|
696
1088
|
|
|
697
1089
|
The transaction callback is passed in a `Transaction` instance which contains all of the same data
|
|
@@ -705,13 +1097,14 @@ operations methods as the `RocksDatabase` instance plus:
|
|
|
705
1097
|
no further transaction operations are permitted. Calling this method multiple times has no effect.
|
|
706
1098
|
- `txn.commit(): Promise<void>` Asynchronously commits the transaction and closes the transaction.
|
|
707
1099
|
- `txn.commitSync()` Synchronously commits and closes the transaction.
|
|
708
|
-
- `txn.getTimestamp(): number` Retrieves the transaction
|
|
709
|
-
defaults to
|
|
1100
|
+
- `txn.getTimestamp(): number` Retrieves the transaction timestamp in milliseconds as a decimal. It
|
|
1101
|
+
defaults to a process-wide monotonic value assigned when the transaction was created.
|
|
710
1102
|
- `txn.id: number` The read-only transaction ID. Transaction IDs are unique to the RocksDB database
|
|
711
1103
|
path, regardless the database name/column family.
|
|
712
|
-
- `txn.setTimestamp(ts?: number): void` Overrides the transaction
|
|
713
|
-
a timestamp, it
|
|
714
|
-
|
|
1104
|
+
- `txn.setTimestamp(ts?: number): void` Overrides the transaction timestamp in milliseconds. If
|
|
1105
|
+
called without a timestamp, it claims a fresh monotonic value. It must be called before staging
|
|
1106
|
+
any write or transaction-log entry, and a supplied value must be finite, positive, and below
|
|
1107
|
+
`8.64e15`.
|
|
715
1108
|
|
|
716
1109
|
#### `txn.abandonWrites(): void`
|
|
717
1110
|
|
|
@@ -747,8 +1140,9 @@ Once called, no further transaction operations are permitted.
|
|
|
747
1140
|
|
|
748
1141
|
#### `txn.getTimestamp(): number`
|
|
749
1142
|
|
|
750
|
-
Retrieves the transaction
|
|
751
|
-
the transaction was created.
|
|
1143
|
+
Retrieves the transaction timestamp in milliseconds since the Unix epoch as a decimal. It defaults
|
|
1144
|
+
to a process-wide monotonic value assigned when the transaction was created. The transaction log
|
|
1145
|
+
uses this timestamp as the batch key; producers may also encode it into their own record format.
|
|
752
1146
|
|
|
753
1147
|
#### `txn.id`
|
|
754
1148
|
|
|
@@ -757,14 +1151,91 @@ Type: `number`
|
|
|
757
1151
|
The transaction ID represented as a 32-bit unsigned integer. Transaction IDs are unique to the
|
|
758
1152
|
RocksDB database path, regardless the database name/column family.
|
|
759
1153
|
|
|
760
|
-
#### `txn.setTimestamp(ts
|
|
1154
|
+
#### `txn.setTimestamp(ts?: number): void`
|
|
1155
|
+
|
|
1156
|
+
Overrides the transaction timestamp in milliseconds since the Unix epoch. Replication receivers and
|
|
1157
|
+
crash replay can use this to adopt an origin transaction's log key. If called without a timestamp,
|
|
1158
|
+
it claims a fresh monotonic value.
|
|
761
1159
|
|
|
762
|
-
|
|
763
|
-
|
|
1160
|
+
The override must happen while the transaction is pending and before any database write or
|
|
1161
|
+
transaction-log entry is staged. A log batch that has already been written keeps its timestamp
|
|
1162
|
+
across a coordinated retry; reapplying that same timestamp remains idempotent while pending. A
|
|
1163
|
+
supplied value must be finite, positive, and below `8.64e15`.
|
|
1164
|
+
|
|
1165
|
+
rocksdb-js does not define a record's value layout or version metadata. A producer that copies the
|
|
1166
|
+
transaction timestamp into record bytes is responsible for calling `setTimestamp()` before reading
|
|
1167
|
+
and encoding it.
|
|
764
1168
|
|
|
765
1169
|
```typescript
|
|
766
1170
|
await db.transaction(async (txn) => {
|
|
767
|
-
txn.setTimestamp(Date.now()
|
|
1171
|
+
txn.setTimestamp(Date.now());
|
|
1172
|
+
});
|
|
1173
|
+
```
|
|
1174
|
+
|
|
1175
|
+
## Error Handling
|
|
1176
|
+
|
|
1177
|
+
### `db.getLastError(): BackgroundError | null`
|
|
1178
|
+
|
|
1179
|
+
Returns the most recent [`BackgroundError`](#event-error) observed on this database, or `null` when
|
|
1180
|
+
none has occurred. This is the **pull** counterpart to the `'error'` [event](#event-error): use it
|
|
1181
|
+
for an on-demand check (e.g. a health probe) or to catch an error that fired before a listener was
|
|
1182
|
+
attached. The value is historical — it is **not** cleared by a successful
|
|
1183
|
+
[`db.resume()`](#dbresume-void); reset it explicitly with
|
|
1184
|
+
[`db.setLastError(null)`](#dbsetlasterrorerror-void). When the returned error's `writesDisabled` is
|
|
1185
|
+
`true`, writes are stopped until recovery.
|
|
1186
|
+
|
|
1187
|
+
```typescript
|
|
1188
|
+
const err = db.getLastError();
|
|
1189
|
+
if (err?.writesDisabled) {
|
|
1190
|
+
// ...free disk space, then...
|
|
1191
|
+
db.resume();
|
|
1192
|
+
}
|
|
1193
|
+
```
|
|
1194
|
+
|
|
1195
|
+
### `db.setLastError(error?): void`
|
|
1196
|
+
|
|
1197
|
+
Sets or clears the last background error, mirroring the Win32 `SetLastError` / `GetLastError` pair.
|
|
1198
|
+
|
|
1199
|
+
- **Clear** — pass `null` (or no argument). [`db.getLastError()`](#dbgetlasterror-backgrounderror--null)
|
|
1200
|
+
then returns `null` and no event fires. This is how you reset the error after handling or
|
|
1201
|
+
recovering it (e.g. after [`db.resume()`](#dbresume-void)) — the equivalent of
|
|
1202
|
+
`SetLastError(ERROR_SUCCESS)`.
|
|
1203
|
+
- **Set** — pass an object (`{ message, severity?, severityName?, writesDisabled?, reason?, reasonName? }`;
|
|
1204
|
+
`type` defaults to `'background'`). It is stored and the `'error'` [event](#event-error) fires with
|
|
1205
|
+
the reconstructed [`BackgroundError`](#event-error). Useful for surfacing an application-level
|
|
1206
|
+
"this database is unusable" state, and for deterministically exercising the error path in tests.
|
|
1207
|
+
|
|
1208
|
+
```typescript
|
|
1209
|
+
// reset after recovery
|
|
1210
|
+
db.resume();
|
|
1211
|
+
db.setLastError(null);
|
|
1212
|
+
|
|
1213
|
+
// inject (e.g. in a test)
|
|
1214
|
+
db.on('error', (err) => console.error(err.message));
|
|
1215
|
+
db.setLastError({
|
|
1216
|
+
message: 'disk quota exceeded',
|
|
1217
|
+
severity: 2,
|
|
1218
|
+
severityName: 'hard',
|
|
1219
|
+
writesDisabled: true,
|
|
1220
|
+
});
|
|
1221
|
+
```
|
|
1222
|
+
|
|
1223
|
+
### `db.resume(): void`
|
|
1224
|
+
|
|
1225
|
+
Attempts to recover the database from a background error (see the `'error'` [event](#event-error))
|
|
1226
|
+
by calling RocksDB's `DB::Resume()`. Call this after the underlying condition has cleared — e.g.
|
|
1227
|
+
once disk space has been freed. On success writes are accepted again (and RocksDB can resume the
|
|
1228
|
+
obsolete-file cleanup it was blocking); on failure — the condition has not actually cleared — it
|
|
1229
|
+
throws with the RocksDB error and the database stays read-only. A no-op on a healthy database.
|
|
1230
|
+
|
|
1231
|
+
Runs synchronously and may briefly block, since recovery can re-flush memtables.
|
|
1232
|
+
|
|
1233
|
+
```typescript
|
|
1234
|
+
db.on('error', (err) => {
|
|
1235
|
+
if (err.writesDisabled) {
|
|
1236
|
+
// ...free disk space, then...
|
|
1237
|
+
db.resume();
|
|
1238
|
+
}
|
|
768
1239
|
});
|
|
769
1240
|
```
|
|
770
1241
|
|
|
@@ -794,6 +1265,89 @@ The `'committed'` event is emitted after the transaction has been written. When
|
|
|
794
1265
|
emitted, the transaction is still cleaning up. If you need to know when the transaction is fully
|
|
795
1266
|
complete, use the `'aftercommit'` event.
|
|
796
1267
|
|
|
1268
|
+
### Event: `'error'`
|
|
1269
|
+
|
|
1270
|
+
When a write fails at the filesystem level — a full disk, an exhausted quota — RocksDB records a
|
|
1271
|
+
background error and, for a hard-or-worse severity, stops accepting writes. The database emits an
|
|
1272
|
+
`'error'` event carrying a `BackgroundError` so a consumer can observe it and recover in-process
|
|
1273
|
+
instead of restarting. When `writesDisabled` is `true`, call
|
|
1274
|
+
[`db.resume()`](#dbresume-void) after the underlying condition clears. Register the listener with
|
|
1275
|
+
`db.on('error', ...)` before the database is put under load — or use the pull-based
|
|
1276
|
+
[`db.getLastError()`](#dbgetlasterror-backgrounderror--null) to catch an error that fired earlier.
|
|
1277
|
+
|
|
1278
|
+
`BackgroundError` is a real `Error` subclass, exported from the package, so both
|
|
1279
|
+
`err instanceof Error` and `err instanceof BackgroundError` hold. It extends `Error` with:
|
|
1280
|
+
|
|
1281
|
+
- `message: string` — the RocksDB error string.
|
|
1282
|
+
- `name: string` — always `'BackgroundError'`.
|
|
1283
|
+
- `type: string` — the error-class discriminator; always `'background'`.
|
|
1284
|
+
- `severity: number` — the RocksDB `Status::Severity`: `1` soft, `2` hard, `3` fatal,
|
|
1285
|
+
`4` unrecoverable.
|
|
1286
|
+
- `severityName: string` — `'soft'`, `'hard'`, `'fatal'`, or `'unrecoverable'`.
|
|
1287
|
+
- `writesDisabled: boolean` — whether RocksDB has disabled writes on the database (`severity >= 2`).
|
|
1288
|
+
Only then is `resume()` warranted; a soft error is auto-recoverable and leaves writes enabled.
|
|
1289
|
+
Distinct from opening the database in read-only mode.
|
|
1290
|
+
- `reason?: number` / `reasonName?: string` — the originating `BackgroundErrorReason`
|
|
1291
|
+
(e.g. `'flush'`, `'compaction'`), present when the error came from a reason-bearing callback.
|
|
1292
|
+
|
|
1293
|
+
```typescript
|
|
1294
|
+
import { BackgroundError } from '@harperfast/rocksdb-js';
|
|
1295
|
+
|
|
1296
|
+
db.on('error', (err) => {
|
|
1297
|
+
if (err instanceof BackgroundError && err.writesDisabled) {
|
|
1298
|
+
console.error(`writes disabled (${err.severityName}): ${err.message}`);
|
|
1299
|
+
}
|
|
1300
|
+
});
|
|
1301
|
+
```
|
|
1302
|
+
|
|
1303
|
+
```typescript
|
|
1304
|
+
db.on('error', (err) => {
|
|
1305
|
+
if (err.writesDisabled) {
|
|
1306
|
+
// Hard-or-worse: RocksDB has disabled writes on this database. They stay
|
|
1307
|
+
// disabled until you clear the underlying condition and call db.resume().
|
|
1308
|
+
console.error(`database writes disabled (${err.severityName}): ${err.message}`);
|
|
1309
|
+
} else {
|
|
1310
|
+
// Soft error: writes are NOT disabled and RocksDB will auto-recover. This
|
|
1311
|
+
// is typically a transient background hiccup (e.g. a retryable I/O error
|
|
1312
|
+
// during flush or compaction), surfaced for visibility only — no action
|
|
1313
|
+
// is required.
|
|
1314
|
+
console.warn(`recoverable background error (${err.severityName}): ${err.message}`);
|
|
1315
|
+
}
|
|
1316
|
+
});
|
|
1317
|
+
```
|
|
1318
|
+
|
|
1319
|
+
### Event: `'writeStall'`
|
|
1320
|
+
|
|
1321
|
+
Emitted when a column family **enters** a RocksDB write-stall — the push signal that writes are being
|
|
1322
|
+
throttled or blocked (for example when `dbWriteBufferSize` is too small for the number of column
|
|
1323
|
+
families and RocksDB thrashes on premature flushes). Listeners receive three string arguments:
|
|
1324
|
+
|
|
1325
|
+
- `columnFamily: string` — the column family that entered the stall (`'default'` for the primary).
|
|
1326
|
+
- `previousCondition: string` — `'normal' | 'delayed' | 'stopped'`.
|
|
1327
|
+
- `currentCondition: string` — `'delayed'` (writes rate-limited) or `'stopped'` (writes blocked until
|
|
1328
|
+
a flush frees a memtable/L0 slot).
|
|
1329
|
+
|
|
1330
|
+
The event is **rising-edge only and rate-limited** per column family. RocksDB oscillates a CF's stall
|
|
1331
|
+
condition many times per second during a sustained stall, so the event fires when a CF _enters_ a
|
|
1332
|
+
stall and then at most once per `ROCKSDB_JS_WRITE_STALL_DEBOUNCE_MS` (default 1000 ms) while it stays
|
|
1333
|
+
stalling — never a flood. **Recovery is not pushed**: reliably distinguishing a brief dip from a real
|
|
1334
|
+
recovery isn't possible without a timer, so for current state use the pull below rather than waiting
|
|
1335
|
+
for a `'normal'` event.
|
|
1336
|
+
|
|
1337
|
+
For "are writes stalled right now?", call [`db.isWriteStalled()`](#dbiswritestalled-boolean) — it
|
|
1338
|
+
reads the live write-controller state and is authoritative. Note it is **database-wide** (the write
|
|
1339
|
+
controller is shared across all column families), so it answers "is anything stalling writes" while
|
|
1340
|
+
the event names _which_ CF; a listener attached mid-stall should consult it rather than expect a
|
|
1341
|
+
missed rising edge.
|
|
1342
|
+
|
|
1343
|
+
```typescript
|
|
1344
|
+
db.on('writeStall', (cf, prev, cur) => {
|
|
1345
|
+
console.warn(`RocksDB write stall entered on ${cf}: ${prev} -> ${cur}`);
|
|
1346
|
+
});
|
|
1347
|
+
// ...and to check the current state on demand:
|
|
1348
|
+
if (db.isWriteStalled()) console.warn('writes are currently throttled or blocked');
|
|
1349
|
+
```
|
|
1350
|
+
|
|
797
1351
|
## Event API
|
|
798
1352
|
|
|
799
1353
|
`rocksdb-js` provides a EventEmitter-like API that lets you asynchronously notify events to one or
|
|
@@ -934,8 +1488,11 @@ Column family and ticker stat values are 64-bit unsigned integers and histogram
|
|
|
934
1488
|
`StatsHistogramData` objects.
|
|
935
1489
|
|
|
936
1490
|
The result also always includes a summarized, aggregate set of `txnlog.*` keys covering all of
|
|
937
|
-
the database's transaction logs
|
|
938
|
-
|
|
1491
|
+
the database's transaction logs, and a set of `writeBufferManager.*` keys describing the
|
|
1492
|
+
process-wide `WriteBufferManager`
|
|
1493
|
+
([`getWriteBufferManagerStats()`](#getwritebuffermanagerstats-writebuffermanagerstats)
|
|
1494
|
+
carries the same values plus its column-family inventory). Both sets are present regardless of
|
|
1495
|
+
whether statistics are enabled and can be fetched individually with `db.getStat()`. For detailed, per-log statistics —
|
|
939
1496
|
including memory-map usage — use [`log.getStats()`](#loggetstats-transactionlogstats). All stat
|
|
940
1497
|
names are documented in [docs/stats.md](docs/stats.md).
|
|
941
1498
|
|
|
@@ -1242,6 +1799,14 @@ holds the last-known version for that key. Because slots are addressed by a hash
|
|
|
1242
1799
|
share a slot; a collision only ever causes a conservative miss (a real read), never a stale value to
|
|
1243
1800
|
be treated as fresh.
|
|
1244
1801
|
|
|
1802
|
+
This first word is the only version the table derives on its own: it is what a read with
|
|
1803
|
+
`populateVersion: true` publishes and what a transaction write invalidates against. A producer whose
|
|
1804
|
+
record format also carries a separately assigned version elsewhere in the value must keep using the
|
|
1805
|
+
first word on both sides of the check — as `expectedVersion`, and as the argument to any explicit
|
|
1806
|
+
[`db.populateVersion()`](#dbpopulateversionkey-key-version-number-void) call, which publishes
|
|
1807
|
+
whatever it is given. Comparing against the other value would miss the fast path, and publishing it
|
|
1808
|
+
would make the slot disagree with what the next write invalidates.
|
|
1809
|
+
|
|
1245
1810
|
The freshness check works as follows:
|
|
1246
1811
|
|
|
1247
1812
|
1. Pass `{ expectedVersion }` to `get()` / `getSync()`. If the slot currently records that version,
|
|
@@ -1359,9 +1924,19 @@ const names = db.listLogs();
|
|
|
1359
1924
|
### `db.purgeLogs({ includeEntryCounts: true, ...options }): { path: string; entries: number }[]`
|
|
1360
1925
|
|
|
1361
1926
|
Deletes transaction log files older than the `transactionLogRetention` (defaults to 3 days).
|
|
1927
|
+
Ordinary retention keeps the sequence file named by `txn.state` and every newer file as the live
|
|
1928
|
+
store's retention floor, or the highest sequence file when there is no persisted flush position. It
|
|
1929
|
+
removes only an eligible contiguous prefix below that floor. An idle store can therefore retain one
|
|
1930
|
+
file past the cutoff until a later write rotates and flushes it; that extra file is bounded by the
|
|
1931
|
+
store's `transactionLogMaxSize` (except when a single transaction exceeds the target). That bound
|
|
1932
|
+
covers only the floor file: when the flush position lags — `txn.state` stuck at an old sequence
|
|
1933
|
+
because RocksDB flushing is behind — every file above the floor is retained too, which
|
|
1934
|
+
`transactionLogMaxSize` does not bound. `purge.retainedUnflushedFiles` reports that case. Use
|
|
1935
|
+
`destroy: true` only to remove the store itself.
|
|
1362
1936
|
|
|
1363
1937
|
- `options: object`
|
|
1364
|
-
- `before?: number` Remove
|
|
1938
|
+
- `before?: number` Remove transaction log files older than the specified timestamp, subject to
|
|
1939
|
+
the retention floor and contiguous-prefix rules above.
|
|
1365
1940
|
- `destroy?: boolean` When `true`, deletes transaction log stores including all log sequence files
|
|
1366
1941
|
on disk.
|
|
1367
1942
|
- `includeEntryCounts?: boolean` When `true`, counts the entries in each deleted log file and
|
|
@@ -1535,7 +2110,9 @@ stats.totals.transactionsWritten; // lifetime count of transactions written
|
|
|
1535
2110
|
|
|
1536
2111
|
The `purge.retainedUnflushedFiles` gauge is useful for diagnosing why logs are not being cleaned
|
|
1537
2112
|
up: a file can be older than the retention period but still retained because its transactions have
|
|
1538
|
-
not yet been flushed to RocksDB (purging it would be unsafe for crash recovery).
|
|
2113
|
+
not yet been flushed to RocksDB (purging it would be unsafe for crash recovery). The retention
|
|
2114
|
+
floor — the sequence named by `txn.state`, or the highest sequence when there is no persisted flush
|
|
2115
|
+
position — is not counted as purgeable even when it is old and fully flushed.
|
|
1539
2116
|
|
|
1540
2117
|
### Transaction Log Initialization
|
|
1541
2118
|
|
|
@@ -1705,7 +2282,7 @@ snapshots of a database. A backup covers the **entire database** — every colum
|
|
|
1705
2282
|
manifest, and (by default) the write-ahead log — so it is not scoped to an individual `Store`.
|
|
1706
2283
|
|
|
1707
2284
|
A backup can be written to a local **directory** (incremental, with a management API) or streamed to
|
|
1708
|
-
a
|
|
2285
|
+
a `WritableStream` as a tar archive with no intermediate copy on disk. See
|
|
1709
2286
|
[docs/backups.md](docs/backups.md) for a full guide covering both modes, restore, checkpoints, and
|
|
1710
2287
|
caveats.
|
|
1711
2288
|
|
|
@@ -1781,10 +2358,10 @@ required bytes — before any files are copied, so a full destination fails fast
|
|
|
1781
2358
|
through (a mid-copy failure can leave zero usable backups). The required size is estimated
|
|
1782
2359
|
conservatively as a full copy of the database's live files, plus the transaction-log snapshot when
|
|
1783
2360
|
`transactionLogs` is set; since a backup only ever copies files, this can never under-estimate.
|
|
1784
|
-
Incremental backups to a right-sized volume may therefore be over-rejected — set
|
|
1785
|
-
false` to skip the check. It also self-disables when free space can't be determined
|
|
1786
|
-
network filesystems report `0`), so it never blocks a backup on an untrustworthy number.
|
|
1787
|
-
directory-target backups are checked; the streaming backup path is unaffected.
|
|
2361
|
+
Incremental backups to a right-sized volume may therefore be over-rejected — set
|
|
2362
|
+
`checkDiskSpace: false` to skip the check. It also self-disables when free space can't be determined
|
|
2363
|
+
(e.g. some network filesystems report `0`), so it never blocks a backup on an untrustworthy number.
|
|
2364
|
+
Only directory-target backups are checked; the streaming backup path is unaffected.
|
|
1788
2365
|
|
|
1789
2366
|
### `db.backup(stream: WritableStream<Uint8Array>, options?: BackupStreamOptions): Promise<void>`
|
|
1790
2367
|
|
|
@@ -2014,7 +2591,9 @@ Options for `get()`, `getSync()`, and the `getBinary*` methods.
|
|
|
2014
2591
|
- `tailing: boolean` When `true`, creates a "tailing iterator" which is a special iterator that
|
|
2015
2592
|
has a view of the complete database including newly added data and is optimized for sequential
|
|
2016
2593
|
reads. This will return records that were inserted into the database after the creation of the
|
|
2017
|
-
iterator. Defaults to `false`.
|
|
2594
|
+
iterator. Defaults to `false`. A tailing iterator ignores a transaction's snapshot: through a
|
|
2595
|
+
transaction it still reads the latest committed state, merged with that transaction's staged
|
|
2596
|
+
writes.
|
|
2018
2597
|
|
|
2019
2598
|
### `RangeOptions`
|
|
2020
2599
|
|