@harperfast/rocksdb-js 2.12.0 → 2.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +375 -49
- package/dist/index.cjs +587 -158
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +276 -58
- package/dist/index.d.mts +276 -58
- package/dist/index.mjs +583 -159
- package/dist/index.mjs.map +1 -1
- package/docs/performance/mutex-contention.md +4 -3
- package/docs/performance/transaction-state-2026-11-09.csv +28 -0
- package/docs/performance/transaction-state.md +67 -0
- package/docs/stats.md +4 -0
- package/docs/steady-clock-design.md +1 -1
- package/docs/transaction-log.md +39 -7
- package/package.json +19 -19
package/README.md
CHANGED
|
@@ -46,6 +46,32 @@ Creates a new database instance.
|
|
|
46
46
|
- `path: string` The path to write the database files to. This path does not need to exist, but the
|
|
47
47
|
parent directories do.
|
|
48
48
|
- `options: object` [optional]
|
|
49
|
+
- `arenaBlockSize: number` The size in bytes of the blocks a column family's memtable allocates
|
|
50
|
+
from. Every column family that has been written to holds at least one block, and with a
|
|
51
|
+
`writeBufferManagerSize` set, those blocks count against that shared budget even when the
|
|
52
|
+
column family is idle — so with many column families, large blocks can use up most of a small
|
|
53
|
+
budget and cause constant tiny flushes. Defaults to `0`, which picks a size automatically: with
|
|
54
|
+
a write buffer manager, `writeBufferManagerSize / 2048`, but at least 64 KiB and at most
|
|
55
|
+
`writeBufferSize / 8` or 1 MiB, whichever is smaller (a 64 MiB budget gives 64 KiB, 512 MiB
|
|
56
|
+
gives 256 KiB, 2 GiB or more gives 1 MiB); without one, RocksDB's own default of
|
|
57
|
+
`writeBufferSize / 8` capped at 1 MiB (1 MiB at the default `writeBufferSize`). The automatic
|
|
58
|
+
size uses the manager's budget at the time the column family is opened. An explicit value is
|
|
59
|
+
used as given, except that RocksDB raises one below 4 KiB to 4 KiB. Like `writeBufferSize`,
|
|
60
|
+
this applies to every column family when a database is first opened in the process and to
|
|
61
|
+
column families created later; a column family that is already open keeps its size.
|
|
62
|
+
- `bloomFilterBitsPerKey: number` The size of the filter written into each SST file, in bits per
|
|
63
|
+
key. A `get()` of a key that is not in a file checks that file's filter and skips its index and
|
|
64
|
+
data blocks, so reads of absent keys, and of keys whose range overlaps several files (keys
|
|
65
|
+
written in random order, or updates still in level 0), touch fewer blocks. Filters cover whole
|
|
66
|
+
keys only, so range reads are unaffected. Defaults to `10` (about 1% false positives); otherwise
|
|
67
|
+
a number from 1 to 100, fractions allowed, or `0` for no filters. Each open SST file holds its
|
|
68
|
+
filter in memory beside its index, outside the block cache (about 1.25 bytes per key at `10`;
|
|
69
|
+
a `secondaryPath` open holds every file open, so the whole database's filters). `0` also stops
|
|
70
|
+
an open from loading the filters files already have, so a reader can trade them for memory. An existing
|
|
71
|
+
database opens with the new setting, but its files gain or lose filters only as compaction
|
|
72
|
+
rewrites them (`compact({ bottommost: true })` rewrites them all). Like `writeBufferSize`, this
|
|
73
|
+
applies to every column family when a database is first opened in the process and to column
|
|
74
|
+
families created later; a column family that is already open keeps its setting.
|
|
49
75
|
- `compression: string | { algorithm: string, level?: number }` The block/blob compression
|
|
50
76
|
algorithm for this column family. Pass an algorithm name — one of `'none'`, `'snappy'`,
|
|
51
77
|
`'zlib'`, `'bzip2'`, `'lz4'`, `'lz4hc'`, or `'zstd'` — or an object with an
|
|
@@ -119,9 +145,10 @@ Creates a new database instance.
|
|
|
119
145
|
- `readOnly: boolean` When `true`, the database is opened in read-only mode. Read operations are
|
|
120
146
|
permitted. Write operations will throw an error with code `ERR_DATABASE_READONLY`. Transactions
|
|
121
147
|
are a no-op in read-only mode. [`flush()`](#dbflushoptions-promisevoid),
|
|
122
|
-
[`flushSync()`](#dbflushsyncoptions-void), [`compact()`](#dbcompactoptions-promisevoid)
|
|
123
|
-
[`compactSync()`](#dbcompactsyncoptions-void) are the
|
|
124
|
-
there are no writes, so they succeed as no-ops rather
|
|
148
|
+
[`flushSync()`](#dbflushsyncoptions-void), [`compact()`](#dbcompactoptions-promisevoid),
|
|
149
|
+
[`compactSync()`](#dbcompactsyncoptions-void) and [`syncWal()`](#dbsyncwal-promisevoid) are the
|
|
150
|
+
exception: they have nothing to do when there are no writes, so they succeed as no-ops rather
|
|
151
|
+
than throwing.
|
|
125
152
|
|
|
126
153
|
A read-only open is a **point-in-time snapshot**: it never sees later writes. It is safe
|
|
127
154
|
against a quiescent database, but **unsafe against a live writer**: the open holds no reference
|
|
@@ -188,11 +215,14 @@ Creates a new database instance.
|
|
|
188
215
|
nonempty directory that reappears before first use is refused until every handle closes and the
|
|
189
216
|
path reopens with this option. The name must be a single path component that does not start with
|
|
190
217
|
`.`: startup discovery skips such directories, so the log could never be scanned again.
|
|
191
|
-
- `verificationTable: boolean` When `true`, this
|
|
192
|
-
[Verification Table](#verification-table):
|
|
193
|
-
|
|
194
|
-
are cached (e.g. the primary column family of a table). Defaults to `false
|
|
195
|
-
|
|
218
|
+
- `verificationTable: boolean` When `true`, this handle participates in the process-global
|
|
219
|
+
[Verification Table](#verification-table): writes through it invalidate the verification slot
|
|
220
|
+
for each written key, and reads through it may seed slots. Enable this only for column families
|
|
221
|
+
whose records are cached (e.g. the primary column family of a table). Defaults to `false`; a
|
|
222
|
+
handle that leaves it unset joins a column family another handle opened with `true`, and an
|
|
223
|
+
explicit `false` throws while any handle on that column family participates (see
|
|
224
|
+
[Verification Table](#verification-table)). Requires `verificationTableEntries` to be configured
|
|
225
|
+
before the first database is opened.
|
|
196
226
|
- `writeBufferSize: number` The per-column-family memtable size in bytes at which the memtable is
|
|
197
227
|
sealed and flushed to an SST file. Smaller values produce more frequent, faster flushes; larger
|
|
198
228
|
values batch more writes per SST file at the cost of memory. Defaults to `16777216` (16 MB).
|
|
@@ -200,13 +230,16 @@ Creates a new database instance.
|
|
|
200
230
|
### `db.close()`
|
|
201
231
|
|
|
202
232
|
Closes a database. This function can be called multiple times and will only close an opened
|
|
203
|
-
database. A database instance can be reopened once it is closed.
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
233
|
+
database. A database instance can be reopened once it is closed. Closing the last handle flushes the
|
|
234
|
+
memtables and waits for that flush, then cancels background compaction instead of waiting for it:
|
|
235
|
+
a running compaction is abandoned and redone later, so close does not wait for the compaction
|
|
236
|
+
backlog (the flush itself still waits out a write stall). A flush failure leaves the native database quarantined so `shutdown()`
|
|
237
|
+
can retry without losing unflushed data; an explicit `destroy()` can instead delete it. A failure
|
|
238
|
+
from RocksDB's own final close is reported after native teardown completes; the optional
|
|
239
|
+
`compactOnClose` pass still runs to completion before background work is cancelled, but it is
|
|
240
|
+
best-effort and its errors do not fail the close, since a skipped compaction loses no data. All
|
|
241
|
+
native close errors emit `database:closeFailed`. The quarantine applies to both writable and
|
|
242
|
+
read-only opens because both modes share the physical path lifecycle.
|
|
210
243
|
|
|
211
244
|
```typescript
|
|
212
245
|
const db = RocksDatabase.open('foo');
|
|
@@ -276,6 +309,21 @@ Sets global database settings.
|
|
|
276
309
|
callers could already produce it. With `1`, a database's async commits run one at a time in
|
|
277
310
|
the order `commit()` was called. Databases already open keep their limit.
|
|
278
311
|
- `compactOnClose: boolean` When `true`, compacts the database on close. Defaults to `false`.
|
|
312
|
+
- `shutdownOnExit: boolean` When `true`, the main thread runs [`shutdown()`](#shutdown-void)
|
|
313
|
+
from a `process.on('exit')` listener, so `process.exit()` still flushes and closes every
|
|
314
|
+
database. Defaults to `true`. Set to `false` to leave memtables unflushed at a Node
|
|
315
|
+
`process.exit()`, e.g. to exercise crash recovery.
|
|
316
|
+
- `transactionLogAbortMarkers: boolean` When `true`, a transaction that wrote its
|
|
317
|
+
transaction-log batch and then ends without committing (an `abort()`, a commit error it gives
|
|
318
|
+
up on, or a worker torn down with it pending) gets that batch marked aborted in place, and every
|
|
319
|
+
reader skips it; see [Abort markers](#abort-markers). Defaults to `false`, which behaves as
|
|
320
|
+
before. Read when a transaction starts and when it is reset for a retry.
|
|
321
|
+
- `transactionLogWriteIntents: boolean` When `true` with `transactionLogAbortMarkers` on, a
|
|
322
|
+
conflicting attempt is refused before it writes its batch when an older transaction holds a
|
|
323
|
+
write intent on one of its keys; see [Abort markers](#abort-markers). This shortens the worst
|
|
324
|
+
waits of transactions that overlap heavily, but adds retries and costs throughput when
|
|
325
|
+
transactions are large or contention is low. Defaults to `false`. Needs `verificationTable`.
|
|
326
|
+
Read with `transactionLogAbortMarkers`.
|
|
279
327
|
- `occValidation: 'parallel' | 'serial'` How writable optimistic databases opened after the call
|
|
280
328
|
validate commits for conflicts. Defaults to `'parallel'` (RocksDB's default). Databases already
|
|
281
329
|
open keep their policy. Pessimistic, read-only and secondary opens are unaffected.
|
|
@@ -328,6 +376,19 @@ Sets global database settings.
|
|
|
328
376
|
[`db.isWriteStalled()`](#dbiswritestalled-boolean) — see
|
|
329
377
|
[`getWriteBufferManagerStats()`](#getwritebuffermanagerstats-writebuffermanagerstats),
|
|
330
378
|
which also describes the watchdog that logs a sustained stall.
|
|
379
|
+
- `writeBufferManagerFlushPolicy: 'oldest' | 'largest' | 'largestAcrossDBs'` What the write
|
|
380
|
+
buffer manager flushes when it is over budget. `'oldest'` flushes the oldest memtable of the
|
|
381
|
+
database that is being written; `'largest'` its largest memtable (databases here are opened with
|
|
382
|
+
atomic flush, so this currently behaves like `'oldest'`); `'largestAcrossDBs'` has a background
|
|
383
|
+
thread flush whichever database holds the most memtable memory, whether or not it is being
|
|
384
|
+
written. When not set, the policy is `'largestAcrossDBs'` while
|
|
385
|
+
`writeBufferManagerAllowStall` is `true` and `'oldest'` otherwise, and it follows later changes
|
|
386
|
+
to `writeBufferManagerAllowStall`. With stalls allowed, `'oldest'` can stop writes permanently:
|
|
387
|
+
a write to one database waits for memory held by other databases that nothing is writing to,
|
|
388
|
+
so nothing flushes them. Without stalls, `'largestAcrossDBs'` has measured slower. Can be
|
|
389
|
+
changed while databases are open. Once set, the value stays in effect for the life of the
|
|
390
|
+
process; it is reported as `flushPolicy` by
|
|
391
|
+
[`getWriteBufferManagerStats()`](#getwritebuffermanagerstats-writebuffermanagerstats).
|
|
331
392
|
- `writeBufferManagerCostToCache: boolean` When `true`, memtable memory is "charged" against the
|
|
332
393
|
shared block cache so the block cache and write buffers draw from a single pool. During write
|
|
333
394
|
bursts the cache shrinks to make room for memtables; once memtables flush, the cache can grow
|
|
@@ -370,6 +431,8 @@ where the call was made.
|
|
|
370
431
|
- `mutableMemoryUsage: number` The share of `memoryUsage` held by active (mutable) memtables; the
|
|
371
432
|
rest is memtables awaiting flush plus retained write history.
|
|
372
433
|
- `allowStall: boolean`, `costToCache: boolean` The manager's configuration.
|
|
434
|
+
- `flushPolicy: 'oldest' | 'largest' | 'largestAcrossDBs'` The flush policy in effect (see
|
|
435
|
+
`writeBufferManagerFlushPolicy`); before a manager exists, the policy one would be created with.
|
|
373
436
|
- `stallActive: boolean` Whether the manager is currently stalling writes.
|
|
374
437
|
- `stallActiveMs: number` How long the current stall has been active; `0` when not stalled.
|
|
375
438
|
Sampled once a second by the watchdog, so `0` for a stall's first second and whenever
|
|
@@ -388,7 +451,7 @@ where the call was made.
|
|
|
388
451
|
|
|
389
452
|
`bufferSize`, `memoryUsage`, `mutableMemoryUsage`, `stallActive` and `stallActiveMs` are also in
|
|
390
453
|
[`db.getStats()`](#dbgetstatsall-boolean-rocksdbstats) and `db.getStat()` under the same
|
|
391
|
-
`writeBufferManager.` prefix, for scraping; `enabled`, `allowStall`, `costToCache`,
|
|
454
|
+
`writeBufferManager.` prefix, for scraping; `enabled`, `allowStall`, `costToCache`, `flushPolicy`,
|
|
392
455
|
`watchdogRunning`, and the column-family inventory are only here, because collecting them either
|
|
393
456
|
walks the database registry or isn't scrape-shaped.
|
|
394
457
|
|
|
@@ -674,7 +737,11 @@ because releasing the native database beneath an active copy would be unsafe.
|
|
|
674
737
|
A previously opened instance does not need to remain open, which allows an explicit `destroy()`
|
|
675
738
|
retry after failed physical cleanup. A never-opened or read-only instance cannot destroy the
|
|
676
739
|
database. `shutdown()` reports a pending cleanup tombstone but never retries deletion; only an
|
|
677
|
-
explicit `destroy()` can remove the path.
|
|
740
|
+
explicit `destroy()` can remove the path. Destruction flushes the memtables before it closes the
|
|
741
|
+
native database, without waiting out a write stall, so a database whose files outlive the call (the
|
|
742
|
+
deletion fails, or the process dies first) keeps writes made with `disableWAL`. If that flush fails,
|
|
743
|
+
destruction still deletes the database and reports the flush error through `database:closeFailed`;
|
|
744
|
+
only when the deletion then fails too are the unflushed `disableWAL` writes lost.
|
|
678
745
|
|
|
679
746
|
```typescript
|
|
680
747
|
db.destroy();
|
|
@@ -806,6 +873,22 @@ db.flushSync({ allowWriteStall: true });
|
|
|
806
873
|
On a [read-only](#new-rocksdatabasepath-options) database it validates its options and then returns
|
|
807
874
|
without flushing, rather than throwing.
|
|
808
875
|
|
|
876
|
+
### `db.syncWal(): Promise<void>`
|
|
877
|
+
|
|
878
|
+
Syncs the write-ahead log to stable storage (`DB::SyncWAL`), so every WAL-backed write committed
|
|
879
|
+
before the call survives a power loss afterwards. Unlike [`flush()`](#dbflushoptions-promisevoid) it
|
|
880
|
+
writes no SST file and never stalls writers. Writes that skipped the WAL (`disableWAL`) and
|
|
881
|
+
transaction-log files, which keep their own durability, are not covered. RocksDB serializes
|
|
882
|
+
concurrent syncs of one WAL, so keep one in flight per database rather than queueing them on the
|
|
883
|
+
libuv threadpool.
|
|
884
|
+
|
|
885
|
+
```typescript
|
|
886
|
+
await db.syncWal();
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
On a [read-only](#new-rocksdatabasepath-options) database the returned promise resolves without
|
|
890
|
+
syncing.
|
|
891
|
+
|
|
809
892
|
### `db.get(key: Key, options?: GetOptions): MaybePromise<any>`
|
|
810
893
|
|
|
811
894
|
Retreives the value for a given key. If the key does not exist, it will resolve `undefined`.
|
|
@@ -964,7 +1047,9 @@ again, so a backward step between runs can reissue a timestamp that is already a
|
|
|
964
1047
|
node's transaction log. Opening with
|
|
965
1048
|
[`timestampFloorLog`](#new-rocksdatabasepath-options) raises the clock
|
|
966
1049
|
above every batch key still durable in the named log before the database handle is returned, so no
|
|
967
|
-
transaction can be constructed below it.
|
|
1050
|
+
transaction can be constructed below it. Keys in segments retention has purged are not durable, and
|
|
1051
|
+
neither is a bound advertised to peers; carry those across a restart yourself with
|
|
1052
|
+
[`raiseTimestampFloor()`](#raisetimestampfloortimestamp-number-boolean).
|
|
968
1053
|
|
|
969
1054
|
Name only a log this process **originates**. A log written under a timestamp adopted from another
|
|
970
1055
|
node — a replication receiver calling [`txn.setTimestamp()`](#txnsettimestampts-number-void) — is
|
|
@@ -1250,7 +1335,9 @@ The `attempt` parameter is the number of times the transaction has been retried.
|
|
|
1250
1335
|
- `disableSnapshot?: boolean` Whether to disable snapshots. Defaults to `false`.
|
|
1251
1336
|
- `maxRetries?: number` The maximum number of times to retry the transaction. Defaults to `3`.
|
|
1252
1337
|
- `retryOnBusy?: boolean` Whether to retry the transaction if the commit fails with `IsBusy`.
|
|
1253
|
-
Defaults to `true` when the transaction
|
|
1338
|
+
Defaults to `true` when the transaction wrote a transaction-log batch or was refused before
|
|
1339
|
+
writing one (`refusedBeforeLog`), otherwise `false`. With `false`, `db.transaction()` also
|
|
1340
|
+
commits without `coordinatedRetry`, so a conflict fails at once instead of waiting first.
|
|
1254
1341
|
|
|
1255
1342
|
### Transaction Retry Logic
|
|
1256
1343
|
|
|
@@ -1263,7 +1350,45 @@ a bad state and the transaction will need to be retried. If the max retries is r
|
|
|
1263
1350
|
transaction is not retried, a `ERR_TRANSACTION_ABANDONED` error will be thrown.
|
|
1264
1351
|
|
|
1265
1352
|
Users should use the `attempt` transaction callback parameter to ensure duplicate transaction log
|
|
1266
|
-
entries are not added.
|
|
1353
|
+
entries are not added. `txn.logWritten()` reports whether the batch is already in the log: a commit
|
|
1354
|
+
that conflicts after writing it keeps it for the retry, while an attempt refused before writing it
|
|
1355
|
+
(`err.refusedBeforeLog`, with `transactionLogAbortMarkers`) leaves nothing and must stage it again.
|
|
1356
|
+
|
|
1357
|
+
### Abort markers
|
|
1358
|
+
|
|
1359
|
+
With `RocksDatabase.config({ transactionLogAbortMarkers: true })`, a transaction's log batch is
|
|
1360
|
+
never left live when the transaction does not commit:
|
|
1361
|
+
|
|
1362
|
+
- A transaction that wrote its batch and then ends without committing has the batch marked aborted:
|
|
1363
|
+
bit `0x02` (`TRANSACTION_LOG_ENTRY_ABORTED_FLAG`) is set on its first entry, and the change is
|
|
1364
|
+
synced to disk before `abort()` returns or the batch stops holding back committed readers. Every
|
|
1365
|
+
reader skips the batch. `abort()` then no longer throws `ERR_TRANSACTION_ABANDONED`.
|
|
1366
|
+
- If the marker cannot be written or synced, `abort()` throws `ERR_TRANSACTION_ABORT_UNRESOLVED`
|
|
1367
|
+
(again on a second call), committed readers stay behind the batch, and the log refuses new batches
|
|
1368
|
+
until a retry on the next batch, database flush or close marks it.
|
|
1369
|
+
- Under `occValidation: 'parallel'` (the default), a retried optimistic commit that RocksDB's own
|
|
1370
|
+
conflict check shows would fail is refused before it writes its batch too, with
|
|
1371
|
+
`refusedBeforeLog` set (needs the RocksDB build this package pins). A first attempt skips the
|
|
1372
|
+
check, so commits that do not conflict pay nothing for it, and so does a retry whose batch an
|
|
1373
|
+
earlier attempt already wrote.
|
|
1374
|
+
- With `transactionLogWriteIntents: true` as well, a commit that finds an older transaction holding
|
|
1375
|
+
a write intent on one of its keys (with `verificationTable: true`) is refused before writing its
|
|
1376
|
+
batch, with `refusedBeforeLog` set on the
|
|
1377
|
+
error, or, if its batch is already written from an earlier attempt, before committing. With
|
|
1378
|
+
`coordinatedRetry` it waits for that transaction first. `commitSync()` never waits: it throws, and
|
|
1379
|
+
`transactionSync()` retries after a short backoff.
|
|
1380
|
+
- `txn.setParkDeadline(epochMs)` bounds those waits by the caller's own deadline.
|
|
1381
|
+
- `log.getStats().totals` reports `abortsMarked`, `abortMarkFailures`, and `watermarkPinnedUs` /
|
|
1382
|
+
`pinnedTransactions`: how long logged batches held committed readers back after their first
|
|
1383
|
+
conflict, until they committed or ended.
|
|
1384
|
+
- A backup waits for every batch it captures that has neither committed nor been aborted, and is
|
|
1385
|
+
refused if one is still unresolved after `ROCKSDB_JS_BACKUP_UNRESOLVED_WAIT_MS` (default
|
|
1386
|
+
`30000`).
|
|
1387
|
+
|
|
1388
|
+
A reader older than the release that added aborted-batch support shows an aborted batch as live, so
|
|
1389
|
+
turn this on only once every reader of the logs (replicas, restores, repair tools) has it. A batch
|
|
1390
|
+
whose transaction was still unresolved when the process crashed is not marked, and is replayed as
|
|
1391
|
+
before.
|
|
1267
1392
|
|
|
1268
1393
|
When `coordinatedRetry: true`, the retry behavior changes for `IsBusy` conflicts: instead of
|
|
1269
1394
|
rejecting (or retrying immediately and potentially conflicting again), the commit waits until the
|
|
@@ -1297,6 +1422,12 @@ operations methods as the `RocksDatabase` instance plus:
|
|
|
1297
1422
|
- `txn.commitSync()` Synchronously commits and closes the transaction.
|
|
1298
1423
|
- `txn.getTimestamp(): number` Retrieves the transaction timestamp in milliseconds as a decimal. It
|
|
1299
1424
|
defaults to a process-wide monotonic value assigned when the transaction was created.
|
|
1425
|
+
- `txn.lastConflictKind(): number` Why the most recent attempt was refused or retried, one of
|
|
1426
|
+
`ConflictKind`: `none` (0), `intentBeforeLog` (1), `intentAfterLog` (2), `atCommit` (3),
|
|
1427
|
+
`checkBeforeLog` (4).
|
|
1428
|
+
- `txn.logWritten(): boolean` Whether the transaction's batch is in the transaction log.
|
|
1429
|
+
- `txn.setParkDeadline(epochMs: number): void` Past this time a coordinated-retry wait resolves at
|
|
1430
|
+
once. `0` clears it.
|
|
1300
1431
|
- `txn.id: number` The read-only transaction ID. Transaction IDs are unique to the RocksDB database
|
|
1301
1432
|
path, regardless the database name/column family.
|
|
1302
1433
|
- `txn.setTimestamp(ts?: number): void` Overrides the transaction timestamp in milliseconds. If
|
|
@@ -1349,6 +1480,11 @@ Type: `number`
|
|
|
1349
1480
|
The transaction ID, a positive integer no greater than `Number.MAX_SAFE_INTEGER`. Transaction IDs
|
|
1350
1481
|
are unique to the RocksDB database path, regardless the database name/column family.
|
|
1351
1482
|
|
|
1483
|
+
A transaction belongs to the thread that created it. Although every `worker_threads` worker that
|
|
1484
|
+
opens the same path shares one id space, using a transaction by id from another worker (for example
|
|
1485
|
+
`{ transaction }` read options or `log.addEntry(data, txnId)`) throws `ERR_TRANSACTION_WRONG_ENV`:
|
|
1486
|
+
RocksDB transactions are not thread-safe.
|
|
1487
|
+
|
|
1352
1488
|
#### `txn.setTimestamp(ts?: number): void`
|
|
1353
1489
|
|
|
1354
1490
|
Overrides the transaction timestamp in milliseconds since the Unix epoch. Replication receivers and
|
|
@@ -1937,6 +2073,10 @@ Attempts to acquire a lock for a given key. If the lock is available, the functi
|
|
|
1937
2073
|
and the optional `onUnlocked` callback is never called. If the lock is not available, the function
|
|
1938
2074
|
returns `false` and the `onUnlocked` callback is queued until the lock is released.
|
|
1939
2075
|
|
|
2076
|
+
A queued `onUnlocked` callback keeps the thread's event loop alive until it is called, even when the
|
|
2077
|
+
lock is held by another thread. A thread with a callback queued on a lock that is never released
|
|
2078
|
+
will not exit on its own; closing the database that queued the callback does not cancel it.
|
|
2079
|
+
|
|
1940
2080
|
When a database is closed, all locks associated to it will be unlocked.
|
|
1941
2081
|
|
|
1942
2082
|
```typescript
|
|
@@ -1977,7 +2117,9 @@ db.unlock('foo'); // false, already unlocked
|
|
|
1977
2117
|
|
|
1978
2118
|
### `db.withLock(key: Key, callback: () => void | Promise<void>): Promise<void>`
|
|
1979
2119
|
|
|
1980
|
-
Runs a function with guaranteed exclusive access across all threads.
|
|
2120
|
+
Runs a function with guaranteed exclusive access across all threads. The returned promise rejects
|
|
2121
|
+
with the error if the callback throws or its promise rejects; the lock is released either way. A
|
|
2122
|
+
pending `withLock()` keeps the thread's event loop alive until its callback has run.
|
|
1981
2123
|
|
|
1982
2124
|
```typescript
|
|
1983
2125
|
await db.withLock('key', async () => {
|
|
@@ -2005,9 +2147,7 @@ await Promise.all([
|
|
|
2005
2147
|
]);
|
|
2006
2148
|
```
|
|
2007
2149
|
|
|
2008
|
-
Note:
|
|
2009
|
-
`--force-node-api-uncaught-exceptions-policy` flag which will cause errors to emit the
|
|
2010
|
-
`'uncaughtException'` event. Future Node.js releases will enable this flag by default.
|
|
2150
|
+
Note: Errors thrown by a `tryLock()` `onUnlocked` callback are discarded.
|
|
2011
2151
|
|
|
2012
2152
|
## File Locking
|
|
2013
2153
|
|
|
@@ -2084,10 +2224,12 @@ The freshness check works as follows:
|
|
|
2084
2224
|
2. On a cold read, pass `{ populateVersion: true }` (or call `db.populateVersion()` afterward) to
|
|
2085
2225
|
seed the slot with the version extracted from the value, so subsequent freshness checks hit.
|
|
2086
2226
|
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2227
|
+
Writes through a handle opened with `verificationTable: true` invalidate the slot for each written
|
|
2228
|
+
key at write time, so a stale version cannot survive them, and a read seeds a slot only through
|
|
2229
|
+
such a handle. Once one handle opens a column family with the option, the handles on it that left
|
|
2230
|
+
the option unset participate too (see below). Invalidation is also what enables
|
|
2231
|
+
[`coordinatedRetry`](#transactionoptions): a conflicting transaction parks on the slot and retries
|
|
2232
|
+
once the write intent is released.
|
|
2091
2233
|
|
|
2092
2234
|
The table is **process-global** and backed by a single shared structure, so versions populated on
|
|
2093
2235
|
the main thread are visible to `worker_threads` workers and vice versa.
|
|
@@ -2096,7 +2238,8 @@ the main thread are visible to `worker_threads` workers and vice versa.
|
|
|
2096
2238
|
|
|
2097
2239
|
The table must be sized before the first database is opened, via the
|
|
2098
2240
|
[`verificationTableEntries`](#dbconfigoptions) config option (default `131072` slots = 1 MB; set to
|
|
2099
|
-
`0` to disable). Then
|
|
2241
|
+
`0` to disable). Then open each cached column family with the `verificationTable: true` open
|
|
2242
|
+
option:
|
|
2100
2243
|
|
|
2101
2244
|
```typescript
|
|
2102
2245
|
import { RocksDatabase } from '@harperfast/rocksdb-js';
|
|
@@ -2109,6 +2252,18 @@ const db = RocksDatabase.open('path/to/db', { verificationTable: true });
|
|
|
2109
2252
|
Enable `verificationTable` only for column families whose records are cached (e.g. the primary
|
|
2110
2253
|
column family of a table); enabling it adds per-write slot invalidation overhead.
|
|
2111
2254
|
|
|
2255
|
+
Participation applies to the whole column family within the process. Once a handle opens a column
|
|
2256
|
+
family with `verificationTable: true`, every later handle on it participates too, even one opened
|
|
2257
|
+
without the option, so a write through any handle invalidates its key's slot. This lasts until
|
|
2258
|
+
every handle on the database closes, or until the column family is opened with an explicit
|
|
2259
|
+
`verificationTable: false` after every participating handle on it has closed. While any handle on
|
|
2260
|
+
it participates, that explicit `false` throws `ERR_VERIFICATION_TABLE_CONFLICT`. Opening with
|
|
2261
|
+
`verificationTable: true` throws `ERR_VERIFICATION_TABLE_CONFLICT` while a non-participating handle
|
|
2262
|
+
is open on that column family: one opened before any opt-in, or with an explicit `false`. After
|
|
2263
|
+
such a handle closes, the same error is thrown while a transaction that existed before it closed
|
|
2264
|
+
is still pending, or while another operation on the database is running; retry the open once they
|
|
2265
|
+
finish.
|
|
2266
|
+
|
|
2112
2267
|
### `db.verifyVersion(key: Key, version: number): boolean`
|
|
2113
2268
|
|
|
2114
2269
|
Returns `true` when the verification table currently records `version` for `key` (in this database
|
|
@@ -2209,7 +2364,10 @@ every file after it is retained regardless of age; `purge.retainedUnflushedFiles
|
|
|
2209
2364
|
While a backup with `transactionLogs` is copying a store's files, purges of that store are skipped
|
|
2210
2365
|
(`purge.retentionPins` reports it). On a filesystem that rejects a directory fsync, the store's last
|
|
2211
2366
|
file is kept, since `txn.state` could not be made durable before deleting it.
|
|
2212
|
-
|
|
2367
|
+
Before deleting any file, the purge records the largest key it held in the log's purge watermark
|
|
2368
|
+
([`log.getPurgeWatermark()`](#loggetpurgewatermark-transactionlogpurgewatermark)) and makes that
|
|
2369
|
+
durable. Use `destroy: true` only to remove the store itself; a log recreated after it starts a new
|
|
2370
|
+
watermark history.
|
|
2213
2371
|
|
|
2214
2372
|
- `options: object`
|
|
2215
2373
|
- `before?: number` Remove transaction log files older than the specified timestamp, subject to
|
|
@@ -2269,6 +2427,8 @@ to disk right before the transaction is committed. You may add multiple enties p
|
|
|
2269
2427
|
underlying architecture is thread safe.
|
|
2270
2428
|
|
|
2271
2429
|
- `log.addEntry()`
|
|
2430
|
+
- `log.equals()`
|
|
2431
|
+
- `log.getPurgeWatermark()`
|
|
2272
2432
|
- `log.path`
|
|
2273
2433
|
- `log.query()`
|
|
2274
2434
|
|
|
@@ -2301,6 +2461,57 @@ Note that the `TransactionLog` class also has internal methods `_getMemoryMapOfF
|
|
|
2301
2461
|
`_findPosition`, and `_getLastCommittedPosition` that should not be used directly and may change in
|
|
2302
2462
|
any version.
|
|
2303
2463
|
|
|
2464
|
+
#### `log.equals(other): boolean`
|
|
2465
|
+
|
|
2466
|
+
Returns `true` when `other` is a `TransactionLog` for the same log: the same database and the same
|
|
2467
|
+
log name. The database is compared by its resolved filesystem identity, so `db.useLog('foo')`,
|
|
2468
|
+
`txn.useLog('foo')` and a log from another handle opened with a different spelling of the same path
|
|
2469
|
+
all compare equal, and two databases opened through the same relative path from different working
|
|
2470
|
+
directories do not. The comparison still works after either database closes. Any other value
|
|
2471
|
+
compares unequal.
|
|
2472
|
+
|
|
2473
|
+
```typescript
|
|
2474
|
+
const log = db.useLog('foo');
|
|
2475
|
+
log.equals(otherHandle.useLog('foo')); // true: same database and name
|
|
2476
|
+
log.equals(db.useLog('bar')); // false
|
|
2477
|
+
```
|
|
2478
|
+
|
|
2479
|
+
A log destroyed with `purgeLogs({ destroy: true })` and recreated under the same name still compares
|
|
2480
|
+
equal to wrappers of the old one.
|
|
2481
|
+
|
|
2482
|
+
#### `log.getPurgeWatermark(): TransactionLogPurgeWatermark`
|
|
2483
|
+
|
|
2484
|
+
```typescript
|
|
2485
|
+
const log = db.useLog('foo');
|
|
2486
|
+
const entries = Array.from(log.query({ start: resumeKey, exclusiveStart: true }));
|
|
2487
|
+
const watermark = log.getPurgeWatermark();
|
|
2488
|
+
const complete = watermark.known && resumeKey >= watermark.purgedThrough;
|
|
2489
|
+
```
|
|
2490
|
+
|
|
2491
|
+
Returns an upper bound on every entry key that retention or `db.purgeLogs()` has deleted from this
|
|
2492
|
+
log, or reports that no such bound can be given. A reader that needs every entry with a key above
|
|
2493
|
+
some position can be served from the log only when that position is at or above `purgedThrough`.
|
|
2494
|
+
|
|
2495
|
+
- `{ known: true, purgedThrough: number }` `purgedThrough` is at or above every key of every file
|
|
2496
|
+
deleted from the log, in whatever order the keys were written (0 when nothing was deleted). It is
|
|
2497
|
+
made durable before each file is deleted, including by the retention that runs when a database
|
|
2498
|
+
opens.
|
|
2499
|
+
- `{ known: false }` The log cannot vouch for what was deleted. This holds permanently for a log
|
|
2500
|
+
created by a release without the watermark, one that file deletions bypassed (such as an older
|
|
2501
|
+
release's retention), and one whose record cannot be read. It is also returned when the log's
|
|
2502
|
+
store no longer exists, such as after `purgeLogs({ destroy: true })` until the next write.
|
|
2503
|
+
|
|
2504
|
+
The call is synchronous. A read-only or secondary database reads the record from disk on each call.
|
|
2505
|
+
The result is a snapshot: a purge can run right after it. To trust what a read returned, read the log
|
|
2506
|
+
first and check the watermark afterwards. See
|
|
2507
|
+
[the purge watermark](docs/transaction-log.md#purge-watermark) for the rules.
|
|
2508
|
+
|
|
2509
|
+
##### `TransactionLogPurgeWatermark`
|
|
2510
|
+
|
|
2511
|
+
```typescript
|
|
2512
|
+
type TransactionLogPurgeWatermark = { known: true; purgedThrough: number } | { known: false };
|
|
2513
|
+
```
|
|
2514
|
+
|
|
2304
2515
|
#### `log.path: string`
|
|
2305
2516
|
|
|
2306
2517
|
Returns the path to the transaction log store files.
|
|
@@ -2310,7 +2521,7 @@ const log = db.useLog('foo');
|
|
|
2310
2521
|
console.log(log.path);
|
|
2311
2522
|
```
|
|
2312
2523
|
|
|
2313
|
-
#### `log.query(options?):
|
|
2524
|
+
#### `log.query(options?): TransactionLogIterator`
|
|
2314
2525
|
|
|
2315
2526
|
Returns an iterable/iterator that streams all log entries for the given filter.
|
|
2316
2527
|
|
|
@@ -2319,6 +2530,11 @@ Returns an iterable/iterator that streams all log entries for the given filter.
|
|
|
2319
2530
|
- `end?: string` The transction end timestamp.
|
|
2320
2531
|
- `exclusiveStart?: boolean` When `true`, this will only match transactions with timestamps after
|
|
2321
2532
|
the start timestamp.
|
|
2533
|
+
- `until?: TransactionLogIterator` Stops before the unread position of another iterator from a log
|
|
2534
|
+
that `equals()` this one, including an entry it has peeked. The bound is exclusive, compares
|
|
2535
|
+
segment and offset before filtering, and works across rotation, even when this query starts past
|
|
2536
|
+
it. It resumes when `until` advances. A closed iterator no longer bounds the query. An invalid or
|
|
2537
|
+
foreign-log iterator throws `TypeError` at query creation.
|
|
2322
2538
|
- `exactStart?: boolean` When `true`, this will only match and iterate starting from a transaction
|
|
2323
2539
|
with the given start timestamp. Once the specified transaction is found, all subsequent
|
|
2324
2540
|
transactions will be returned (regardless of whether their timestamp comes before the `start`
|
|
@@ -2335,6 +2551,32 @@ Returns an iterable/iterator that streams all log entries for the given filter.
|
|
|
2335
2551
|
have been written to the log but are no longer considered committed if they were not flushed to
|
|
2336
2552
|
disk.
|
|
2337
2553
|
|
|
2554
|
+
An unbounded query whose starting segment is temporarily unmappable reports done and retries
|
|
2555
|
+
its original position on the next pull.
|
|
2556
|
+
|
|
2557
|
+
`iterator.peek()` returns the next entry without consuming it. Repeated peeks return the same
|
|
2558
|
+
result until `next()` consumes it once. A `done` result is not cached, so later appends remain
|
|
2559
|
+
visible. Errors propagate as from `next()`.
|
|
2560
|
+
|
|
2561
|
+
With `until`, `done: true` means the reader has caught up to the bound as of that pull. Running
|
|
2562
|
+
out of readable entries short of the bound throws instead, so an unavailable segment cannot
|
|
2563
|
+
silently trigger a handoff. Exact-start queries take the index's stop-to-tail jump only when
|
|
2564
|
+
its target does not pass the handoff position.
|
|
2565
|
+
|
|
2566
|
+
`iterator.close()` permanently ends the iterator and releases its cached entry and mapping.
|
|
2567
|
+
It is idempotent; later `next()` and `peek()` calls report done. Breaking out of `for...of`
|
|
2568
|
+
keeps the iterator resumable and does not close it. Destroying a log with
|
|
2569
|
+
`purgeLogs({ destroy: true })` invalidates its existing iterators; close them and create new
|
|
2570
|
+
iterators for a recreated log, including any `until` bounds.
|
|
2571
|
+
|
|
2572
|
+
```typescript
|
|
2573
|
+
const live = log.query();
|
|
2574
|
+
const replay = log.query({ start: startTime, until: live });
|
|
2575
|
+
for (const entry of replay) deliver(entry);
|
|
2576
|
+
replay.close();
|
|
2577
|
+
// A later commit belongs to live even if its timestamp is earlier than startTime.
|
|
2578
|
+
```
|
|
2579
|
+
|
|
2338
2580
|
The iterator produces an object with the log entry timestamp and data.
|
|
2339
2581
|
|
|
2340
2582
|
- `object`
|
|
@@ -2342,6 +2584,25 @@ The iterator produces an object with the log entry timestamp and data.
|
|
|
2342
2584
|
- `timestamp: number` The entry timestamp used to collate entries by transaction.
|
|
2343
2585
|
- `endTxn: boolean` This is `true` when the entry is the last entry in a transaction.
|
|
2344
2586
|
|
|
2587
|
+
These errors leave the iterator usable:
|
|
2588
|
+
|
|
2589
|
+
- `TransactionLogUntilUnreachedError`: readable input ended before `until`. The cursor stays in
|
|
2590
|
+
place; retry after the input becomes readable or the pending transaction commits. Properties:
|
|
2591
|
+
`logId` and `position`, identifying where the read stopped.
|
|
2592
|
+
- `TransactionLogGapError`: the iterator reached segments it had not read yet and could not read
|
|
2593
|
+
them, usually because retention purged them while the reader lagged. Their entries are not in
|
|
2594
|
+
this log anymore, so a consumer that replicates the log should treat its copy as discontinuous
|
|
2595
|
+
(for example, by resyncing from a full copy) rather than continuing. Properties: `logId` (the
|
|
2596
|
+
last segment read), `resumeLogId` (the segment the next `next()` call reads), and
|
|
2597
|
+
`unavailableLogCount`. A new query positioned within the retained segments does not report the
|
|
2598
|
+
segments purged before its starting point.
|
|
2599
|
+
- `CorruptFrameError` (a `RangeError`): an entry's framing is broken. `resyncPosition` is where
|
|
2600
|
+
valid framing resumes, or `undefined` when nothing follows the break.
|
|
2601
|
+
|
|
2602
|
+
All three classes are exported from the package. After a gap or corrupt frame, calling `next()`
|
|
2603
|
+
again continues past the problem. Other errors, such as a failure to open a segment, do not move
|
|
2604
|
+
the iterator, so retrying them without a fix fails the same way.
|
|
2605
|
+
|
|
2345
2606
|
```typescript
|
|
2346
2607
|
const log = db.useLog('foo');
|
|
2347
2608
|
const iter = log.query({});
|
|
@@ -2495,7 +2756,7 @@ Contract:
|
|
|
2495
2756
|
`QueryPerformanceCounter` on Windows).
|
|
2496
2757
|
- **Origin and lifetime**: the origin is unspecified and fixed for the life of the process. Every
|
|
2497
2758
|
sample taken in the process is in one domain — the main thread and every `worker_threads` worker,
|
|
2498
|
-
including workers started or restarted at any later time, under Node
|
|
2759
|
+
including workers started or restarted at any later time, under Node and Bun alike. Samples
|
|
2499
2760
|
are not meaningful across processes or restarts, and are unrelated to the Unix epoch: never compare
|
|
2500
2761
|
them with `Date.now()`, `db.getMonotonicTimestamp()` or transaction timestamps.
|
|
2501
2762
|
- **Monotonic, not unique**: a sample taken after another (in real time, on any thread) is `>=` it.
|
|
@@ -2521,6 +2782,55 @@ Contract:
|
|
|
2521
2782
|
- **Cost**: one clock read and a double conversion; no lock, no native allocation, no wall-clock read,
|
|
2522
2783
|
no database or log I/O, no per-worker calibration. Safe from any thread. The runtime may allocate a boxed JS number.
|
|
2523
2784
|
|
|
2785
|
+
### `raiseTimestampFloor(timestamp: number): boolean`
|
|
2786
|
+
|
|
2787
|
+
Raises the process-wide monotonic timestamp clock — the clock behind
|
|
2788
|
+
[`db.getMonotonicTimestamp()`](#dbgetmonotonictimestamp-number), every transaction's initial
|
|
2789
|
+
timestamp and therefore every transaction-log batch key — so that every timestamp the process
|
|
2790
|
+
issues afterwards is strictly greater than `timestamp` (Unix epoch milliseconds). No database
|
|
2791
|
+
handle is needed.
|
|
2792
|
+
|
|
2793
|
+
```typescript
|
|
2794
|
+
import { RocksDatabase, raiseTimestampFloor } from '@harperfast/rocksdb-js';
|
|
2795
|
+
|
|
2796
|
+
// Before anything in this process starts a transaction:
|
|
2797
|
+
raiseTimestampFloor(persistedFloor);
|
|
2798
|
+
const db = RocksDatabase.open('/path/to/db', { timestampFloorLog: 'local' });
|
|
2799
|
+
```
|
|
2800
|
+
|
|
2801
|
+
The clock is monotonic within a process, not across restarts, and
|
|
2802
|
+
[`timestampFloorLog`](#timestamp-floor-at-open) only restores keys still on disk. A bound this node
|
|
2803
|
+
has promised that is not in its log — a progress floor advertised to peers, or the largest key of
|
|
2804
|
+
segments retention has since purged — can only come back through this call. The caller names the
|
|
2805
|
+
value because only the caller knows which bounds it has promised.
|
|
2806
|
+
|
|
2807
|
+
Contract:
|
|
2808
|
+
|
|
2809
|
+
- **Returns** `true` if the clock was raised: `timestamp` was ahead of both the wall clock and every
|
|
2810
|
+
timestamp already issued, as after a backward clock step. `false` if the clock was already at or
|
|
2811
|
+
above it, which is the usual result at a restart. Raising is one-way; a lower value never moves
|
|
2812
|
+
the clock back.
|
|
2813
|
+
- **Throws** a `TypeError` if `timestamp` is not a number, and a `RangeError` if it is not finite, is
|
|
2814
|
+
not greater than `0`, is not less than `8.64e15`, or is more than ten years ahead of the wall
|
|
2815
|
+
clock — the same bound `timestampFloorLog` applies to a durable key. An unusable persisted floor
|
|
2816
|
+
therefore fails loudly rather than being ignored. The implausibility error includes the wall
|
|
2817
|
+
clock it was checked against, since a host that booted with an unsynchronized clock trips it too.
|
|
2818
|
+
- **Process-wide**: it applies to every database and every `worker_threads` worker in the process,
|
|
2819
|
+
but not to other processes. Each new process must call it again.
|
|
2820
|
+
- **Only issued timestamps**: a transaction constructed before the call keeps its timestamp, and
|
|
2821
|
+
[`txn.setTimestamp(ts)`](#txnsettimestampts-number-void) can still adopt any valid timestamp,
|
|
2822
|
+
including one below the floor. `txn.setTimestamp()` with no argument issues a fresh timestamp
|
|
2823
|
+
above it.
|
|
2824
|
+
- **Not durable and not a fence**: the floor lives in memory only, and nothing stops a transaction
|
|
2825
|
+
constructed concurrently with the call. Persist the floor yourself, and restore it before the
|
|
2826
|
+
process starts any transaction it originates. It composes with `timestampFloorLog` (the clock takes
|
|
2827
|
+
the larger of the two), so keep naming the local log unless the persisted floor already covers
|
|
2828
|
+
every key written to it.
|
|
2829
|
+
- **Trusted input only**: the floor is not lowered until the process exits, so a value hours or years
|
|
2830
|
+
ahead holds every timestamp that far ahead of wall time until the clock catches up — and keys
|
|
2831
|
+
written in the meantime raise the next restart's seed with them. Pass only floors this node
|
|
2832
|
+
persisted itself, never an unvalidated value received from another node.
|
|
2833
|
+
|
|
2524
2834
|
### `currentThreadId(): number`
|
|
2525
2835
|
|
|
2526
2836
|
Returns the current thread ID.
|
|
@@ -2554,27 +2864,41 @@ console.log(getRegistryStatus());
|
|
|
2554
2864
|
|
|
2555
2865
|
### `shutdown(): void`
|
|
2556
2866
|
|
|
2557
|
-
The `shutdown()` will flush all in-memory data to disk and
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2867
|
+
The `shutdown()` will flush all in-memory data to disk and cancel outstanding background
|
|
2868
|
+
compactions, for all open databases. It throws the first close failure after attempting every
|
|
2869
|
+
claimed database; call it again to retry any descriptor whose native teardown did not complete. It
|
|
2870
|
+
reports pending destroy-cleanup tombstones without deleting their paths; retry those with an
|
|
2871
|
+
explicit `destroy()`.
|
|
2872
|
+
|
|
2873
|
+
You do not need to call it at exit. When rocksdb-js loads on the main thread, it registers a
|
|
2874
|
+
`process.on('exit')` listener that runs the same shutdown, so a `process.exit()` (which skips the
|
|
2875
|
+
native cleanup Node runs on a natural exit) still flushes and closes every database, including
|
|
2876
|
+
those opened by workers that are still running. That listener reports failures to stderr instead
|
|
2877
|
+
of throwing, and sets `process.exitCode` to 1 (unless it is already nonzero) when data may be left
|
|
2878
|
+
unflushed: a database another thread was still closing when the shutdown timed out, or a final
|
|
2879
|
+
flush that failed. Afterwards opening a database throws. It
|
|
2880
|
+
still runs when an earlier `exit` listener throws. It does not run when the process is killed by a
|
|
2881
|
+
signal it does not handle.
|
|
2882
|
+
|
|
2883
|
+
Like the cleanup on a natural exit, it waits for native work already in flight (a commit, a flush
|
|
2884
|
+
waiting out a write stall, or a backup) without a time limit, so `process.exit()` returns only once
|
|
2885
|
+
that work finishes.
|
|
2563
2886
|
|
|
2564
|
-
|
|
2565
|
-
|
|
2887
|
+
Because it is registered when rocksdb-js loads, `exit` listeners added later run after databases
|
|
2888
|
+
are closed. An `exit` listener that still writes to a database must run first:
|
|
2566
2889
|
|
|
2567
2890
|
```typescript
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
try {
|
|
2571
|
-
shutdown();
|
|
2572
|
-
} catch (error) {
|
|
2573
|
-
console.error('rocksdb-js shutdown failed', error);
|
|
2574
|
-
}
|
|
2891
|
+
process.prependListener('exit', () => {
|
|
2892
|
+
db.putSync('lastSeen', Date.now());
|
|
2575
2893
|
});
|
|
2576
2894
|
```
|
|
2577
2895
|
|
|
2896
|
+
Set `RocksDatabase.config({ shutdownOnExit: false })` to skip it, for example to leave memtables
|
|
2897
|
+
unflushed at `process.exit()` and exercise crash recovery in a test. The listener stays registered
|
|
2898
|
+
and does nothing: on a natural exit the runtime's native cleanup still flushes, and whether Bun's
|
|
2899
|
+
does at `process.exit()` depends on the Bun release.
|
|
2900
|
+
Calling `shutdown()` from your own `exit` listener as well is safe.
|
|
2901
|
+
|
|
2578
2902
|
### `versions: { 'rocksdb': string; 'rocksdb-js': string }`
|
|
2579
2903
|
|
|
2580
2904
|
Returns the `rocksdb-js` and RocksDB version.
|
|
@@ -2899,11 +3223,13 @@ Options for `get()`, `getSync()`, and the `getBinary*` methods.
|
|
|
2899
3223
|
- `expectedVersion: number` When set, the [Verification Table](#verification-table) is checked
|
|
2900
3224
|
before reading. If the slot holds this version, the read is skipped and the
|
|
2901
3225
|
`FRESH_VERSION_FLAG` sentinel is returned. After a database read, the slot is seeded with the
|
|
2902
|
-
version extracted from the value. Requires the
|
|
3226
|
+
version extracted from the value. Requires the handle to be opened with
|
|
2903
3227
|
`verificationTable: true`.
|
|
2904
3228
|
- `populateVersion: boolean` When `true`, after a database read the verification-table slot is
|
|
2905
3229
|
seeded with the version extracted from the value, eliminating the need for a separate
|
|
2906
|
-
`db.populateVersion()` call on cold reads.
|
|
3230
|
+
`db.populateVersion()` call on cold reads. Requires the handle to participate in the
|
|
3231
|
+
[Verification Table](#verification-table); otherwise the read seeds nothing. Defaults to
|
|
3232
|
+
`false`.
|
|
2907
3233
|
- `skipDecode: boolean` When `true`, the value is returned without being decoded. Defaults to
|
|
2908
3234
|
`false`.
|
|
2909
3235
|
|