@harperfast/rocksdb-js 2.0.0 → 2.2.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 +344 -31
- package/bin/rocksdb-js.mjs +290 -2
- package/dist/index.cjs +224 -34
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +770 -31
- package/dist/index.d.mts +770 -31
- package/dist/index.mjs +223 -35
- package/dist/index.mjs.map +1 -1
- package/docs/stats.md +459 -0
- package/docs/transaction-log.md +279 -0
- package/package.json +20 -19
package/README.md
CHANGED
|
@@ -74,6 +74,11 @@ Creates a new database instance.
|
|
|
74
74
|
before purging. Defaults to `'3d'` (3 days).
|
|
75
75
|
- `transactionLogsPath: string` The path to store transaction logs. Defaults to
|
|
76
76
|
`"${db.path}/transaction_logs"`.
|
|
77
|
+
- `verificationTable: boolean` When `true`, this column family participates in the process-global
|
|
78
|
+
[Verification Table](#verification-table): transaction writes to this column family invalidate
|
|
79
|
+
the verification slot for each written key. Enable this only for column families whose records
|
|
80
|
+
are cached (e.g. the primary column family of a table). Defaults to `false`. Requires
|
|
81
|
+
`verificationTableEntries` to be configured before the first database is opened.
|
|
77
82
|
|
|
78
83
|
### `db.close()`
|
|
79
84
|
|
|
@@ -106,6 +111,11 @@ Sets global database settings.
|
|
|
106
111
|
Defaults to 32MB. Set to `0` (zero) disables block cache for future opened databases. Existing
|
|
107
112
|
block cache for any opened databases is resized immediately. Negative values throw an error.
|
|
108
113
|
- `compactOnClose: boolean` When `true`, compacts the database on close. Defaults to `false`.
|
|
114
|
+
- `verificationTableEntries: number` The number of slots in the process-global
|
|
115
|
+
[Verification Table](#verification-table). Each slot is 8 bytes, so the default of `131072`
|
|
116
|
+
(128K) slots is 1 MB. Set to `0` to disable the verification table. This must be configured
|
|
117
|
+
before the first database is opened; once the table is materialized, attempts to change this
|
|
118
|
+
value throw.
|
|
109
119
|
- `writeBufferManagerAllowStall: boolean` When `true`, writes are stalled once the manager's
|
|
110
120
|
`buffer_size` is exceeded, providing a hard cap on memtable memory. When `false`, memtables are
|
|
111
121
|
allowed to grow past the limit and flushes are simply scheduled more aggressively. Off by
|
|
@@ -305,9 +315,28 @@ assert.equal(result, 'foo');
|
|
|
305
315
|
|
|
306
316
|
Note that all errors are returned as rejected promises.
|
|
307
317
|
|
|
318
|
+
See [`GetOptions`](#getoptions) for the available options.
|
|
319
|
+
|
|
320
|
+
When the `expectedVersion` option is set and the [Verification Table](#verification-table) records
|
|
321
|
+
a matching version for the key, `get()` returns the `FRESH_VERSION_FLAG` sentinel
|
|
322
|
+
(`constants.FRESH_VERSION_FLAG`) instead of reading the value — signalling that any value the
|
|
323
|
+
caller has already cached for this key is still fresh and no read was performed. Be sure to check
|
|
324
|
+
for this sentinel before treating the result as a value:
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
import { constants } from '@harperfast/rocksdb-js';
|
|
328
|
+
|
|
329
|
+
const result = db.get(key, { expectedVersion: cachedEntry.version });
|
|
330
|
+
if (result === constants.FRESH_VERSION_FLAG) {
|
|
331
|
+
// the cached value is still valid; no read occurred
|
|
332
|
+
return cachedEntry.value;
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
308
336
|
### `db.getSync(key: Key, options?: GetOptions): any`
|
|
309
337
|
|
|
310
|
-
Synchronous version of `get()`.
|
|
338
|
+
Synchronous version of `get()`. Like `get()`, this can return the `FRESH_VERSION_FLAG` sentinel when
|
|
339
|
+
the `expectedVersion` option is used.
|
|
311
340
|
|
|
312
341
|
### `db.getEstimatedKeyCount(): number`
|
|
313
342
|
|
|
@@ -564,6 +593,12 @@ The `attempt` parameter is the number of times the transaction has been retried.
|
|
|
564
593
|
|
|
565
594
|
### `TransactionOptions`
|
|
566
595
|
|
|
596
|
+
- `coordinatedRetry?: boolean` When `true`, an `IsBusy` conflict at commit time is retried
|
|
597
|
+
automatically instead of being rejected. Rather than retrying immediately and racing the
|
|
598
|
+
conflicting transaction again, the retry waits until the conflicting transaction has committed and
|
|
599
|
+
released its write intent, then re-runs the transaction body right away with no backoff delay.
|
|
600
|
+
Requires the column family to be opened with `verificationTable: true`. See
|
|
601
|
+
[Verification Table](#verification-table). Defaults to `false`.
|
|
567
602
|
- `disableSnapshot?: boolean` Whether to disable snapshots. Defaults to `false`.
|
|
568
603
|
- `maxRetries?: number` The maximum number of times to retry the transaction. Defaults to `3`.
|
|
569
604
|
- `retryOnBusy?: boolean` Whether to retry the transaction if the commit fails with `IsBusy`.
|
|
@@ -582,6 +617,14 @@ transaction is not retried, a `ERR_TRANSACTION_ABANDONED` error will be thrown.
|
|
|
582
617
|
Users should use the `attempt` transaction callback parameter to ensure duplicate transaction log
|
|
583
618
|
entries are not added.
|
|
584
619
|
|
|
620
|
+
When `coordinatedRetry: true`, the retry behavior changes for `IsBusy` conflicts: instead of
|
|
621
|
+
rejecting (or retrying immediately and potentially conflicting again), the commit waits until the
|
|
622
|
+
conflicting transaction has committed and released its write intent, then re-runs the transaction
|
|
623
|
+
body immediately with no backoff. This is still bounded by `maxRetries` — if the transaction has
|
|
624
|
+
not committed after the configured number of coordinated retries, the transaction is abandoned with
|
|
625
|
+
an `ERR_TRANSACTION_ABANDONED` error. Coordinated retry requires the column family to be opened with
|
|
626
|
+
`verificationTable: true`.
|
|
627
|
+
|
|
585
628
|
### Class: `Transaction`
|
|
586
629
|
|
|
587
630
|
The transaction callback is passed in a `Transaction` instance which contains all of the same data
|
|
@@ -804,35 +847,28 @@ same column family-level properties, but includes all internal tickers and histo
|
|
|
804
847
|
Column family and ticker stat values are 64-bit unsigned integers and histogram values are
|
|
805
848
|
`StatsHistogramData` objects.
|
|
806
849
|
|
|
850
|
+
The result also always includes a summarized, aggregate set of `txnlog.*` keys covering all of
|
|
851
|
+
the database's transaction logs. These are present regardless of whether statistics are enabled
|
|
852
|
+
and can be fetched individually with `db.getStat('txnlog.…')`. For detailed, per-log statistics —
|
|
853
|
+
including memory-map usage — use [`log.getStats()`](#loggetstats-transactionlogstats). All stat
|
|
854
|
+
names are documented in [docs/stats.md](docs/stats.md).
|
|
855
|
+
|
|
807
856
|
```typescript
|
|
808
857
|
// get essential stats
|
|
809
858
|
console.log(db.getStats());
|
|
810
859
|
|
|
811
860
|
// get all stats
|
|
812
861
|
console.log(db.getStats(true));
|
|
813
|
-
```
|
|
814
|
-
|
|
815
|
-
### `stats`
|
|
816
|
-
|
|
817
|
-
An object containing stat specific constants including ticker and histogram names.
|
|
818
|
-
|
|
819
|
-
#### `stats.histograms`
|
|
820
862
|
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
```typescript
|
|
824
|
-
import { stats } from '@harperfast/rocksdb-js';
|
|
825
|
-
console.log(stats.histograms);
|
|
863
|
+
// transaction log bytes across all logs
|
|
864
|
+
console.log(db.getStats()['txnlog.totalSizeBytes']);
|
|
826
865
|
```
|
|
827
866
|
|
|
828
|
-
|
|
867
|
+
### `stats`
|
|
829
868
|
|
|
830
|
-
An
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
import { stats } from '@harperfast/rocksdb-js';
|
|
834
|
-
console.log(stats.tickers);
|
|
835
|
-
```
|
|
869
|
+
An object containing stat-specific constants. The full catalog of available stat names (RocksDB
|
|
870
|
+
tickers, histograms, internal properties, and transaction log stats) is documented in
|
|
871
|
+
[docs/stats.md](docs/stats.md).
|
|
836
872
|
|
|
837
873
|
#### `stats.StatsLevel`
|
|
838
874
|
|
|
@@ -960,6 +996,74 @@ Note: If the `callback` throws an error, Node.js suppress the error. Node.js 18.
|
|
|
960
996
|
`--force-node-api-uncaught-exceptions-policy` flag which will cause errors to emit the
|
|
961
997
|
`'uncaughtException'` event. Future Node.js releases will enable this flag by default.
|
|
962
998
|
|
|
999
|
+
## Verification Table
|
|
1000
|
+
|
|
1001
|
+
The verification table is a process-global, fixed-size structure that lets an application cheaply
|
|
1002
|
+
check whether a value it has already cached is still fresh, without performing a full read. It is
|
|
1003
|
+
intended for read-heavy workloads where records carry a monotonically increasing numeric version.
|
|
1004
|
+
|
|
1005
|
+
Each record's version is the numeric value stored in the first 8 bytes of its value (interpreted as
|
|
1006
|
+
a big-endian float64). The table maps `(database, column family, key)` to a single 8-byte slot that
|
|
1007
|
+
holds the last-known version for that key. Because slots are addressed by a hash, distinct keys may
|
|
1008
|
+
share a slot; a collision only ever causes a conservative miss (a real read), never a stale value to
|
|
1009
|
+
be treated as fresh.
|
|
1010
|
+
|
|
1011
|
+
The freshness check works as follows:
|
|
1012
|
+
|
|
1013
|
+
1. Pass `{ expectedVersion }` to `get()` / `getSync()`. If the slot currently records that version,
|
|
1014
|
+
the read is skipped and the `FRESH_VERSION_FLAG` sentinel is returned.
|
|
1015
|
+
2. On a cold read, pass `{ populateVersion: true }` (or call `db.populateVersion()` afterward) to
|
|
1016
|
+
seed the slot with the version extracted from the value, so subsequent freshness checks hit.
|
|
1017
|
+
|
|
1018
|
+
Transaction writes to a column family opened with `verificationTable: true` invalidate the slot for
|
|
1019
|
+
each written key at write time, so a stale version can never survive a write. This is also what
|
|
1020
|
+
enables [`coordinatedRetry`](#transactionoptions): a conflicting transaction parks on the slot and
|
|
1021
|
+
retries once the write intent is released.
|
|
1022
|
+
|
|
1023
|
+
The table is **process-global** and backed by a single shared structure, so versions populated on
|
|
1024
|
+
the main thread are visible to `worker_threads` workers and vice versa.
|
|
1025
|
+
|
|
1026
|
+
### Enabling the verification table
|
|
1027
|
+
|
|
1028
|
+
The table must be sized before the first database is opened, via the
|
|
1029
|
+
[`verificationTableEntries`](#dbconfigoptions) config option (default `131072` slots = 1 MB; set to
|
|
1030
|
+
`0` to disable). Then opt-in per column family with the `verificationTable: true` open option:
|
|
1031
|
+
|
|
1032
|
+
```typescript
|
|
1033
|
+
import { RocksDatabase } from '@harperfast/rocksdb-js';
|
|
1034
|
+
|
|
1035
|
+
RocksDatabase.config({ verificationTableEntries: 128 * 1024 });
|
|
1036
|
+
|
|
1037
|
+
const db = RocksDatabase.open('path/to/db', { verificationTable: true });
|
|
1038
|
+
```
|
|
1039
|
+
|
|
1040
|
+
Enable `verificationTable` only for column families whose records are cached (e.g. the primary
|
|
1041
|
+
column family of a table); enabling it adds per-write slot invalidation overhead.
|
|
1042
|
+
|
|
1043
|
+
### `db.verifyVersion(key: Key, version: number): boolean`
|
|
1044
|
+
|
|
1045
|
+
Returns `true` when the verification table currently records `version` for `key` (in this database
|
|
1046
|
+
and column family), indicating a cached value with that version is still fresh. Returns `false`
|
|
1047
|
+
otherwise — including when the table is disabled. This is a cheap, synchronous check that performs
|
|
1048
|
+
no database read.
|
|
1049
|
+
|
|
1050
|
+
```typescript
|
|
1051
|
+
if (db.verifyVersion(key, cachedEntry.version)) {
|
|
1052
|
+
return cachedEntry.value;
|
|
1053
|
+
}
|
|
1054
|
+
const value = db.getSync(key);
|
|
1055
|
+
db.populateVersion(key, extractVersion(value));
|
|
1056
|
+
```
|
|
1057
|
+
|
|
1058
|
+
### `db.populateVersion(key: Key, version: number): void`
|
|
1059
|
+
|
|
1060
|
+
Seeds the verification-table slot for `key` with `version`. This is typically called after a full
|
|
1061
|
+
read where the caller already knows the version. It has no effect if the slot is currently
|
|
1062
|
+
lock-tagged (a transaction is mid-write on that key) or if the verification table is disabled.
|
|
1063
|
+
|
|
1064
|
+
Passing `{ populateVersion: true }` to `get()` / `getSync()` performs the equivalent seeding
|
|
1065
|
+
automatically after a cold read, avoiding a separate call.
|
|
1066
|
+
|
|
963
1067
|
## Transaction Log
|
|
964
1068
|
|
|
965
1069
|
A user controlled API for logging transactions. This API is designed to be generic so that you can
|
|
@@ -968,6 +1072,46 @@ log gets, puts, and deletes, but also arbitrary entries.
|
|
|
968
1072
|
Transaction logs are isolated by the database path allowing different column families in the same
|
|
969
1073
|
database to share the transaction log store, but not other databases.
|
|
970
1074
|
|
|
1075
|
+
### Memory-Map Handling
|
|
1076
|
+
|
|
1077
|
+
Transaction log files are read through read-only memory maps. Understanding how these maps interact
|
|
1078
|
+
with system memory helps when interpreting [`log.getStats()`](#loggetstats-transactionlogstats)
|
|
1079
|
+
figures and process memory usage:
|
|
1080
|
+
|
|
1081
|
+
- **Maps are created lazily and live until the file is closed.** Writing log entries does not map
|
|
1082
|
+
anything; a log file is mapped the first time a `log.query()` reads from it. The native layer
|
|
1083
|
+
holds each file's map for the life of the file — it is released when the log file is purged or
|
|
1084
|
+
the database is closed, not by garbage collection. JS `Buffer` views over the map (including
|
|
1085
|
+
`entry.data`) hold an additional reference, so the underlying memory is never unmapped while a
|
|
1086
|
+
view is still reachable. `stats.memory.activeMaps` counts the maps currently held by the native
|
|
1087
|
+
layer.
|
|
1088
|
+
- **Mapped bytes are virtual, not resident.** Creating a map reserves address space only. A page
|
|
1089
|
+
consumes physical RAM (RSS) when it is first read (demand paging). Querying a multi-gigabyte log
|
|
1090
|
+
can show `memory.mappedBytes` in the gigabytes while actual memory usage barely moves.
|
|
1091
|
+
- **Queries are zero-copy.** The iterator returned by `log.query()` reads only each entry's small
|
|
1092
|
+
header; `entry.data` is a `Buffer` view directly into the map (no copy). Payload pages are
|
|
1093
|
+
faulted into memory only if and when the entry data is actually read.
|
|
1094
|
+
- **Resident pages are reclaimable.** Because the maps are read-only and file-backed, every
|
|
1095
|
+
resident page is "clean" — the kernel can evict it at any time under memory pressure and re-read
|
|
1096
|
+
it from disk on the next access. Mapped log data therefore lives in the page cache and cannot
|
|
1097
|
+
exhaust memory the way heap allocations can; a full scan of a log larger than RAM will simply
|
|
1098
|
+
cycle pages through the cache.
|
|
1099
|
+
|
|
1100
|
+
OS-specific differences:
|
|
1101
|
+
|
|
1102
|
+
- **POSIX (Linux and macOS):** The active write file is mapped at the full configured
|
|
1103
|
+
`transactionLogMaxSize` (an anonymous reservation with the file's contents overlaid on top), so
|
|
1104
|
+
`memory.mappedBytes` over-reports the active file; `memory.overlayBytes` is the file-backed
|
|
1105
|
+
portion and is the closer proxy for real consumption.
|
|
1106
|
+
- **macOS:** Activity Monitor's "Memory" column reports the physical footprint, which excludes
|
|
1107
|
+
clean file-backed pages — mapped log data is essentially invisible there even when resident. Use
|
|
1108
|
+
process RSS (e.g. `process.memoryUsage().rss`, `ps`, or `vmmap <pid>`) to observe it.
|
|
1109
|
+
- **Linux:** Resident map pages are visible in process RSS, and `/proc/<pid>/smaps` reports exact
|
|
1110
|
+
per-file `Rss`/`Pss` for each mapped `.txnlog` file.
|
|
1111
|
+
- **Windows:** Maps are created with `CreateFileMapping`/`MapViewOfFile` at the file's current
|
|
1112
|
+
size. There is no overlay mechanism, so `memory.overlayBytes` is always `0`, and the active
|
|
1113
|
+
write file's map is not cached because it is not growable.
|
|
1114
|
+
|
|
971
1115
|
### `db.listLogs(): string[]`
|
|
972
1116
|
|
|
973
1117
|
Returns an array of log store names.
|
|
@@ -978,19 +1122,34 @@ const names = db.listLogs();
|
|
|
978
1122
|
|
|
979
1123
|
### `db.purgeLogs(options?): string[]`
|
|
980
1124
|
|
|
1125
|
+
### `db.purgeLogs({ includeEntryCounts: true, ...options }): { path: string; entries: number }[]`
|
|
1126
|
+
|
|
981
1127
|
Deletes transaction log files older than the `transactionLogRetention` (defaults to 3 days).
|
|
982
1128
|
|
|
983
1129
|
- `options: object`
|
|
984
1130
|
- `before?: number` Remove all transaction log files older than the specified timestamp.
|
|
985
1131
|
- `destroy?: boolean` When `true`, deletes transaction log stores including all log sequence files
|
|
986
1132
|
on disk.
|
|
1133
|
+
- `includeEntryCounts?: boolean` When `true`, counts the entries in each deleted log file and
|
|
1134
|
+
returns an array of `{ path, entries }` objects instead of an array of file paths. Counting reads
|
|
1135
|
+
each file before it is removed, so it is only performed when this option is enabled.
|
|
987
1136
|
- `name?: string` The name of a store to limit the purging to.
|
|
988
1137
|
|
|
989
|
-
|
|
1138
|
+
The method is overloaded so the return type follows `includeEntryCounts`: by default (omitted or
|
|
1139
|
+
`false`) it returns `string[]` — the full path of each log file deleted; when `includeEntryCounts` is
|
|
1140
|
+
`true` it returns `{ path: string; entries: number }[]`, each entry being the `path` of the deleted
|
|
1141
|
+
log file and the number of `entries` it held. Because of the overloads, the object-array form is
|
|
1142
|
+
returned directly when `includeEntryCounts: true` is passed as a literal, with no casting required.
|
|
990
1143
|
|
|
991
1144
|
```typescript
|
|
992
1145
|
const removed = db.purgeLogs();
|
|
993
1146
|
console.log(`Removed ${removed.length} log files`);
|
|
1147
|
+
|
|
1148
|
+
// Include the entry count for each deleted log file:
|
|
1149
|
+
const purged = db.purgeLogs({ includeEntryCounts: true });
|
|
1150
|
+
for (const { path, entries } of purged) {
|
|
1151
|
+
console.log(`Removed ${path} (${entries} entries)`);
|
|
1152
|
+
}
|
|
994
1153
|
```
|
|
995
1154
|
|
|
996
1155
|
### `db.useLog(name): TransactionLog`
|
|
@@ -1116,6 +1275,34 @@ for (const entry of rangeIter) {
|
|
|
1116
1275
|
Returns the size of the given transaction log sequence file in bytes. Omit the sequence number to
|
|
1117
1276
|
get the total size of all the transaction log sequence files for this log.
|
|
1118
1277
|
|
|
1278
|
+
#### `log.getStats(): TransactionLogStats`
|
|
1279
|
+
|
|
1280
|
+
Returns a detailed statistics snapshot for this transaction log, including file/transaction
|
|
1281
|
+
gauges, memory-map usage, recovery positions, purge/retention gauges, and lifetime counters. All
|
|
1282
|
+
sizes are in bytes; timestamps are milliseconds since the Unix epoch.
|
|
1283
|
+
|
|
1284
|
+
```typescript
|
|
1285
|
+
const log = db.useLog('replication');
|
|
1286
|
+
const stats = log.getStats();
|
|
1287
|
+
|
|
1288
|
+
stats.fileCount; // number of sequence files on disk
|
|
1289
|
+
stats.totalSizeBytes; // total bytes across all sequence files
|
|
1290
|
+
stats.memory.mappedBytes; // bytes mapped into memory (virtual address space)
|
|
1291
|
+
stats.memory.overlayBytes; // POSIX file-backed overlay portion (0 on Windows)
|
|
1292
|
+
stats.replayGapBytes; // bytes between the last flushed position and the write head
|
|
1293
|
+
stats.purge.retainedUnflushedFiles; // files past retention but kept (not yet flushed to RocksDB)
|
|
1294
|
+
stats.totals.transactionsWritten; // lifetime count of transactions written
|
|
1295
|
+
```
|
|
1296
|
+
|
|
1297
|
+
> **Memory note:** `memory.mappedBytes` is virtual address space — the active write file is mapped
|
|
1298
|
+
> at the full configured `transactionLogMaxSize` on POSIX, so it does not reflect resident memory.
|
|
1299
|
+
> `memory.overlayBytes` (POSIX only) is the file-backed portion and is the closer proxy for real
|
|
1300
|
+
> consumption.
|
|
1301
|
+
|
|
1302
|
+
The `purge.retainedUnflushedFiles` gauge is useful for diagnosing why logs are not being cleaned
|
|
1303
|
+
up: a file can be older than the retention period but still retained because its transactions have
|
|
1304
|
+
not yet been flushed to RocksDB (purging it would be unsafe for crash recovery).
|
|
1305
|
+
|
|
1119
1306
|
### Transaction Log Initialization
|
|
1120
1307
|
|
|
1121
1308
|
When a database is opened, `rocksdb-js` will automatically discover the transaction log files. If
|
|
@@ -1198,6 +1385,109 @@ import { versions } from '@harperfast/rocksdb-js';
|
|
|
1198
1385
|
console.log(versions); // { "rocksdb": "10.10.1", "rocksdb-js": "0.1.2" }
|
|
1199
1386
|
```
|
|
1200
1387
|
|
|
1388
|
+
## Backups
|
|
1389
|
+
|
|
1390
|
+
Backups use RocksDB's `BackupEngine` to capture consistent, incremental, checksum-verified
|
|
1391
|
+
snapshots of a database. A backup covers the **entire database** — every column family, the
|
|
1392
|
+
manifest, and (by default) the write-ahead log — so it is not scoped to an individual `Store`.
|
|
1393
|
+
|
|
1394
|
+
Creating a backup is an instance method (`db.backup()`) because it needs a live database. The
|
|
1395
|
+
remaining operations act on a backup directory and do not require an open database, so they are
|
|
1396
|
+
grouped under the `backups` namespace export.
|
|
1397
|
+
|
|
1398
|
+
```typescript
|
|
1399
|
+
import { RocksDatabase, backups } from '@harperfast/rocksdb-js';
|
|
1400
|
+
|
|
1401
|
+
const db = RocksDatabase.open('/path/to/database');
|
|
1402
|
+
await db.put('foo', 'bar');
|
|
1403
|
+
|
|
1404
|
+
// Create a backup, then restore it into a fresh directory.
|
|
1405
|
+
const id = await db.backup('/path/to/backups');
|
|
1406
|
+
await backups.restore('/path/to/backups', '/path/to/restored-db');
|
|
1407
|
+
```
|
|
1408
|
+
|
|
1409
|
+
### `db.backup(backupDir: string, options?: BackupOptions): Promise<number>`
|
|
1410
|
+
|
|
1411
|
+
Creates a new backup of the entire database into `backupDir`, creating parent directories as
|
|
1412
|
+
needed, and resolves with the new backup id (a monotonically increasing integer). Subsequent
|
|
1413
|
+
backups into the same directory are incremental — unchanged immutable files are shared rather than
|
|
1414
|
+
re-copied.
|
|
1415
|
+
|
|
1416
|
+
When the database was opened with `disableWAL`, the memtable is flushed before the backup by
|
|
1417
|
+
default so unflushed data is not lost; otherwise flushing follows `options.flushBeforeBackup`.
|
|
1418
|
+
|
|
1419
|
+
```typescript
|
|
1420
|
+
const id = await db.backup('/path/to/backups', { metadata: 'nightly-2026-06-04' });
|
|
1421
|
+
```
|
|
1422
|
+
|
|
1423
|
+
`BackupOptions`:
|
|
1424
|
+
|
|
1425
|
+
| Option | Type | Default | Description |
|
|
1426
|
+
| ------------------------- | --------- | ---------------------- | --------------------------------------------------------------------- |
|
|
1427
|
+
| `flushBeforeBackup` | `boolean` | `true` if WAL disabled | Flush the memtable before backing up. |
|
|
1428
|
+
| `metadata` | `string` | `''` | Application metadata stored with the backup, returned by `list()`. |
|
|
1429
|
+
| `shareTableFiles` | `boolean` | `true` | Share files between backups to enable incremental backups. |
|
|
1430
|
+
| `shareFilesWithChecksum` | `boolean` | `true` | Distinguish shared files by checksum to avoid cross-database clashes. |
|
|
1431
|
+
| `backupLogFiles` | `boolean` | `true` | Include write-ahead log files in the backup. |
|
|
1432
|
+
| `sync` | `boolean` | `true` | `fsync` backup files for crash consistency. |
|
|
1433
|
+
| `maxBackgroundOperations` | `number` | `1` | Number of background threads used to copy files. |
|
|
1434
|
+
|
|
1435
|
+
### `backups.restore(backupDir: string, dbDir: string, options?: RestoreOptions): Promise<void>`
|
|
1436
|
+
|
|
1437
|
+
Restores a backup from `backupDir` into `dbDir` (creating parent directories as needed). The
|
|
1438
|
+
database must **not** be open at `dbDir`, and the default restore mode is **destructive** — it
|
|
1439
|
+
purges `dbDir` before restoring. Restoring into the backup directory itself is rejected.
|
|
1440
|
+
|
|
1441
|
+
```typescript
|
|
1442
|
+
// Restore the latest backup.
|
|
1443
|
+
await backups.restore('/path/to/backups', '/path/to/restored-db');
|
|
1444
|
+
|
|
1445
|
+
// Restore a specific backup id without purging matching existing files.
|
|
1446
|
+
await backups.restore('/path/to/backups', '/path/to/restored-db', {
|
|
1447
|
+
backupId: 1,
|
|
1448
|
+
mode: 'keepLatestDbSessionIdFiles',
|
|
1449
|
+
});
|
|
1450
|
+
```
|
|
1451
|
+
|
|
1452
|
+
`RestoreOptions`:
|
|
1453
|
+
|
|
1454
|
+
| Option | Type | Default | Description |
|
|
1455
|
+
| -------------- | --------------------------------------------------------------------- | ----------------- | ------------------------------------------------------------ |
|
|
1456
|
+
| `backupId` | `number` | latest backup | The backup id to restore. |
|
|
1457
|
+
| `walDir` | `string` | `dbDir` | Directory to restore write-ahead log files into. |
|
|
1458
|
+
| `keepLogFiles` | `boolean` | `false` | Keep existing log files in `walDir` rather than overwriting. |
|
|
1459
|
+
| `mode` | `'purgeAllFiles' \| 'keepLatestDbSessionIdFiles' \| 'verifyChecksum'` | `'purgeAllFiles'` | The restore strategy (default purges the destination). |
|
|
1460
|
+
|
|
1461
|
+
### `backups.list(backupDir: string): Promise<BackupInfo[]>`
|
|
1462
|
+
|
|
1463
|
+
Lists the non-corrupt backups in `backupDir`, ordered by id.
|
|
1464
|
+
|
|
1465
|
+
```typescript
|
|
1466
|
+
const list = await backups.list('/path/to/backups');
|
|
1467
|
+
// [{ backupId: 1, timestamp: 1749000000, size: 4096, numberFiles: 3, appMetadata: '' }, ...]
|
|
1468
|
+
```
|
|
1469
|
+
|
|
1470
|
+
Each `BackupInfo` contains `backupId`, `timestamp` (seconds since the epoch), `size` (bytes),
|
|
1471
|
+
`numberFiles`, and `appMetadata`.
|
|
1472
|
+
|
|
1473
|
+
### `backups.delete(backupDir: string, backupId: number): Promise<void>`
|
|
1474
|
+
|
|
1475
|
+
Deletes a specific backup. Shared files are reference-counted and only removed once no remaining
|
|
1476
|
+
backup references them, so this is not equivalent to deleting files manually.
|
|
1477
|
+
|
|
1478
|
+
### `backups.purge(backupDir: string, keepCount: number): Promise<void>`
|
|
1479
|
+
|
|
1480
|
+
Deletes all but the newest `keepCount` backups.
|
|
1481
|
+
|
|
1482
|
+
### `backups.verify(backupDir: string, backupId: number, options?: { verifyWithChecksum?: boolean }): Promise<void>`
|
|
1483
|
+
|
|
1484
|
+
Verifies a backup's file sizes, and optionally their checksums (which requires reading all
|
|
1485
|
+
backed-up data). Resolves if the backup is intact and rejects otherwise.
|
|
1486
|
+
|
|
1487
|
+
```typescript
|
|
1488
|
+
await backups.verify('/path/to/backups', 1, { verifyWithChecksum: true });
|
|
1489
|
+
```
|
|
1490
|
+
|
|
1201
1491
|
## Custom Store
|
|
1202
1492
|
|
|
1203
1493
|
The store is a class that sits between the `RocksDatabase` or `Transaction` instance and the native
|
|
@@ -1214,7 +1504,7 @@ The default `Store` contains the following methods which can be overridden:
|
|
|
1214
1504
|
- `decodeValue(value)`
|
|
1215
1505
|
- `encodeKey(key)`
|
|
1216
1506
|
- `encodeValue(value)`
|
|
1217
|
-
- `get(context, key,
|
|
1507
|
+
- `get(context, key, alwaysCreateNewBuffer?, options?)`
|
|
1218
1508
|
- `getCount(context, options?, txnId?)`
|
|
1219
1509
|
- `getKeys(context, options?)`
|
|
1220
1510
|
- `getKeysCount(context, options?)`
|
|
@@ -1225,11 +1515,13 @@ The default `Store` contains the following methods which can be overridden:
|
|
|
1225
1515
|
- `isOpen()`
|
|
1226
1516
|
- `listLogs()`
|
|
1227
1517
|
- `open()`
|
|
1518
|
+
- `populateVersion(key, version)`
|
|
1228
1519
|
- `putSync(context, key, value, options?)`
|
|
1229
1520
|
- `removeSync(context, key, options?)`
|
|
1230
1521
|
- `tryLock(key, onUnlocked?)`
|
|
1231
1522
|
- `unlock(key)`
|
|
1232
1523
|
- `useLog(context, name)`
|
|
1524
|
+
- `verifyVersion(key, version)`
|
|
1233
1525
|
- `withLock(key, callback?)`
|
|
1234
1526
|
|
|
1235
1527
|
To use it, extend the default `Store` and pass in an instance of your store into the `RocksDatabase`
|
|
@@ -1239,15 +1531,15 @@ constructor.
|
|
|
1239
1531
|
import { RocksDatabase, Store } from '@harperfast/rocksdb-js';
|
|
1240
1532
|
|
|
1241
1533
|
class MyStore extends Store {
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1534
|
+
get(context, key, alwaysCreateNewBuffer, options) {
|
|
1535
|
+
console.log('Getting:', key);
|
|
1536
|
+
return super.get(context, key, alwaysCreateNewBuffer, options);
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
putSync(context, key, value, options) {
|
|
1540
|
+
console.log('Putting:', key);
|
|
1541
|
+
return super.putSync(context, key, value, options);
|
|
1542
|
+
}
|
|
1251
1543
|
}
|
|
1252
1544
|
|
|
1253
1545
|
const myStore = new MyStore('path/to/db');
|
|
@@ -1267,6 +1559,22 @@ console.log(await db.get('foo'));
|
|
|
1267
1559
|
|
|
1268
1560
|
## Interfaces
|
|
1269
1561
|
|
|
1562
|
+
### `GetOptions`
|
|
1563
|
+
|
|
1564
|
+
Options for `get()`, `getSync()`, and the `getBinary*` methods.
|
|
1565
|
+
|
|
1566
|
+
- `options: object`
|
|
1567
|
+
- `expectedVersion: number` When set, the [Verification Table](#verification-table) is checked
|
|
1568
|
+
before reading. If the slot holds this version, the read is skipped and the
|
|
1569
|
+
`FRESH_VERSION_FLAG` sentinel is returned. After a database read, the slot is seeded with the
|
|
1570
|
+
version extracted from the value. Requires the column family to be opened with
|
|
1571
|
+
`verificationTable: true`.
|
|
1572
|
+
- `populateVersion: boolean` When `true`, after a database read the verification-table slot is
|
|
1573
|
+
seeded with the version extracted from the value, eliminating the need for a separate
|
|
1574
|
+
`db.populateVersion()` call on cold reads. Defaults to `false`.
|
|
1575
|
+
- `skipDecode: boolean` When `true`, the value is returned without being decoded. Defaults to
|
|
1576
|
+
`false`.
|
|
1577
|
+
|
|
1270
1578
|
### `RocksDBOptions`
|
|
1271
1579
|
|
|
1272
1580
|
- `options: object`
|
|
@@ -1332,6 +1640,11 @@ Options:
|
|
|
1332
1640
|
|
|
1333
1641
|
Available commands:
|
|
1334
1642
|
|
|
1643
|
+
- `backups <dir> [subcommand]` Manage database backups; with no subcommand or `ls`/`list`, lists the backups in
|
|
1644
|
+
`<dir>`. Subcommands: `backup` (create a backup of the open database), `restore <backup-id>`
|
|
1645
|
+
(restore into the open database after confirmation; unavailable in read-only mode),
|
|
1646
|
+
`verify <backup-id>` (checksum verification), `delete <backup-id>`, and `purge <keep-count>`
|
|
1647
|
+
(delete all but the newest backups). `delete` and `purge` report the recovered disk space.
|
|
1335
1648
|
- `clear` Clear all data in the current column family
|
|
1336
1649
|
- `columns` List column families
|
|
1337
1650
|
- `compact` Compact the current column family
|