@harperfast/rocksdb-js 2.10.0 → 2.12.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 +252 -62
- package/bin/rocksdb-js.mjs +49 -5
- package/dist/index.cjs +152 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +134 -5
- package/dist/index.d.mts +134 -5
- package/dist/index.mjs +152 -28
- package/dist/index.mjs.map +1 -1
- package/docs/backups.md +27 -0
- package/docs/stats.md +55 -51
- package/docs/transaction-log.md +142 -4
- package/docs/transaction-timestamp-integrity-design.md +70 -8
- package/package.json +19 -18
package/dist/index.cjs
CHANGED
|
@@ -130,7 +130,17 @@ const NativeDatabase = binding.Database;
|
|
|
130
130
|
const NativeIterator = binding.Iterator;
|
|
131
131
|
const NativeTransaction = binding.Transaction;
|
|
132
132
|
const TransactionLog = binding.TransactionLog;
|
|
133
|
-
|
|
133
|
+
/**
|
|
134
|
+
* Returns the status of every database currently held by the native registry.
|
|
135
|
+
* Walks every registered descriptor, so it samples live state rather than
|
|
136
|
+
* reading a stored property.
|
|
137
|
+
*/
|
|
138
|
+
const getRegistryStatus = binding.registryStatus;
|
|
139
|
+
/**
|
|
140
|
+
* @deprecated Use {@link getRegistryStatus} instead. This alias will be removed
|
|
141
|
+
* in v3.0.0.
|
|
142
|
+
*/
|
|
143
|
+
const registryStatus = getRegistryStatus;
|
|
134
144
|
/**
|
|
135
145
|
* Flushes every open database and waits for outstanding compactions, then
|
|
136
146
|
* releases the registry.
|
|
@@ -194,6 +204,12 @@ const coolTransactionLogs = binding.coolTransactionLogs;
|
|
|
194
204
|
*/
|
|
195
205
|
const transactionLogMapCount = binding.transactionLogMapCount;
|
|
196
206
|
/**
|
|
207
|
+
* Number of coordinated-retry wake callbacks registered on verification-table
|
|
208
|
+
* locks across the process. Internal — used by tests to verify that a park
|
|
209
|
+
* ending by timeout, env teardown, or close leaves no registration behind.
|
|
210
|
+
*/
|
|
211
|
+
const lockWakeCallbackCount = binding.lockWakeCallbackCount;
|
|
212
|
+
/**
|
|
197
213
|
* Test-only: force the next `count` transaction commits to fail with TryAgain (rolled back, so
|
|
198
214
|
* no data is committed), reproducing a stranded-snapshot conflict deterministically. Pass 0 to
|
|
199
215
|
* disarm. Used by the ERR_TRY_AGAIN retry regression test.
|
|
@@ -534,6 +550,14 @@ const backups = {
|
|
|
534
550
|
* Verifies a backup's file sizes, and optionally their checksums (which
|
|
535
551
|
* requires reading all backed-up data).
|
|
536
552
|
*
|
|
553
|
+
* Set `verifyTables: true` to also prove this build can read the backup
|
|
554
|
+
* before restoring it: every table file is opened, one at a time, and its
|
|
555
|
+
* footer (including `format_version`) and properties must be readable, and
|
|
556
|
+
* every compression algorithm it records must be supported by this build.
|
|
557
|
+
* A checksum-valid table written by a newer or differently built RocksDB
|
|
558
|
+
* otherwise passes and fails on the first read after a restore. Blob files
|
|
559
|
+
* are not checked.
|
|
560
|
+
*
|
|
537
561
|
* When the backup was created with `transactionLogs: true`, the backup's
|
|
538
562
|
* transaction log snapshot (`<backupDir>/transaction_logs/<backupId>/`) is
|
|
539
563
|
* also validated — every log file's header and entry framing must be intact
|
|
@@ -541,7 +565,9 @@ const backups = {
|
|
|
541
565
|
* is a verification failure). Set `verifyTransactionLogs: false` to skip.
|
|
542
566
|
*/
|
|
543
567
|
async verify(backupDir, backupId, options) {
|
|
544
|
-
|
|
568
|
+
const verifyTables = options?.verifyTables;
|
|
569
|
+
if (verifyTables !== void 0 && typeof verifyTables !== "boolean") throw new TypeError(`verifyTables must be a boolean, received ${verifyTables === null ? "null" : typeof verifyTables}`);
|
|
570
|
+
await new Promise((resolve, reject) => nativeBackupVerify(resolve, reject, backupDir, backupId, options?.verifyWithChecksum ?? false, verifyTables === true));
|
|
545
571
|
if (options?.verifyTransactionLogs === false) return;
|
|
546
572
|
const logsDir = (0, node_path.join)(backupDir, TRANSACTION_LOGS_DIRNAME, String(backupId));
|
|
547
573
|
if (!await exists(logsDir)) return;
|
|
@@ -1731,6 +1757,7 @@ var Store = class {
|
|
|
1731
1757
|
/**
|
|
1732
1758
|
* The target maximum size of a transaction log before rotation. Transactions
|
|
1733
1759
|
* are never split across files, so one transaction may exceed this target.
|
|
1760
|
+
* Defaults to 16 MiB. `0` (formerly unlimited) is rejected at open.
|
|
1734
1761
|
*/
|
|
1735
1762
|
transactionLogMaxSize;
|
|
1736
1763
|
/**
|
|
@@ -1745,6 +1772,11 @@ var Store = class {
|
|
|
1745
1772
|
*/
|
|
1746
1773
|
transactionLogsPath;
|
|
1747
1774
|
/**
|
|
1775
|
+
* The transaction log whose batch keys this process originates; seeds the
|
|
1776
|
+
* monotonic timestamp floor at open. See `StoreOptions.timestampFloorLog`.
|
|
1777
|
+
*/
|
|
1778
|
+
timestampFloorLog;
|
|
1779
|
+
/**
|
|
1748
1780
|
* Whether this store's column family participates in the VerificationTable.
|
|
1749
1781
|
*/
|
|
1750
1782
|
verificationTable;
|
|
@@ -1803,6 +1835,7 @@ var Store = class {
|
|
|
1803
1835
|
this.transactionLogMaxSize = options?.transactionLogMaxSize;
|
|
1804
1836
|
this.transactionLogRetention = options?.transactionLogRetention;
|
|
1805
1837
|
this.transactionLogsPath = options?.transactionLogsPath;
|
|
1838
|
+
this.timestampFloorLog = options?.timestampFloorLog;
|
|
1806
1839
|
this.verificationTable = options?.verificationTable;
|
|
1807
1840
|
this.writeBufferSize = options?.writeBufferSize;
|
|
1808
1841
|
this.writeKey = writeKey;
|
|
@@ -2167,6 +2200,7 @@ var Store = class {
|
|
|
2167
2200
|
transactionLogMaxSize: this.transactionLogMaxSize,
|
|
2168
2201
|
transactionLogRetentionMs: this.transactionLogRetention ? parseDuration(this.transactionLogRetention) : void 0,
|
|
2169
2202
|
transactionLogsPath: this.transactionLogsPath,
|
|
2203
|
+
timestampFloorLog: this.timestampFloorLog,
|
|
2170
2204
|
verificationTable: this.verificationTable,
|
|
2171
2205
|
writeBufferSize: this.writeBufferSize
|
|
2172
2206
|
});
|
|
@@ -2648,6 +2682,66 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
2648
2682
|
return this.store.db.getCompression();
|
|
2649
2683
|
}
|
|
2650
2684
|
/**
|
|
2685
|
+
* Dynamically changes the compression algorithm (and optional level) for
|
|
2686
|
+
* this database's column family on an already-open database — no close, no
|
|
2687
|
+
* reopen, and it does not conflict with another handle that already has
|
|
2688
|
+
* this column family open (unlike an explicit open-time `compression`
|
|
2689
|
+
* mismatch). That other handle's live options change too, silently,
|
|
2690
|
+
* without notice; a later explicit reopen of it is compared against the
|
|
2691
|
+
* new value, so a stale request it still holds can then conflict. Backed
|
|
2692
|
+
* by RocksDB's `DB::SetOptions()`, which documents both `compression` and
|
|
2693
|
+
* `blob_compression_type` as mutable column family options.
|
|
2694
|
+
*
|
|
2695
|
+
* This governs only *newly written* files (the next flush and any future
|
|
2696
|
+
* compaction output) going forward; SST and blob files already on disk
|
|
2697
|
+
* keep their existing compression until they are rewritten by a later
|
|
2698
|
+
* compaction. `compact()`'s default options can skip already-bottommost
|
|
2699
|
+
* files; use `compact({ bottommost: true })` to rewrite them under the new
|
|
2700
|
+
* codec. This is the live-mutation counterpart to the open-time
|
|
2701
|
+
* `compression` option — see the README's Compression section for when to
|
|
2702
|
+
* use each.
|
|
2703
|
+
*
|
|
2704
|
+
* @example
|
|
2705
|
+
* ```typescript
|
|
2706
|
+
* const db = RocksDatabase.open('/path/to/db', { compression: 'none' });
|
|
2707
|
+
* db.setCompression({ algorithm: 'zstd', level: 19 });
|
|
2708
|
+
* // new writes are now compressed with zstd; existing SSTs are untouched
|
|
2709
|
+
* // until compacted.
|
|
2710
|
+
* ```
|
|
2711
|
+
*/
|
|
2712
|
+
setCompression(compression) {
|
|
2713
|
+
const { compression: algorithm, compressionLevel } = normalizeCompression(compression);
|
|
2714
|
+
if (algorithm === void 0) throw new TypeError("setCompression requires a compression algorithm");
|
|
2715
|
+
try {
|
|
2716
|
+
this.store.db.setCompression(algorithm, compressionLevel);
|
|
2717
|
+
} catch (error) {
|
|
2718
|
+
if (error?.appliedInMemoryOnly) this.syncStoreCompressionFromLive();
|
|
2719
|
+
throw error;
|
|
2720
|
+
}
|
|
2721
|
+
this.syncStoreCompressionFromLive();
|
|
2722
|
+
}
|
|
2723
|
+
/**
|
|
2724
|
+
* Resyncs the store's cached open-time compression option from the live
|
|
2725
|
+
* database state after `setCompression()`. Also clears
|
|
2726
|
+
* `compressionForAllColumnFamilies`: that flag means "apply this option to
|
|
2727
|
+
* every column family opened for this database", which no longer describes
|
|
2728
|
+
* a live change scoped to a single column family — leaving it set would
|
|
2729
|
+
* restamp every other family with this algorithm on the next
|
|
2730
|
+
* same-instance `close()` + `open()`.
|
|
2731
|
+
*
|
|
2732
|
+
* The flag clear is unconditional (a plain assignment, so it cannot itself
|
|
2733
|
+
* fail) and comes first: a concurrent close racing the live-state read
|
|
2734
|
+
* below throws "Database not open" from the getter, and that read failing
|
|
2735
|
+
* must not also skip the flag clear -- otherwise the same restamp this
|
|
2736
|
+
* method exists to prevent survives on the next open().
|
|
2737
|
+
*/
|
|
2738
|
+
syncStoreCompressionFromLive() {
|
|
2739
|
+
this.store.compressionForAllColumnFamilies = false;
|
|
2740
|
+
try {
|
|
2741
|
+
this.store.compression = this.compression;
|
|
2742
|
+
} catch {}
|
|
2743
|
+
}
|
|
2744
|
+
/**
|
|
2651
2745
|
* The informational-log settings currently in effect for this database,
|
|
2652
2746
|
* read live from RocksDB, as `{ maxLogFileSize, infoLogLevel }`. These are
|
|
2653
2747
|
* database-wide settings (not per-column-family): `maxLogFileSize` bounds
|
|
@@ -3651,18 +3745,11 @@ function findResyncPosition(dataView, from, dataEnd) {
|
|
|
3651
3745
|
}
|
|
3652
3746
|
}
|
|
3653
3747
|
/**
|
|
3654
|
-
* Builds the report for a broken frame, with the offset iteration should continue from.
|
|
3655
|
-
* the written extent here rather than per frame: `getLogFileSize` crosses into native and takes
|
|
3656
|
-
* the store mutex, which a healthy read must never pay.
|
|
3748
|
+
* Builds the report for a broken frame, with the offset iteration should continue from.
|
|
3657
3749
|
*/
|
|
3658
3750
|
function corruptFrame(message, transactionLog, dataView, logBuffer, position, limit, size, readUncommitted) {
|
|
3659
3751
|
let dataEnd = limit;
|
|
3660
|
-
if (readUncommitted)
|
|
3661
|
-
const writtenExtent = transactionLog.getLogFileSize(logBuffer.logId);
|
|
3662
|
-
dataEnd = writtenExtent > 0 ? Math.min(logBuffer.length, writtenExtent) : 0;
|
|
3663
|
-
} catch {
|
|
3664
|
-
dataEnd = 0;
|
|
3665
|
-
}
|
|
3752
|
+
if (readUncommitted) dataEnd = Math.min(logBuffer.length, logBuffer.size ?? readableExtent(logBuffer));
|
|
3666
3753
|
const resyncPosition = findResyncPosition(dataView, position + 1, dataEnd);
|
|
3667
3754
|
return {
|
|
3668
3755
|
error: new CorruptFrameError(message, logBuffer.logId, position, resyncPosition),
|
|
@@ -3692,7 +3779,7 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3692
3779
|
let logId = latestLogId;
|
|
3693
3780
|
let position = 0;
|
|
3694
3781
|
let dataView;
|
|
3695
|
-
let logBuffer = this._currentLogBuffer;
|
|
3782
|
+
let logBuffer = this._currentLogBuffer?.deref();
|
|
3696
3783
|
let foundExactStart = false;
|
|
3697
3784
|
if (start === void 0 && !startFromLastFlushed) {
|
|
3698
3785
|
position = size;
|
|
@@ -3710,7 +3797,7 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3710
3797
|
}
|
|
3711
3798
|
if (logBuffer === void 0 || logBuffer.logId !== logId) {
|
|
3712
3799
|
logBuffer = getLogMemoryMap(this, logId);
|
|
3713
|
-
if (logBuffer && latestLogId === logId && !readUncommitted) this._currentLogBuffer = logBuffer;
|
|
3800
|
+
if (logBuffer && latestLogId === logId && !readUncommitted && this._currentLogBuffer?.deref() !== logBuffer) this._currentLogBuffer = new WeakRef(logBuffer);
|
|
3714
3801
|
if (logBuffer === void 0) {
|
|
3715
3802
|
logBuffer = Buffer.alloc(0);
|
|
3716
3803
|
logBuffer.logId = 0;
|
|
@@ -3720,9 +3807,11 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3720
3807
|
}
|
|
3721
3808
|
}
|
|
3722
3809
|
dataView = logBuffer.dataView;
|
|
3723
|
-
if (latestLogId
|
|
3724
|
-
|
|
3725
|
-
|
|
3810
|
+
if (latestLogId < logId) size = 0;
|
|
3811
|
+
else if (latestLogId > logId) {
|
|
3812
|
+
const cachedSize = logBuffer.size;
|
|
3813
|
+
if (cachedSize === void 0) size = logBuffer.size = readableExtent(logBuffer);
|
|
3814
|
+
else size = cachedSize;
|
|
3726
3815
|
}
|
|
3727
3816
|
return {
|
|
3728
3817
|
[Symbol.iterator]() {
|
|
@@ -3732,15 +3821,15 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3732
3821
|
let timestamp;
|
|
3733
3822
|
if (position >= size) {
|
|
3734
3823
|
const { logId: latestLogId, size: latestSize } = loadLastPosition(transactionLog, !!readUncommitted);
|
|
3735
|
-
size = latestSize;
|
|
3824
|
+
size = latestLogId < logBuffer.logId ? 0 : latestSize;
|
|
3736
3825
|
if (latestLogId > logBuffer.logId) {
|
|
3737
|
-
size = logBuffer.size ?? (logBuffer.size =
|
|
3826
|
+
size = logBuffer.size ?? (logBuffer.size = readableExtent(logBuffer));
|
|
3738
3827
|
if (position >= size) {
|
|
3739
|
-
const nextLogBuffer =
|
|
3828
|
+
const nextLogBuffer = nextReadableLogBuffer(transactionLog, logBuffer.logId, latestLogId);
|
|
3740
3829
|
if (nextLogBuffer) {
|
|
3741
3830
|
dataView = nextLogBuffer.dataView;
|
|
3742
3831
|
logBuffer = nextLogBuffer;
|
|
3743
|
-
if (latestLogId > logBuffer.logId) size = logBuffer.size ?? (logBuffer.size =
|
|
3832
|
+
if (latestLogId > logBuffer.logId) size = logBuffer.size ?? (logBuffer.size = readableExtent(logBuffer));
|
|
3744
3833
|
else size = latestSize;
|
|
3745
3834
|
position = TRANSACTION_LOG_FILE_HEADER_SIZE;
|
|
3746
3835
|
}
|
|
@@ -3791,19 +3880,25 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3791
3880
|
};
|
|
3792
3881
|
if (position >= size) {
|
|
3793
3882
|
const { logId: latestLogId, size: latestSize } = loadLastPosition(transactionLog, !!readUncommitted);
|
|
3794
|
-
size = latestSize;
|
|
3883
|
+
size = latestLogId < logBuffer.logId ? 0 : latestSize;
|
|
3795
3884
|
if (latestLogId > logBuffer.logId) {
|
|
3796
|
-
|
|
3885
|
+
size = logBuffer.size ?? readableExtent(logBuffer);
|
|
3886
|
+
if (!readUncommitted) logBuffer.size = size;
|
|
3887
|
+
if (position < size) continue;
|
|
3888
|
+
const nextLogBuffer = nextReadableLogBuffer(transactionLog, logBuffer.logId, latestLogId);
|
|
3797
3889
|
if (!nextLogBuffer) return {
|
|
3798
3890
|
done: true,
|
|
3799
3891
|
value: void 0
|
|
3800
3892
|
};
|
|
3801
3893
|
logBuffer = nextLogBuffer;
|
|
3802
3894
|
dataView = logBuffer.dataView;
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
if (
|
|
3895
|
+
if (latestLogId === logBuffer.logId) size = latestSize;
|
|
3896
|
+
else {
|
|
3897
|
+
const cachedSize = logBuffer.size;
|
|
3898
|
+
if (cachedSize === void 0) {
|
|
3899
|
+
size = readableExtent(logBuffer);
|
|
3900
|
+
if (!readUncommitted) logBuffer.size = size;
|
|
3901
|
+
} else size = cachedSize;
|
|
3807
3902
|
}
|
|
3808
3903
|
position = TRANSACTION_LOG_FILE_HEADER_SIZE;
|
|
3809
3904
|
}
|
|
@@ -3816,6 +3911,35 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3816
3911
|
}
|
|
3817
3912
|
};
|
|
3818
3913
|
} });
|
|
3914
|
+
/**
|
|
3915
|
+
* Maps the next readable segment after `fromLogId`, skipping a run retention has deleted —
|
|
3916
|
+
* stopping at the hole wedges the iterator there for every later poll (invariant 30).
|
|
3917
|
+
*
|
|
3918
|
+
* The jump target is `_nextLogId()`, the successor over the store's registered segments — *not*
|
|
3919
|
+
* `_findPosition(0)`, which names the bottom of the contiguous run ending at the current segment
|
|
3920
|
+
* and so lands *past* a survivor sitting between two holes.
|
|
3921
|
+
*/
|
|
3922
|
+
function nextReadableLogBuffer(transactionLog, fromLogId, latestLogId) {
|
|
3923
|
+
let candidateLogId = fromLogId + 1;
|
|
3924
|
+
while (candidateLogId <= latestLogId) {
|
|
3925
|
+
const logBuffer = getLogMemoryMap(transactionLog, candidateLogId);
|
|
3926
|
+
if (logBuffer) {
|
|
3927
|
+
if (candidateLogId === latestLogId || readableExtent(logBuffer) > TRANSACTION_LOG_FILE_HEADER_SIZE) return logBuffer;
|
|
3928
|
+
} else if (transactionLog.getLogFileSize(candidateLogId) > TRANSACTION_LOG_FILE_HEADER_SIZE) return;
|
|
3929
|
+
candidateLogId = transactionLog._nextLogId(candidateLogId);
|
|
3930
|
+
if (candidateLogId === 0) return;
|
|
3931
|
+
}
|
|
3932
|
+
}
|
|
3933
|
+
/**
|
|
3934
|
+
* The bound for reading `logBuffer`. Native advances this append-owned extent only after a
|
|
3935
|
+
* successful write. It remains attached to the mapping after purge forgets the file object, so
|
|
3936
|
+
* readers retain committed history without interpreting a partial failed append as another frame.
|
|
3937
|
+
*/
|
|
3938
|
+
function readableExtent(logBuffer) {
|
|
3939
|
+
const extent = logBuffer.readableExtent;
|
|
3940
|
+
if (extent === void 0) throw new Error(`Transaction log buffer ${logBuffer.logId} has no readableExtent`);
|
|
3941
|
+
return Math.min(logBuffer.length, extent);
|
|
3942
|
+
}
|
|
3819
3943
|
function getLogMemoryMap(transactionLog, logId) {
|
|
3820
3944
|
if (logId <= 0) return;
|
|
3821
3945
|
let logBuffer = transactionLog._logBuffers.get(logId)?.deref();
|
|
@@ -3861,7 +3985,7 @@ function loadLastPosition(transactionLog, readUncommitted) {
|
|
|
3861
3985
|
//#region src/index.ts
|
|
3862
3986
|
const versions = {
|
|
3863
3987
|
rocksdb: version,
|
|
3864
|
-
"rocksdb-js": "2.
|
|
3988
|
+
"rocksdb-js": "2.12.0"
|
|
3865
3989
|
};
|
|
3866
3990
|
|
|
3867
3991
|
//#endregion
|
|
@@ -3879,6 +4003,7 @@ exports.constants = constants;
|
|
|
3879
4003
|
exports.coolTransactionLogs = coolTransactionLogs;
|
|
3880
4004
|
exports.currentThreadId = currentThreadId;
|
|
3881
4005
|
exports.fileLockRelease = fileLockRelease;
|
|
4006
|
+
exports.getRegistryStatus = getRegistryStatus;
|
|
3882
4007
|
exports.getWriteBufferManagerStats = getWriteBufferManagerStats;
|
|
3883
4008
|
exports.parseTransactionLog = parseTransactionLog;
|
|
3884
4009
|
exports.registryStatus = registryStatus;
|