@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.mjs
CHANGED
|
@@ -105,7 +105,17 @@ const NativeDatabase = binding.Database;
|
|
|
105
105
|
const NativeIterator = binding.Iterator;
|
|
106
106
|
const NativeTransaction = binding.Transaction;
|
|
107
107
|
const TransactionLog = binding.TransactionLog;
|
|
108
|
-
|
|
108
|
+
/**
|
|
109
|
+
* Returns the status of every database currently held by the native registry.
|
|
110
|
+
* Walks every registered descriptor, so it samples live state rather than
|
|
111
|
+
* reading a stored property.
|
|
112
|
+
*/
|
|
113
|
+
const getRegistryStatus = binding.registryStatus;
|
|
114
|
+
/**
|
|
115
|
+
* @deprecated Use {@link getRegistryStatus} instead. This alias will be removed
|
|
116
|
+
* in v3.0.0.
|
|
117
|
+
*/
|
|
118
|
+
const registryStatus = getRegistryStatus;
|
|
109
119
|
/**
|
|
110
120
|
* Flushes every open database and waits for outstanding compactions, then
|
|
111
121
|
* releases the registry.
|
|
@@ -169,6 +179,12 @@ const coolTransactionLogs = binding.coolTransactionLogs;
|
|
|
169
179
|
*/
|
|
170
180
|
const transactionLogMapCount = binding.transactionLogMapCount;
|
|
171
181
|
/**
|
|
182
|
+
* Number of coordinated-retry wake callbacks registered on verification-table
|
|
183
|
+
* locks across the process. Internal — used by tests to verify that a park
|
|
184
|
+
* ending by timeout, env teardown, or close leaves no registration behind.
|
|
185
|
+
*/
|
|
186
|
+
const lockWakeCallbackCount = binding.lockWakeCallbackCount;
|
|
187
|
+
/**
|
|
172
188
|
* Test-only: force the next `count` transaction commits to fail with TryAgain (rolled back, so
|
|
173
189
|
* no data is committed), reproducing a stranded-snapshot conflict deterministically. Pass 0 to
|
|
174
190
|
* disarm. Used by the ERR_TRY_AGAIN retry regression test.
|
|
@@ -509,6 +525,14 @@ const backups = {
|
|
|
509
525
|
* Verifies a backup's file sizes, and optionally their checksums (which
|
|
510
526
|
* requires reading all backed-up data).
|
|
511
527
|
*
|
|
528
|
+
* Set `verifyTables: true` to also prove this build can read the backup
|
|
529
|
+
* before restoring it: every table file is opened, one at a time, and its
|
|
530
|
+
* footer (including `format_version`) and properties must be readable, and
|
|
531
|
+
* every compression algorithm it records must be supported by this build.
|
|
532
|
+
* A checksum-valid table written by a newer or differently built RocksDB
|
|
533
|
+
* otherwise passes and fails on the first read after a restore. Blob files
|
|
534
|
+
* are not checked.
|
|
535
|
+
*
|
|
512
536
|
* When the backup was created with `transactionLogs: true`, the backup's
|
|
513
537
|
* transaction log snapshot (`<backupDir>/transaction_logs/<backupId>/`) is
|
|
514
538
|
* also validated — every log file's header and entry framing must be intact
|
|
@@ -516,7 +540,9 @@ const backups = {
|
|
|
516
540
|
* is a verification failure). Set `verifyTransactionLogs: false` to skip.
|
|
517
541
|
*/
|
|
518
542
|
async verify(backupDir, backupId, options) {
|
|
519
|
-
|
|
543
|
+
const verifyTables = options?.verifyTables;
|
|
544
|
+
if (verifyTables !== void 0 && typeof verifyTables !== "boolean") throw new TypeError(`verifyTables must be a boolean, received ${verifyTables === null ? "null" : typeof verifyTables}`);
|
|
545
|
+
await new Promise((resolve, reject) => nativeBackupVerify(resolve, reject, backupDir, backupId, options?.verifyWithChecksum ?? false, verifyTables === true));
|
|
520
546
|
if (options?.verifyTransactionLogs === false) return;
|
|
521
547
|
const logsDir = join(backupDir, TRANSACTION_LOGS_DIRNAME, String(backupId));
|
|
522
548
|
if (!await exists(logsDir)) return;
|
|
@@ -1706,6 +1732,7 @@ var Store = class {
|
|
|
1706
1732
|
/**
|
|
1707
1733
|
* The target maximum size of a transaction log before rotation. Transactions
|
|
1708
1734
|
* are never split across files, so one transaction may exceed this target.
|
|
1735
|
+
* Defaults to 16 MiB. `0` (formerly unlimited) is rejected at open.
|
|
1709
1736
|
*/
|
|
1710
1737
|
transactionLogMaxSize;
|
|
1711
1738
|
/**
|
|
@@ -1720,6 +1747,11 @@ var Store = class {
|
|
|
1720
1747
|
*/
|
|
1721
1748
|
transactionLogsPath;
|
|
1722
1749
|
/**
|
|
1750
|
+
* The transaction log whose batch keys this process originates; seeds the
|
|
1751
|
+
* monotonic timestamp floor at open. See `StoreOptions.timestampFloorLog`.
|
|
1752
|
+
*/
|
|
1753
|
+
timestampFloorLog;
|
|
1754
|
+
/**
|
|
1723
1755
|
* Whether this store's column family participates in the VerificationTable.
|
|
1724
1756
|
*/
|
|
1725
1757
|
verificationTable;
|
|
@@ -1778,6 +1810,7 @@ var Store = class {
|
|
|
1778
1810
|
this.transactionLogMaxSize = options?.transactionLogMaxSize;
|
|
1779
1811
|
this.transactionLogRetention = options?.transactionLogRetention;
|
|
1780
1812
|
this.transactionLogsPath = options?.transactionLogsPath;
|
|
1813
|
+
this.timestampFloorLog = options?.timestampFloorLog;
|
|
1781
1814
|
this.verificationTable = options?.verificationTable;
|
|
1782
1815
|
this.writeBufferSize = options?.writeBufferSize;
|
|
1783
1816
|
this.writeKey = writeKey;
|
|
@@ -2142,6 +2175,7 @@ var Store = class {
|
|
|
2142
2175
|
transactionLogMaxSize: this.transactionLogMaxSize,
|
|
2143
2176
|
transactionLogRetentionMs: this.transactionLogRetention ? parseDuration(this.transactionLogRetention) : void 0,
|
|
2144
2177
|
transactionLogsPath: this.transactionLogsPath,
|
|
2178
|
+
timestampFloorLog: this.timestampFloorLog,
|
|
2145
2179
|
verificationTable: this.verificationTable,
|
|
2146
2180
|
writeBufferSize: this.writeBufferSize
|
|
2147
2181
|
});
|
|
@@ -2623,6 +2657,66 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
2623
2657
|
return this.store.db.getCompression();
|
|
2624
2658
|
}
|
|
2625
2659
|
/**
|
|
2660
|
+
* Dynamically changes the compression algorithm (and optional level) for
|
|
2661
|
+
* this database's column family on an already-open database — no close, no
|
|
2662
|
+
* reopen, and it does not conflict with another handle that already has
|
|
2663
|
+
* this column family open (unlike an explicit open-time `compression`
|
|
2664
|
+
* mismatch). That other handle's live options change too, silently,
|
|
2665
|
+
* without notice; a later explicit reopen of it is compared against the
|
|
2666
|
+
* new value, so a stale request it still holds can then conflict. Backed
|
|
2667
|
+
* by RocksDB's `DB::SetOptions()`, which documents both `compression` and
|
|
2668
|
+
* `blob_compression_type` as mutable column family options.
|
|
2669
|
+
*
|
|
2670
|
+
* This governs only *newly written* files (the next flush and any future
|
|
2671
|
+
* compaction output) going forward; SST and blob files already on disk
|
|
2672
|
+
* keep their existing compression until they are rewritten by a later
|
|
2673
|
+
* compaction. `compact()`'s default options can skip already-bottommost
|
|
2674
|
+
* files; use `compact({ bottommost: true })` to rewrite them under the new
|
|
2675
|
+
* codec. This is the live-mutation counterpart to the open-time
|
|
2676
|
+
* `compression` option — see the README's Compression section for when to
|
|
2677
|
+
* use each.
|
|
2678
|
+
*
|
|
2679
|
+
* @example
|
|
2680
|
+
* ```typescript
|
|
2681
|
+
* const db = RocksDatabase.open('/path/to/db', { compression: 'none' });
|
|
2682
|
+
* db.setCompression({ algorithm: 'zstd', level: 19 });
|
|
2683
|
+
* // new writes are now compressed with zstd; existing SSTs are untouched
|
|
2684
|
+
* // until compacted.
|
|
2685
|
+
* ```
|
|
2686
|
+
*/
|
|
2687
|
+
setCompression(compression) {
|
|
2688
|
+
const { compression: algorithm, compressionLevel } = normalizeCompression(compression);
|
|
2689
|
+
if (algorithm === void 0) throw new TypeError("setCompression requires a compression algorithm");
|
|
2690
|
+
try {
|
|
2691
|
+
this.store.db.setCompression(algorithm, compressionLevel);
|
|
2692
|
+
} catch (error) {
|
|
2693
|
+
if (error?.appliedInMemoryOnly) this.syncStoreCompressionFromLive();
|
|
2694
|
+
throw error;
|
|
2695
|
+
}
|
|
2696
|
+
this.syncStoreCompressionFromLive();
|
|
2697
|
+
}
|
|
2698
|
+
/**
|
|
2699
|
+
* Resyncs the store's cached open-time compression option from the live
|
|
2700
|
+
* database state after `setCompression()`. Also clears
|
|
2701
|
+
* `compressionForAllColumnFamilies`: that flag means "apply this option to
|
|
2702
|
+
* every column family opened for this database", which no longer describes
|
|
2703
|
+
* a live change scoped to a single column family — leaving it set would
|
|
2704
|
+
* restamp every other family with this algorithm on the next
|
|
2705
|
+
* same-instance `close()` + `open()`.
|
|
2706
|
+
*
|
|
2707
|
+
* The flag clear is unconditional (a plain assignment, so it cannot itself
|
|
2708
|
+
* fail) and comes first: a concurrent close racing the live-state read
|
|
2709
|
+
* below throws "Database not open" from the getter, and that read failing
|
|
2710
|
+
* must not also skip the flag clear -- otherwise the same restamp this
|
|
2711
|
+
* method exists to prevent survives on the next open().
|
|
2712
|
+
*/
|
|
2713
|
+
syncStoreCompressionFromLive() {
|
|
2714
|
+
this.store.compressionForAllColumnFamilies = false;
|
|
2715
|
+
try {
|
|
2716
|
+
this.store.compression = this.compression;
|
|
2717
|
+
} catch {}
|
|
2718
|
+
}
|
|
2719
|
+
/**
|
|
2626
2720
|
* The informational-log settings currently in effect for this database,
|
|
2627
2721
|
* read live from RocksDB, as `{ maxLogFileSize, infoLogLevel }`. These are
|
|
2628
2722
|
* database-wide settings (not per-column-family): `maxLogFileSize` bounds
|
|
@@ -3626,18 +3720,11 @@ function findResyncPosition(dataView, from, dataEnd) {
|
|
|
3626
3720
|
}
|
|
3627
3721
|
}
|
|
3628
3722
|
/**
|
|
3629
|
-
* Builds the report for a broken frame, with the offset iteration should continue from.
|
|
3630
|
-
* the written extent here rather than per frame: `getLogFileSize` crosses into native and takes
|
|
3631
|
-
* the store mutex, which a healthy read must never pay.
|
|
3723
|
+
* Builds the report for a broken frame, with the offset iteration should continue from.
|
|
3632
3724
|
*/
|
|
3633
3725
|
function corruptFrame(message, transactionLog, dataView, logBuffer, position, limit, size, readUncommitted) {
|
|
3634
3726
|
let dataEnd = limit;
|
|
3635
|
-
if (readUncommitted)
|
|
3636
|
-
const writtenExtent = transactionLog.getLogFileSize(logBuffer.logId);
|
|
3637
|
-
dataEnd = writtenExtent > 0 ? Math.min(logBuffer.length, writtenExtent) : 0;
|
|
3638
|
-
} catch {
|
|
3639
|
-
dataEnd = 0;
|
|
3640
|
-
}
|
|
3727
|
+
if (readUncommitted) dataEnd = Math.min(logBuffer.length, logBuffer.size ?? readableExtent(logBuffer));
|
|
3641
3728
|
const resyncPosition = findResyncPosition(dataView, position + 1, dataEnd);
|
|
3642
3729
|
return {
|
|
3643
3730
|
error: new CorruptFrameError(message, logBuffer.logId, position, resyncPosition),
|
|
@@ -3667,7 +3754,7 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3667
3754
|
let logId = latestLogId;
|
|
3668
3755
|
let position = 0;
|
|
3669
3756
|
let dataView;
|
|
3670
|
-
let logBuffer = this._currentLogBuffer;
|
|
3757
|
+
let logBuffer = this._currentLogBuffer?.deref();
|
|
3671
3758
|
let foundExactStart = false;
|
|
3672
3759
|
if (start === void 0 && !startFromLastFlushed) {
|
|
3673
3760
|
position = size;
|
|
@@ -3685,7 +3772,7 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3685
3772
|
}
|
|
3686
3773
|
if (logBuffer === void 0 || logBuffer.logId !== logId) {
|
|
3687
3774
|
logBuffer = getLogMemoryMap(this, logId);
|
|
3688
|
-
if (logBuffer && latestLogId === logId && !readUncommitted) this._currentLogBuffer = logBuffer;
|
|
3775
|
+
if (logBuffer && latestLogId === logId && !readUncommitted && this._currentLogBuffer?.deref() !== logBuffer) this._currentLogBuffer = new WeakRef(logBuffer);
|
|
3689
3776
|
if (logBuffer === void 0) {
|
|
3690
3777
|
logBuffer = Buffer.alloc(0);
|
|
3691
3778
|
logBuffer.logId = 0;
|
|
@@ -3695,9 +3782,11 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3695
3782
|
}
|
|
3696
3783
|
}
|
|
3697
3784
|
dataView = logBuffer.dataView;
|
|
3698
|
-
if (latestLogId
|
|
3699
|
-
|
|
3700
|
-
|
|
3785
|
+
if (latestLogId < logId) size = 0;
|
|
3786
|
+
else if (latestLogId > logId) {
|
|
3787
|
+
const cachedSize = logBuffer.size;
|
|
3788
|
+
if (cachedSize === void 0) size = logBuffer.size = readableExtent(logBuffer);
|
|
3789
|
+
else size = cachedSize;
|
|
3701
3790
|
}
|
|
3702
3791
|
return {
|
|
3703
3792
|
[Symbol.iterator]() {
|
|
@@ -3707,15 +3796,15 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3707
3796
|
let timestamp;
|
|
3708
3797
|
if (position >= size) {
|
|
3709
3798
|
const { logId: latestLogId, size: latestSize } = loadLastPosition(transactionLog, !!readUncommitted);
|
|
3710
|
-
size = latestSize;
|
|
3799
|
+
size = latestLogId < logBuffer.logId ? 0 : latestSize;
|
|
3711
3800
|
if (latestLogId > logBuffer.logId) {
|
|
3712
|
-
size = logBuffer.size ?? (logBuffer.size =
|
|
3801
|
+
size = logBuffer.size ?? (logBuffer.size = readableExtent(logBuffer));
|
|
3713
3802
|
if (position >= size) {
|
|
3714
|
-
const nextLogBuffer =
|
|
3803
|
+
const nextLogBuffer = nextReadableLogBuffer(transactionLog, logBuffer.logId, latestLogId);
|
|
3715
3804
|
if (nextLogBuffer) {
|
|
3716
3805
|
dataView = nextLogBuffer.dataView;
|
|
3717
3806
|
logBuffer = nextLogBuffer;
|
|
3718
|
-
if (latestLogId > logBuffer.logId) size = logBuffer.size ?? (logBuffer.size =
|
|
3807
|
+
if (latestLogId > logBuffer.logId) size = logBuffer.size ?? (logBuffer.size = readableExtent(logBuffer));
|
|
3719
3808
|
else size = latestSize;
|
|
3720
3809
|
position = TRANSACTION_LOG_FILE_HEADER_SIZE;
|
|
3721
3810
|
}
|
|
@@ -3766,19 +3855,25 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3766
3855
|
};
|
|
3767
3856
|
if (position >= size) {
|
|
3768
3857
|
const { logId: latestLogId, size: latestSize } = loadLastPosition(transactionLog, !!readUncommitted);
|
|
3769
|
-
size = latestSize;
|
|
3858
|
+
size = latestLogId < logBuffer.logId ? 0 : latestSize;
|
|
3770
3859
|
if (latestLogId > logBuffer.logId) {
|
|
3771
|
-
|
|
3860
|
+
size = logBuffer.size ?? readableExtent(logBuffer);
|
|
3861
|
+
if (!readUncommitted) logBuffer.size = size;
|
|
3862
|
+
if (position < size) continue;
|
|
3863
|
+
const nextLogBuffer = nextReadableLogBuffer(transactionLog, logBuffer.logId, latestLogId);
|
|
3772
3864
|
if (!nextLogBuffer) return {
|
|
3773
3865
|
done: true,
|
|
3774
3866
|
value: void 0
|
|
3775
3867
|
};
|
|
3776
3868
|
logBuffer = nextLogBuffer;
|
|
3777
3869
|
dataView = logBuffer.dataView;
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
if (
|
|
3870
|
+
if (latestLogId === logBuffer.logId) size = latestSize;
|
|
3871
|
+
else {
|
|
3872
|
+
const cachedSize = logBuffer.size;
|
|
3873
|
+
if (cachedSize === void 0) {
|
|
3874
|
+
size = readableExtent(logBuffer);
|
|
3875
|
+
if (!readUncommitted) logBuffer.size = size;
|
|
3876
|
+
} else size = cachedSize;
|
|
3782
3877
|
}
|
|
3783
3878
|
position = TRANSACTION_LOG_FILE_HEADER_SIZE;
|
|
3784
3879
|
}
|
|
@@ -3791,6 +3886,35 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3791
3886
|
}
|
|
3792
3887
|
};
|
|
3793
3888
|
} });
|
|
3889
|
+
/**
|
|
3890
|
+
* Maps the next readable segment after `fromLogId`, skipping a run retention has deleted —
|
|
3891
|
+
* stopping at the hole wedges the iterator there for every later poll (invariant 30).
|
|
3892
|
+
*
|
|
3893
|
+
* The jump target is `_nextLogId()`, the successor over the store's registered segments — *not*
|
|
3894
|
+
* `_findPosition(0)`, which names the bottom of the contiguous run ending at the current segment
|
|
3895
|
+
* and so lands *past* a survivor sitting between two holes.
|
|
3896
|
+
*/
|
|
3897
|
+
function nextReadableLogBuffer(transactionLog, fromLogId, latestLogId) {
|
|
3898
|
+
let candidateLogId = fromLogId + 1;
|
|
3899
|
+
while (candidateLogId <= latestLogId) {
|
|
3900
|
+
const logBuffer = getLogMemoryMap(transactionLog, candidateLogId);
|
|
3901
|
+
if (logBuffer) {
|
|
3902
|
+
if (candidateLogId === latestLogId || readableExtent(logBuffer) > TRANSACTION_LOG_FILE_HEADER_SIZE) return logBuffer;
|
|
3903
|
+
} else if (transactionLog.getLogFileSize(candidateLogId) > TRANSACTION_LOG_FILE_HEADER_SIZE) return;
|
|
3904
|
+
candidateLogId = transactionLog._nextLogId(candidateLogId);
|
|
3905
|
+
if (candidateLogId === 0) return;
|
|
3906
|
+
}
|
|
3907
|
+
}
|
|
3908
|
+
/**
|
|
3909
|
+
* The bound for reading `logBuffer`. Native advances this append-owned extent only after a
|
|
3910
|
+
* successful write. It remains attached to the mapping after purge forgets the file object, so
|
|
3911
|
+
* readers retain committed history without interpreting a partial failed append as another frame.
|
|
3912
|
+
*/
|
|
3913
|
+
function readableExtent(logBuffer) {
|
|
3914
|
+
const extent = logBuffer.readableExtent;
|
|
3915
|
+
if (extent === void 0) throw new Error(`Transaction log buffer ${logBuffer.logId} has no readableExtent`);
|
|
3916
|
+
return Math.min(logBuffer.length, extent);
|
|
3917
|
+
}
|
|
3794
3918
|
function getLogMemoryMap(transactionLog, logId) {
|
|
3795
3919
|
if (logId <= 0) return;
|
|
3796
3920
|
let logBuffer = transactionLog._logBuffers.get(logId)?.deref();
|
|
@@ -3836,9 +3960,9 @@ function loadLastPosition(transactionLog, readUncommitted) {
|
|
|
3836
3960
|
//#region src/index.ts
|
|
3837
3961
|
const versions = {
|
|
3838
3962
|
rocksdb: version,
|
|
3839
|
-
"rocksdb-js": "2.
|
|
3963
|
+
"rocksdb-js": "2.12.0"
|
|
3840
3964
|
};
|
|
3841
3965
|
|
|
3842
3966
|
//#endregion
|
|
3843
|
-
export { BackgroundError, CorruptFrameError, CountEstimator, DBI, DBIterator, RocksDatabase, Store, Transaction, TransactionLog, backups, constants, coolTransactionLogs, currentThreadId, fileLockRelease, getWriteBufferManagerStats, parseTransactionLog, registryStatus, shutdown, stats, steadyClockNow, supportedCompression, tryFileLock, validateTransactionLogStore, versions };
|
|
3967
|
+
export { BackgroundError, CorruptFrameError, CountEstimator, DBI, DBIterator, RocksDatabase, Store, Transaction, TransactionLog, backups, constants, coolTransactionLogs, currentThreadId, fileLockRelease, getRegistryStatus, getWriteBufferManagerStats, parseTransactionLog, registryStatus, shutdown, stats, steadyClockNow, supportedCompression, tryFileLock, validateTransactionLogStore, versions };
|
|
3844
3968
|
//# sourceMappingURL=index.mjs.map
|