@harperfast/rocksdb-js 2.8.0 → 2.9.1
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 +357 -27
- package/dist/index.cjs +290 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +327 -38
- package/dist/index.d.mts +327 -38
- package/dist/index.mjs +289 -41
- package/dist/index.mjs.map +1 -1
- package/docs/stats.md +46 -1
- package/docs/transaction-log.md +18 -5
- package/docs/transaction-timestamp-integrity-design.md +123 -0
- package/package.json +19 -19
package/README.md
CHANGED
|
@@ -95,10 +95,20 @@ Creates a new database instance.
|
|
|
95
95
|
- `maxWriteBufferSizeToMaintain: number` The number of bytes of recent memtable history to keep in
|
|
96
96
|
memory for transaction conflict checking. `-1` (the default) derives the value from
|
|
97
97
|
`maxWriteBufferNumber * writeBufferSize` (the RocksDB-recommended default for optimistic
|
|
98
|
-
transactions) — except when a
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
98
|
+
transactions) — except when the database has a [`writeBufferManager`](#dbconfigoptions)
|
|
99
|
+
attached, in which case it resolves to `1` so the manager is not filled with history it will
|
|
100
|
+
never release. What decides this is the manager the database itself holds, not the current
|
|
101
|
+
`writeBufferManagerSize`: a column family created later on an already-open database is clamped
|
|
102
|
+
too, because its history is still charged to that manager. It also applies to any attached
|
|
103
|
+
manager, not only a stalling one — `writeBufferManagerAllowStall` can be changed at runtime
|
|
104
|
+
while this value is fixed when a column family is created, and history the budget cannot reclaim
|
|
105
|
+
is a problem either way. `1` is the smallest target a transactional database can be given: RocksDB's transaction wrappers rewrite a `0` target to
|
|
106
|
+
the derived value, so `0` requests the _largest_ history rather than none, and an explicitly
|
|
107
|
+
requested `0` is normalized to `1` for the same reason. A positive target still retains the most
|
|
108
|
+
recent flushed memtable per column family until that family's next write. An explicit positive
|
|
109
|
+
value is honored as-is; sizing it against the budget and the column-family count is then yours,
|
|
110
|
+
and a configuration whose known families already reach the budget is reported on the
|
|
111
|
+
[`'log.warn'`](#event-api) channel.
|
|
102
112
|
- `name: string` The column family name. Defaults to `"default"`.
|
|
103
113
|
- `noBlockCache: boolean` When `true`, disables the block cache. Block caching is enabled by
|
|
104
114
|
default and the cache is shared across all database instances.
|
|
@@ -112,6 +122,40 @@ Creates a new database instance.
|
|
|
112
122
|
[`flushSync()`](#dbflushsyncoptions-void), [`compact()`](#dbcompactoptions-promisevoid) and
|
|
113
123
|
[`compactSync()`](#dbcompactsyncoptions-void) are the exception: they have nothing to do when
|
|
114
124
|
there are no writes, so they succeed as no-ops rather than throwing.
|
|
125
|
+
|
|
126
|
+
A read-only open is a **point-in-time snapshot**: it never sees later writes. It is safe
|
|
127
|
+
against a quiescent database, but **unsafe against a live writer**: the open holds no reference
|
|
128
|
+
on the files it is about to read, so a concurrent compaction, blob GC, or flush in the writing
|
|
129
|
+
process can delete one mid-open and the open throws with code `ERR_CONCURRENT_COMPACTION` — the
|
|
130
|
+
database is not corrupt; the reader lost a race, and retrying can succeed. Even a successful
|
|
131
|
+
open can fail _later reads_ the same way, because files the snapshot references are opened
|
|
132
|
+
lazily while the writer keeps deleting obsolete ones. To follow a database another process is
|
|
133
|
+
actively writing, open a secondary instead (`secondaryPath` below) — that is the supported mode
|
|
134
|
+
for a live follower.
|
|
135
|
+
|
|
136
|
+
- `secondaryPath: string` Opens the database as a **secondary instance**: a read-only follower of
|
|
137
|
+
a live primary that tolerates the primary deleting files and sees new writes on each
|
|
138
|
+
[`catchUpWithPrimary()`](#dbcatchupwithprimary-promisevoid) call. This is the supported way to
|
|
139
|
+
read a database another process is actively writing. The value is the secondary instance's own
|
|
140
|
+
workspace directory (created if missing) where RocksDB keeps the secondary's private state — it
|
|
141
|
+
must be outside `path` (enforced) and exclusive to one secondary instance. Exclusivity is
|
|
142
|
+
enforced: reusing a workspace in-process for a different database is rejected at open (the same
|
|
143
|
+
database + workspace share the one follower instance), and a kernel advisory lock on
|
|
144
|
+
`<secondaryPath>/.secondary.lock` (same discipline and caveats as the
|
|
145
|
+
[backup directory lock](#backups)) excludes other processes — with the same limits: on
|
|
146
|
+
filesystems without advisory locking (`flock` unsupported — e.g. the FUSE/9p mounts behind
|
|
147
|
+
Docker Desktop bind mounts) the lock degrades to a no-op, and on network filesystems with
|
|
148
|
+
node-local `flock` (NFS `local_lock`, CIFS, 9p) it does not exclude across hosts, so workspace
|
|
149
|
+
exclusivity is the caller's job there. Implies
|
|
150
|
+
`readOnly: true` (an explicit `readOnly: false` throws), so all read-only behavior above
|
|
151
|
+
applies. Forces `maxOpenFiles: -1`: every table and blob file is opened and held for the life
|
|
152
|
+
of each version, which is what makes the primary's deletions safe — budget file descriptors
|
|
153
|
+
accordingly on large databases (an explicit `maxOpenFiles` other than `-1` is rejected). Column
|
|
154
|
+
families created by the primary after the secondary opens are invisible until the secondary
|
|
155
|
+
reopens. Note: RocksDB upstream documents secondary instances as unsupported in combination
|
|
156
|
+
with integrated BlobDB (which this library enables for values ≥ 2KB); the pinned RocksDB build
|
|
157
|
+
handles blob files in secondary mode — covered by native regression tests — but upstream does
|
|
158
|
+
not guarantee the combination.
|
|
115
159
|
- `statsLevel: StatsLevel` Controls which type of statistics to skip and reduce statistic
|
|
116
160
|
overhead. Defaults to `StatsLevel.ExceptDetailedTimers`.
|
|
117
161
|
- `store: Store` A custom store that handles all interaction between the `RocksDatabase` or
|
|
@@ -156,7 +200,7 @@ Returns the list of column families in the RocksDB database.
|
|
|
156
200
|
const db = RocksDatabase.open('path/to/db');
|
|
157
201
|
console.log(db.columns); // ['default']
|
|
158
202
|
|
|
159
|
-
|
|
203
|
+
db.use('users');
|
|
160
204
|
console.log(db.columns); // ['default', 'users']
|
|
161
205
|
```
|
|
162
206
|
|
|
@@ -191,15 +235,21 @@ Sets global database settings.
|
|
|
191
235
|
- `writeBufferManagerAllowStall: boolean` When `true`, writes are stalled once the manager's
|
|
192
236
|
`buffer_size` is exceeded, providing a hard cap on memtable memory. When `false`, memtables are
|
|
193
237
|
allowed to grow past the limit and flushes are simply scheduled more aggressively. Off by
|
|
194
|
-
default to favor write throughput over hard memory bounding. Defaults to `false`.
|
|
238
|
+
default to favor write throughput over hard memory bounding. Defaults to `false`. A stall here
|
|
239
|
+
is invisible to RocksDB's own stall counters and to
|
|
240
|
+
[`db.isWriteStalled()`](#dbiswritestalled-boolean) — see
|
|
241
|
+
[`getWriteBufferManagerStats()`](#getwritebuffermanagerstats-writebuffermanagerstats),
|
|
242
|
+
which also describes the watchdog that logs a sustained stall.
|
|
195
243
|
- `writeBufferManagerCostToCache: boolean` When `true`, memtable memory is "charged" against the
|
|
196
244
|
shared block cache so the block cache and write buffers draw from a single pool. During write
|
|
197
245
|
bursts the cache shrinks to make room for memtables; once memtables flush, the cache can grow
|
|
198
246
|
back into the reclaimed space. Defaults to `false`.
|
|
199
247
|
- `writeBufferManagerSize: number` Total memtable memory limit (bytes) shared across every
|
|
200
248
|
database opened in this process. When set, RocksDB uses a single `WriteBufferManager` so write
|
|
201
|
-
buffers are bounded process-wide rather than per database.
|
|
202
|
-
|
|
249
|
+
buffers are bounded process-wide rather than per database. Defaults to `0`, which means no
|
|
250
|
+
manager. Setting `0` later stops _new_ opens from attaching one, but does not detach or resize
|
|
251
|
+
the manager a database already holds — `write_buffer_manager` is fixed for the life of an open
|
|
252
|
+
database, so its memtables stay charged against that budget.
|
|
203
253
|
|
|
204
254
|
```typescript
|
|
205
255
|
RocksDatabase.config({
|
|
@@ -211,6 +261,80 @@ RocksDatabase.config({
|
|
|
211
261
|
});
|
|
212
262
|
```
|
|
213
263
|
|
|
264
|
+
### `getWriteBufferManagerStats(): WriteBufferManagerStats`
|
|
265
|
+
|
|
266
|
+
Reads the live state of the `WriteBufferManager`.
|
|
267
|
+
|
|
268
|
+
**Process-wide, not per-database.** The manager is a singleton shared by every database opened in
|
|
269
|
+
this process, `worker_threads` included, so these values describe the whole process regardless of
|
|
270
|
+
where the call was made.
|
|
271
|
+
|
|
272
|
+
- `enabled: boolean` Whether a manager has been created. `bufferSize`, `memoryUsage`,
|
|
273
|
+
`mutableMemoryUsage`, `stallActive`, `stallActiveMs`, `watchdogRunning` and `columnFamilies` are
|
|
274
|
+
`0`/`false` when not; `allowStall` and `costToCache` still reflect the configured setting (a
|
|
275
|
+
manager is only created once `writeBufferManagerSize` is also set), and `inventoryAvailable` is
|
|
276
|
+
`true` (there is nothing to fail to collect).
|
|
277
|
+
- `bufferSize: number` The budget in bytes (`writeBufferManagerSize`, read live).
|
|
278
|
+
- `memoryUsage: number` Total memtable memory in bytes charged against the manager.
|
|
279
|
+
- `mutableMemoryUsage: number` The share of `memoryUsage` held by active (mutable) memtables; the
|
|
280
|
+
rest is memtables awaiting flush plus retained write history.
|
|
281
|
+
- `allowStall: boolean`, `costToCache: boolean` The manager's configuration.
|
|
282
|
+
- `stallActive: boolean` Whether the manager is currently stalling writes.
|
|
283
|
+
- `stallActiveMs: number` How long the current stall has been active; `0` when not stalled.
|
|
284
|
+
Sampled once a second by the watchdog, so `0` for a stall's first second and whenever
|
|
285
|
+
`watchdogRunning` is `false`.
|
|
286
|
+
- `watchdogRunning: boolean` Whether the stall watchdog thread is running.
|
|
287
|
+
- `columnFamilies: number` Live column families across every database attached to this manager. A
|
|
288
|
+
dropped column family keeps charging the manager until its last handle closes, so it is counted
|
|
289
|
+
until then.
|
|
290
|
+
- `inventoryAvailable: boolean` `false` when the inventory could not be collected because the
|
|
291
|
+
database registry or a column-family inventory was locked. The two inventory fields are then
|
|
292
|
+
empty and everything else is still live; the call never blocks on database work that may itself
|
|
293
|
+
be stalled.
|
|
294
|
+
- `maxWriteBufferSizeToMaintain: Record<string, number>` Effective per-column-family retained-history
|
|
295
|
+
target (as a decimal string) to how many of those column families carry it. Effective, not
|
|
296
|
+
requested: RocksDB rewrites a requested `0` for a transaction database.
|
|
297
|
+
|
|
298
|
+
`bufferSize`, `memoryUsage`, `mutableMemoryUsage`, `stallActive` and `stallActiveMs` are also in
|
|
299
|
+
[`db.getStats()`](#dbgetstatsall-boolean-rocksdbstats) and `db.getStat()` under the same
|
|
300
|
+
`writeBufferManager.` prefix, for scraping; `enabled`, `allowStall`, `costToCache`,
|
|
301
|
+
`watchdogRunning`, and the column-family inventory are only here, because collecting them either
|
|
302
|
+
walks the database registry or isn't scrape-shaped.
|
|
303
|
+
|
|
304
|
+
`stallActive` is the signal that distinguishes a stalled process from an idle one.
|
|
305
|
+
[`db.isWriteStalled()`](#dbiswritestalled-boolean), the [`'writeStall'` event](#event-writestall)
|
|
306
|
+
and `rocksdb.stall.micros` all track RocksDB's `WriteController`, which a `WriteBufferManager`
|
|
307
|
+
stall never goes through.
|
|
308
|
+
|
|
309
|
+
#### The stall watchdog
|
|
310
|
+
|
|
311
|
+
While a manager exists with `writeBufferManagerAllowStall`, one low-frequency thread samples the
|
|
312
|
+
manager once a second. When a stall has been continuously active past
|
|
313
|
+
`ROCKSDB_JS_WBM_STALL_WARN_MS` (default `5000`; `0` disables the watchdog) it writes **one** line
|
|
314
|
+
to `stderr` and emits it as a process-wide `'log.warn'` event — once per stall episode, not per
|
|
315
|
+
blocked writer and not per sample:
|
|
316
|
+
|
|
317
|
+
```
|
|
318
|
+
[rocksdb-js] WriteBufferManager write stall active for 5.0s - no write can complete until memtable
|
|
319
|
+
memory drops below the budget. budget=661.2MB usage=662.2MB (100.2%) mutable=12.4MB (1.9%)
|
|
320
|
+
allowStall=true costToCache=true columnFamilies=28 maxWriteBufferSizeToMaintain={268435456:28}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
It needs its own thread because every other candidate is blocked by the very condition it reports:
|
|
324
|
+
a writer is parked inside RocksDB, RocksDB's stall callbacks are `WriteController`-only, and a JS
|
|
325
|
+
timer cannot fire on a thread parked in a synchronous write. The line goes to `stderr` as well as
|
|
326
|
+
the event so it is not lost when nothing has registered a `'log.warn'` listener; route one and
|
|
327
|
+
ignore `stderr` if you would rather have it in your own log.
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
RocksDatabase.on('log.warn', (message) => logger.warn(message));
|
|
331
|
+
|
|
332
|
+
const wbm = getWriteBufferManagerStats();
|
|
333
|
+
if (wbm.stallActive) {
|
|
334
|
+
logger.warn(`writes stalled for ${wbm.stallActiveMs}ms: ${wbm.memoryUsage}/${wbm.bufferSize}`);
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
214
338
|
### `db.isOpen(): boolean`
|
|
215
339
|
|
|
216
340
|
Returns `true` if the database is open, otherwise false.
|
|
@@ -260,6 +384,20 @@ There's also a static `open()` method for convenience that performs the same thi
|
|
|
260
384
|
const db = RocksDatabase.open('path/to/db');
|
|
261
385
|
```
|
|
262
386
|
|
|
387
|
+
### `db.secondaryPath: string | undefined`
|
|
388
|
+
|
|
389
|
+
The secondary instance's workspace directory when the database was opened as a
|
|
390
|
+
[secondary](#new-rocksdatabasepath-options) (a read-only follower of a live primary), or
|
|
391
|
+
`undefined` for a regular or plain read-only open.
|
|
392
|
+
|
|
393
|
+
```typescript
|
|
394
|
+
const follower = RocksDatabase.open('/path/to/database', {
|
|
395
|
+
secondaryPath: '/path/to/follower-workspace',
|
|
396
|
+
});
|
|
397
|
+
console.log(follower.secondaryPath); // '/path/to/follower-workspace'
|
|
398
|
+
console.log(follower.readOnly); // true
|
|
399
|
+
```
|
|
400
|
+
|
|
263
401
|
### `db.status: 'opened' | 'closed'`
|
|
264
402
|
|
|
265
403
|
Returns a string `'opened'` or `'closed'` indicating if the database is opened or closed.
|
|
@@ -268,8 +406,95 @@ Returns a string `'opened'` or `'closed'` indicating if the database is opened o
|
|
|
268
406
|
console.log(db.status);
|
|
269
407
|
```
|
|
270
408
|
|
|
409
|
+
### `db.use(name, options?): RocksDatabase`
|
|
410
|
+
|
|
411
|
+
Returns a `RocksDatabase` bound to the `name` column family of this same database, opening — and
|
|
412
|
+
creating, if it does not exist — the column family on first use. This is a factory (like
|
|
413
|
+
[`useLog`](#dbuselogname-transactionlog)), not a stateful switch: the returned view is an
|
|
414
|
+
independent instance whose own reads and writes target its column family, while sharing the same
|
|
415
|
+
underlying database. Because a single RocksDB database backs every column family, a transaction,
|
|
416
|
+
backup, or checkpoint still spans all of them.
|
|
417
|
+
|
|
418
|
+
- `name: string` The column family name.
|
|
419
|
+
- `options?: object` Options for the column family (same shape as the constructor's, minus `name`),
|
|
420
|
+
overriding the options inherited from this database. Options only take effect when the view is
|
|
421
|
+
(re)opened.
|
|
422
|
+
|
|
423
|
+
`use()` is get-or-create, backed by a weak cache: `db.use('events') === db.use('events')` while the
|
|
424
|
+
view is still referenced **and open**, and calling with this database's own column-family name
|
|
425
|
+
returns `this`. A view that has been closed (or garbage-collected — the cache does not pin it) is
|
|
426
|
+
transparently recreated on the next `use()`. Views are independent handles: closing this database
|
|
427
|
+
does not close them (and vice versa); the underlying database stays open until every handle is closed
|
|
428
|
+
or collected.
|
|
429
|
+
|
|
430
|
+
The view's store is derived by `Store#createColumnFamilyStore(name, options)`, which builds an
|
|
431
|
+
independent store of the same class with **its own codec state** — views never share a mutable
|
|
432
|
+
encoder/decoder. Consequently a database configured with a _pre-constructed_ encoder/decoder instance
|
|
433
|
+
cannot derive views (its `name`/`structures` would be shared and corrupt both column families); use an
|
|
434
|
+
encoder factory (`{ Encoder }`) or a named `encoding` instead. A custom `Store` whose constructor
|
|
435
|
+
can't be recreated from `(path, options)` (e.g. it takes injected dependencies) should override
|
|
436
|
+
`createColumnFamilyStore` to build its views.
|
|
437
|
+
|
|
438
|
+
```typescript
|
|
439
|
+
const db = RocksDatabase.open('path/to/db');
|
|
440
|
+
|
|
441
|
+
const events = db.use('events');
|
|
442
|
+
await events.put('e1', payload);
|
|
443
|
+
|
|
444
|
+
await db.put('k', 'v'); // default column family, unaffected
|
|
445
|
+
console.log(events.get('e1')); // payload
|
|
446
|
+
console.log(db.get('e1')); // undefined — different column family
|
|
447
|
+
|
|
448
|
+
events.close();
|
|
449
|
+
db.close();
|
|
450
|
+
```
|
|
451
|
+
|
|
271
452
|
## Data Operations
|
|
272
453
|
|
|
454
|
+
### `db.catchUpWithPrimary(): Promise<void>`
|
|
455
|
+
|
|
456
|
+
Advances a [secondary instance](#new-rocksdatabasepath-options) to the primary's current state by
|
|
457
|
+
tailing and replaying the primary's MANIFEST and WAL. A secondary does not see the primary's
|
|
458
|
+
writes — flushed or not — until it catches up. Reads through the handle remain safe while the
|
|
459
|
+
catch-up runs; catch-ups on the same database are serialized internally.
|
|
460
|
+
|
|
461
|
+
Throws with code `ERR_NOT_SECONDARY` on a database that was not opened with `secondaryPath`.
|
|
462
|
+
Column families the primary created after the secondary opened stay invisible until the secondary
|
|
463
|
+
reopens; ones the primary dropped remain readable until then. Catch-up advances the **database**
|
|
464
|
+
view only: transaction-log reads through a secondary serve the stores discovered at open, and a
|
|
465
|
+
store the primary creates afterward becomes visible only on reopen. Whether entries appended to an
|
|
466
|
+
already-open store are visible depends on where the writer is: a cross-process primary's appends
|
|
467
|
+
are not (the reader's view of the file extent is fixed at open), while a writer in the _same_
|
|
468
|
+
process shares the store object, so its appends are. Either way a log entry can describe data the
|
|
469
|
+
database view does not have yet — the log write completes before the RocksDB commit for every
|
|
470
|
+
writer, so log-leads-database is the normal direction and a consumer has to tolerate it. Serialize
|
|
471
|
+
catch-up calls per database. Each async call holds a libuv worker for the whole replay, and
|
|
472
|
+
concurrent calls queue on an internal per-database mutex while holding theirs, so a handful of
|
|
473
|
+
overlapping catch-ups on a backlogged follower can exhaust the default four-thread pool and stall
|
|
474
|
+
unrelated `fs`/`dns`/`crypto` work in the process. Await one before starting the next rather than
|
|
475
|
+
firing one per timer tick.
|
|
476
|
+
|
|
477
|
+
```typescript
|
|
478
|
+
const primary = RocksDatabase.open('/path/to/database');
|
|
479
|
+
const follower = RocksDatabase.open('/path/to/database', {
|
|
480
|
+
secondaryPath: '/path/to/follower-workspace',
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
primary.putSync('foo', 'bar');
|
|
484
|
+
console.log(follower.getSync('foo')); // undefined — not caught up yet
|
|
485
|
+
await follower.catchUpWithPrimary();
|
|
486
|
+
console.log(follower.getSync('foo')); // 'bar'
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
### `db.catchUpWithPrimarySync(): void`
|
|
490
|
+
|
|
491
|
+
Synchronous version of `catchUpWithPrimary()`. The replay runs on the JS thread, so prefer the
|
|
492
|
+
async form unless the caller is already blocking.
|
|
493
|
+
|
|
494
|
+
```typescript
|
|
495
|
+
follower.catchUpWithPrimarySync();
|
|
496
|
+
```
|
|
497
|
+
|
|
273
498
|
### `db.clear(options?): Promise<number>`
|
|
274
499
|
|
|
275
500
|
Asychronously removes all data in the current database.
|
|
@@ -571,7 +796,7 @@ const range = db.getKeysCount({ start: 'a', end: 'z' }); // exact number of keys
|
|
|
571
796
|
### `db.getMonotonicTimestamp(): number`
|
|
572
797
|
|
|
573
798
|
Returns the current timestamp as a monotonically increasing timestamp in milliseconds represented as
|
|
574
|
-
a decimal number.
|
|
799
|
+
a decimal number. This process-wide clock also supplies each transaction's initial timestamp.
|
|
575
800
|
|
|
576
801
|
```typescript
|
|
577
802
|
const ts = db.getMonotonicTimestamp();
|
|
@@ -631,6 +856,25 @@ const blobFiles = db.getDBIntProperty('rocksdb.num-blob-files');
|
|
|
631
856
|
const numKeys = db.getDBIntProperty('rocksdb.estimate-num-keys');
|
|
632
857
|
```
|
|
633
858
|
|
|
859
|
+
### `db.isWriteStalled(): boolean`
|
|
860
|
+
|
|
861
|
+
Whether RocksDB is currently applying write backpressure to this database —
|
|
862
|
+
delaying (rate-limiting) or fully stopping writes. Reads the live
|
|
863
|
+
`rocksdb.is-write-stopped` and `rocksdb.actual-delayed-write-rate` properties.
|
|
864
|
+
|
|
865
|
+
This is the authoritative, live pull counterpart to the
|
|
866
|
+
[`'writeStall'` event](#event-writestall): the event pushes per-column-family
|
|
867
|
+
entries into a stall (rate-limited), while this reports the current state on
|
|
868
|
+
demand and can never be stale. It is **database-wide** — the write controller is
|
|
869
|
+
shared across every column family, so it answers "are writes stalled anywhere"
|
|
870
|
+
rather than for one column family.
|
|
871
|
+
|
|
872
|
+
```typescript
|
|
873
|
+
if (db.isWriteStalled()) {
|
|
874
|
+
console.warn('writes are currently throttled or blocked');
|
|
875
|
+
}
|
|
876
|
+
```
|
|
877
|
+
|
|
634
878
|
### `db.getRange(options?: IteratorOptions): ExtendedIterable`
|
|
635
879
|
|
|
636
880
|
Retrieves a range of keys and their values. Supports both synchronous and asynchronous iteration.
|
|
@@ -652,6 +896,23 @@ for (const { key, value } of db.getRange({ start: 'a', end: 'z' })) {
|
|
|
652
896
|
}
|
|
653
897
|
```
|
|
654
898
|
|
|
899
|
+
Pass `transaction: txn` to iterate this column family through that transaction, exactly as
|
|
900
|
+
`get()` does with the same option: the iterator sees the transaction's staged writes and reads on its
|
|
901
|
+
snapshot, and range bounds apply to staged keys too. `getKeys()` and `getKeysCount()` accept it as
|
|
902
|
+
well. On a transaction (`txn.getRange()`) the transaction itself is the context and takes precedence
|
|
903
|
+
over a `transaction` option, as it does for `txn.get()`. Such an iterator is closed when the
|
|
904
|
+
transaction commits or aborts: a later `next()` throws, while `return()` stays a no-op. Opening a
|
|
905
|
+
range or counting through a transaction that has already started committing throws as well.
|
|
906
|
+
|
|
907
|
+
```typescript
|
|
908
|
+
await db.transaction(async (txn) => {
|
|
909
|
+
await txn.put('c', 'staged');
|
|
910
|
+
for (const { key, value } of db.getRange({ start: 'a', end: 'z', transaction: txn })) {
|
|
911
|
+
console.log({ key, value }); // includes { key: "c", value: "staged" }
|
|
912
|
+
}
|
|
913
|
+
});
|
|
914
|
+
```
|
|
915
|
+
|
|
655
916
|
### `db.getUserSharedBuffer(key: Key, defaultBuffer: ArrayBuffer, options?)`
|
|
656
917
|
|
|
657
918
|
Creates a new buffer with the contents of `defaultBuffer` that can be accessed across threads. This
|
|
@@ -848,13 +1109,14 @@ operations methods as the `RocksDatabase` instance plus:
|
|
|
848
1109
|
no further transaction operations are permitted. Calling this method multiple times has no effect.
|
|
849
1110
|
- `txn.commit(): Promise<void>` Asynchronously commits the transaction and closes the transaction.
|
|
850
1111
|
- `txn.commitSync()` Synchronously commits and closes the transaction.
|
|
851
|
-
- `txn.getTimestamp(): number` Retrieves the transaction
|
|
852
|
-
defaults to
|
|
1112
|
+
- `txn.getTimestamp(): number` Retrieves the transaction timestamp in milliseconds as a decimal. It
|
|
1113
|
+
defaults to a process-wide monotonic value assigned when the transaction was created.
|
|
853
1114
|
- `txn.id: number` The read-only transaction ID. Transaction IDs are unique to the RocksDB database
|
|
854
1115
|
path, regardless the database name/column family.
|
|
855
|
-
- `txn.setTimestamp(ts?: number): void` Overrides the transaction
|
|
856
|
-
a timestamp, it
|
|
857
|
-
|
|
1116
|
+
- `txn.setTimestamp(ts?: number): void` Overrides the transaction timestamp in milliseconds. If
|
|
1117
|
+
called without a timestamp, it claims a fresh monotonic value. It must be called before staging
|
|
1118
|
+
any write or transaction-log entry, and a supplied value must be finite, positive, and below
|
|
1119
|
+
`8.64e15`.
|
|
858
1120
|
|
|
859
1121
|
#### `txn.abandonWrites(): void`
|
|
860
1122
|
|
|
@@ -890,24 +1152,35 @@ Once called, no further transaction operations are permitted.
|
|
|
890
1152
|
|
|
891
1153
|
#### `txn.getTimestamp(): number`
|
|
892
1154
|
|
|
893
|
-
Retrieves the transaction
|
|
894
|
-
the transaction was created.
|
|
1155
|
+
Retrieves the transaction timestamp in milliseconds since the Unix epoch as a decimal. It defaults
|
|
1156
|
+
to a process-wide monotonic value assigned when the transaction was created. The transaction log
|
|
1157
|
+
uses this timestamp as the batch key; producers may also encode it into their own record format.
|
|
895
1158
|
|
|
896
1159
|
#### `txn.id`
|
|
897
1160
|
|
|
898
1161
|
Type: `number`
|
|
899
1162
|
|
|
900
|
-
The transaction ID
|
|
901
|
-
RocksDB database path, regardless the database name/column family.
|
|
1163
|
+
The transaction ID, a positive integer no greater than `Number.MAX_SAFE_INTEGER`. Transaction IDs
|
|
1164
|
+
are unique to the RocksDB database path, regardless the database name/column family.
|
|
1165
|
+
|
|
1166
|
+
#### `txn.setTimestamp(ts?: number): void`
|
|
902
1167
|
|
|
903
|
-
|
|
1168
|
+
Overrides the transaction timestamp in milliseconds since the Unix epoch. Replication receivers and
|
|
1169
|
+
crash replay can use this to adopt an origin transaction's log key. If called without a timestamp,
|
|
1170
|
+
it claims a fresh monotonic value.
|
|
904
1171
|
|
|
905
|
-
|
|
906
|
-
|
|
1172
|
+
The override must happen while the transaction is pending and before any database write or
|
|
1173
|
+
transaction-log entry is staged. A log batch that has already been written keeps its timestamp
|
|
1174
|
+
across a coordinated retry; reapplying that same timestamp remains idempotent while pending. A
|
|
1175
|
+
supplied value must be finite, positive, and below `8.64e15`.
|
|
1176
|
+
|
|
1177
|
+
rocksdb-js does not define a record's value layout or version metadata. A producer that copies the
|
|
1178
|
+
transaction timestamp into record bytes is responsible for calling `setTimestamp()` before reading
|
|
1179
|
+
and encoding it.
|
|
907
1180
|
|
|
908
1181
|
```typescript
|
|
909
1182
|
await db.transaction(async (txn) => {
|
|
910
|
-
txn.setTimestamp(Date.now()
|
|
1183
|
+
txn.setTimestamp(Date.now());
|
|
911
1184
|
});
|
|
912
1185
|
```
|
|
913
1186
|
|
|
@@ -1055,6 +1328,38 @@ db.on('error', (err) => {
|
|
|
1055
1328
|
});
|
|
1056
1329
|
```
|
|
1057
1330
|
|
|
1331
|
+
### Event: `'writeStall'`
|
|
1332
|
+
|
|
1333
|
+
Emitted when a column family **enters** a RocksDB write-stall — the push signal that writes are being
|
|
1334
|
+
throttled or blocked (for example when `dbWriteBufferSize` is too small for the number of column
|
|
1335
|
+
families and RocksDB thrashes on premature flushes). Listeners receive three string arguments:
|
|
1336
|
+
|
|
1337
|
+
- `columnFamily: string` — the column family that entered the stall (`'default'` for the primary).
|
|
1338
|
+
- `previousCondition: string` — `'normal' | 'delayed' | 'stopped'`.
|
|
1339
|
+
- `currentCondition: string` — `'delayed'` (writes rate-limited) or `'stopped'` (writes blocked until
|
|
1340
|
+
a flush frees a memtable/L0 slot).
|
|
1341
|
+
|
|
1342
|
+
The event is **rising-edge only and rate-limited** per column family. RocksDB oscillates a CF's stall
|
|
1343
|
+
condition many times per second during a sustained stall, so the event fires when a CF _enters_ a
|
|
1344
|
+
stall and then at most once per `ROCKSDB_JS_WRITE_STALL_DEBOUNCE_MS` (default 1000 ms) while it stays
|
|
1345
|
+
stalling — never a flood. **Recovery is not pushed**: reliably distinguishing a brief dip from a real
|
|
1346
|
+
recovery isn't possible without a timer, so for current state use the pull below rather than waiting
|
|
1347
|
+
for a `'normal'` event.
|
|
1348
|
+
|
|
1349
|
+
For "are writes stalled right now?", call [`db.isWriteStalled()`](#dbiswritestalled-boolean) — it
|
|
1350
|
+
reads the live write-controller state and is authoritative. Note it is **database-wide** (the write
|
|
1351
|
+
controller is shared across all column families), so it answers "is anything stalling writes" while
|
|
1352
|
+
the event names _which_ CF; a listener attached mid-stall should consult it rather than expect a
|
|
1353
|
+
missed rising edge.
|
|
1354
|
+
|
|
1355
|
+
```typescript
|
|
1356
|
+
db.on('writeStall', (cf, prev, cur) => {
|
|
1357
|
+
console.warn(`RocksDB write stall entered on ${cf}: ${prev} -> ${cur}`);
|
|
1358
|
+
});
|
|
1359
|
+
// ...and to check the current state on demand:
|
|
1360
|
+
if (db.isWriteStalled()) console.warn('writes are currently throttled or blocked');
|
|
1361
|
+
```
|
|
1362
|
+
|
|
1058
1363
|
## Event API
|
|
1059
1364
|
|
|
1060
1365
|
`rocksdb-js` provides a EventEmitter-like API that lets you asynchronously notify events to one or
|
|
@@ -1195,8 +1500,11 @@ Column family and ticker stat values are 64-bit unsigned integers and histogram
|
|
|
1195
1500
|
`StatsHistogramData` objects.
|
|
1196
1501
|
|
|
1197
1502
|
The result also always includes a summarized, aggregate set of `txnlog.*` keys covering all of
|
|
1198
|
-
the database's transaction logs
|
|
1199
|
-
|
|
1503
|
+
the database's transaction logs, and a set of `writeBufferManager.*` keys describing the
|
|
1504
|
+
process-wide `WriteBufferManager`
|
|
1505
|
+
([`getWriteBufferManagerStats()`](#getwritebuffermanagerstats-writebuffermanagerstats)
|
|
1506
|
+
carries the same values plus its column-family inventory). Both sets are present regardless of
|
|
1507
|
+
whether statistics are enabled and can be fetched individually with `db.getStat()`. For detailed, per-log statistics —
|
|
1200
1508
|
including memory-map usage — use [`log.getStats()`](#loggetstats-transactionlogstats). All stat
|
|
1201
1509
|
names are documented in [docs/stats.md](docs/stats.md).
|
|
1202
1510
|
|
|
@@ -1503,6 +1811,14 @@ holds the last-known version for that key. Because slots are addressed by a hash
|
|
|
1503
1811
|
share a slot; a collision only ever causes a conservative miss (a real read), never a stale value to
|
|
1504
1812
|
be treated as fresh.
|
|
1505
1813
|
|
|
1814
|
+
This first word is the only version the table derives on its own: it is what a read with
|
|
1815
|
+
`populateVersion: true` publishes and what a transaction write invalidates against. A producer whose
|
|
1816
|
+
record format also carries a separately assigned version elsewhere in the value must keep using the
|
|
1817
|
+
first word on both sides of the check — as `expectedVersion`, and as the argument to any explicit
|
|
1818
|
+
[`db.populateVersion()`](#dbpopulateversionkey-key-version-number-void) call, which publishes
|
|
1819
|
+
whatever it is given. Comparing against the other value would miss the fast path, and publishing it
|
|
1820
|
+
would make the slot disagree with what the next write invalidates.
|
|
1821
|
+
|
|
1506
1822
|
The freshness check works as follows:
|
|
1507
1823
|
|
|
1508
1824
|
1. Pass `{ expectedVersion }` to `get()` / `getSync()`. If the slot currently records that version,
|
|
@@ -1620,9 +1936,19 @@ const names = db.listLogs();
|
|
|
1620
1936
|
### `db.purgeLogs({ includeEntryCounts: true, ...options }): { path: string; entries: number }[]`
|
|
1621
1937
|
|
|
1622
1938
|
Deletes transaction log files older than the `transactionLogRetention` (defaults to 3 days).
|
|
1939
|
+
Ordinary retention keeps the sequence file named by `txn.state` and every newer file as the live
|
|
1940
|
+
store's retention floor, or the highest sequence file when there is no persisted flush position. It
|
|
1941
|
+
removes only an eligible contiguous prefix below that floor. An idle store can therefore retain one
|
|
1942
|
+
file past the cutoff until a later write rotates and flushes it; that extra file is bounded by the
|
|
1943
|
+
store's `transactionLogMaxSize` (except when a single transaction exceeds the target). That bound
|
|
1944
|
+
covers only the floor file: when the flush position lags — `txn.state` stuck at an old sequence
|
|
1945
|
+
because RocksDB flushing is behind — every file above the floor is retained too, which
|
|
1946
|
+
`transactionLogMaxSize` does not bound. `purge.retainedUnflushedFiles` reports that case. Use
|
|
1947
|
+
`destroy: true` only to remove the store itself.
|
|
1623
1948
|
|
|
1624
1949
|
- `options: object`
|
|
1625
|
-
- `before?: number` Remove
|
|
1950
|
+
- `before?: number` Remove transaction log files older than the specified timestamp, subject to
|
|
1951
|
+
the retention floor and contiguous-prefix rules above.
|
|
1626
1952
|
- `destroy?: boolean` When `true`, deletes transaction log stores including all log sequence files
|
|
1627
1953
|
on disk.
|
|
1628
1954
|
- `includeEntryCounts?: boolean` When `true`, counts the entries in each deleted log file and
|
|
@@ -1796,7 +2122,9 @@ stats.totals.transactionsWritten; // lifetime count of transactions written
|
|
|
1796
2122
|
|
|
1797
2123
|
The `purge.retainedUnflushedFiles` gauge is useful for diagnosing why logs are not being cleaned
|
|
1798
2124
|
up: a file can be older than the retention period but still retained because its transactions have
|
|
1799
|
-
not yet been flushed to RocksDB (purging it would be unsafe for crash recovery).
|
|
2125
|
+
not yet been flushed to RocksDB (purging it would be unsafe for crash recovery). The retention
|
|
2126
|
+
floor — the sequence named by `txn.state`, or the highest sequence when there is no persisted flush
|
|
2127
|
+
position — is not counted as purgeable even when it is old and fully flushed.
|
|
1800
2128
|
|
|
1801
2129
|
### Transaction Log Initialization
|
|
1802
2130
|
|
|
@@ -2275,7 +2603,9 @@ Options for `get()`, `getSync()`, and the `getBinary*` methods.
|
|
|
2275
2603
|
- `tailing: boolean` When `true`, creates a "tailing iterator" which is a special iterator that
|
|
2276
2604
|
has a view of the complete database including newly added data and is optimized for sequential
|
|
2277
2605
|
reads. This will return records that were inserted into the database after the creation of the
|
|
2278
|
-
iterator. Defaults to `false`.
|
|
2606
|
+
iterator. Defaults to `false`. A tailing iterator ignores a transaction's snapshot: through a
|
|
2607
|
+
transaction it still reads the latest committed state, merged with that transaction's staged
|
|
2608
|
+
writes.
|
|
2279
2609
|
|
|
2280
2610
|
### `RangeOptions`
|
|
2281
2611
|
|