@harperfast/rocksdb-js 2.10.0 → 2.11.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 +227 -46
- package/bin/rocksdb-js.mjs +49 -5
- package/dist/index.cjs +141 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +124 -5
- package/dist/index.d.mts +124 -5
- package/dist/index.mjs +141 -27
- package/dist/index.mjs.map +1 -1
- package/docs/stats.md +48 -47
- package/docs/transaction-log.md +120 -1
- package/docs/transaction-timestamp-integrity-design.md +70 -8
- package/package.json +16 -16
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.
|
|
@@ -1731,6 +1747,7 @@ var Store = class {
|
|
|
1731
1747
|
/**
|
|
1732
1748
|
* The target maximum size of a transaction log before rotation. Transactions
|
|
1733
1749
|
* are never split across files, so one transaction may exceed this target.
|
|
1750
|
+
* Defaults to 16 MiB. `0` (formerly unlimited) is rejected at open.
|
|
1734
1751
|
*/
|
|
1735
1752
|
transactionLogMaxSize;
|
|
1736
1753
|
/**
|
|
@@ -1745,6 +1762,11 @@ var Store = class {
|
|
|
1745
1762
|
*/
|
|
1746
1763
|
transactionLogsPath;
|
|
1747
1764
|
/**
|
|
1765
|
+
* The transaction log whose batch keys this process originates; seeds the
|
|
1766
|
+
* monotonic timestamp floor at open. See `StoreOptions.timestampFloorLog`.
|
|
1767
|
+
*/
|
|
1768
|
+
timestampFloorLog;
|
|
1769
|
+
/**
|
|
1748
1770
|
* Whether this store's column family participates in the VerificationTable.
|
|
1749
1771
|
*/
|
|
1750
1772
|
verificationTable;
|
|
@@ -1803,6 +1825,7 @@ var Store = class {
|
|
|
1803
1825
|
this.transactionLogMaxSize = options?.transactionLogMaxSize;
|
|
1804
1826
|
this.transactionLogRetention = options?.transactionLogRetention;
|
|
1805
1827
|
this.transactionLogsPath = options?.transactionLogsPath;
|
|
1828
|
+
this.timestampFloorLog = options?.timestampFloorLog;
|
|
1806
1829
|
this.verificationTable = options?.verificationTable;
|
|
1807
1830
|
this.writeBufferSize = options?.writeBufferSize;
|
|
1808
1831
|
this.writeKey = writeKey;
|
|
@@ -2167,6 +2190,7 @@ var Store = class {
|
|
|
2167
2190
|
transactionLogMaxSize: this.transactionLogMaxSize,
|
|
2168
2191
|
transactionLogRetentionMs: this.transactionLogRetention ? parseDuration(this.transactionLogRetention) : void 0,
|
|
2169
2192
|
transactionLogsPath: this.transactionLogsPath,
|
|
2193
|
+
timestampFloorLog: this.timestampFloorLog,
|
|
2170
2194
|
verificationTable: this.verificationTable,
|
|
2171
2195
|
writeBufferSize: this.writeBufferSize
|
|
2172
2196
|
});
|
|
@@ -2648,6 +2672,66 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
2648
2672
|
return this.store.db.getCompression();
|
|
2649
2673
|
}
|
|
2650
2674
|
/**
|
|
2675
|
+
* Dynamically changes the compression algorithm (and optional level) for
|
|
2676
|
+
* this database's column family on an already-open database — no close, no
|
|
2677
|
+
* reopen, and it does not conflict with another handle that already has
|
|
2678
|
+
* this column family open (unlike an explicit open-time `compression`
|
|
2679
|
+
* mismatch). That other handle's live options change too, silently,
|
|
2680
|
+
* without notice; a later explicit reopen of it is compared against the
|
|
2681
|
+
* new value, so a stale request it still holds can then conflict. Backed
|
|
2682
|
+
* by RocksDB's `DB::SetOptions()`, which documents both `compression` and
|
|
2683
|
+
* `blob_compression_type` as mutable column family options.
|
|
2684
|
+
*
|
|
2685
|
+
* This governs only *newly written* files (the next flush and any future
|
|
2686
|
+
* compaction output) going forward; SST and blob files already on disk
|
|
2687
|
+
* keep their existing compression until they are rewritten by a later
|
|
2688
|
+
* compaction. `compact()`'s default options can skip already-bottommost
|
|
2689
|
+
* files; use `compact({ bottommost: true })` to rewrite them under the new
|
|
2690
|
+
* codec. This is the live-mutation counterpart to the open-time
|
|
2691
|
+
* `compression` option — see the README's Compression section for when to
|
|
2692
|
+
* use each.
|
|
2693
|
+
*
|
|
2694
|
+
* @example
|
|
2695
|
+
* ```typescript
|
|
2696
|
+
* const db = RocksDatabase.open('/path/to/db', { compression: 'none' });
|
|
2697
|
+
* db.setCompression({ algorithm: 'zstd', level: 19 });
|
|
2698
|
+
* // new writes are now compressed with zstd; existing SSTs are untouched
|
|
2699
|
+
* // until compacted.
|
|
2700
|
+
* ```
|
|
2701
|
+
*/
|
|
2702
|
+
setCompression(compression) {
|
|
2703
|
+
const { compression: algorithm, compressionLevel } = normalizeCompression(compression);
|
|
2704
|
+
if (algorithm === void 0) throw new TypeError("setCompression requires a compression algorithm");
|
|
2705
|
+
try {
|
|
2706
|
+
this.store.db.setCompression(algorithm, compressionLevel);
|
|
2707
|
+
} catch (error) {
|
|
2708
|
+
if (error?.appliedInMemoryOnly) this.syncStoreCompressionFromLive();
|
|
2709
|
+
throw error;
|
|
2710
|
+
}
|
|
2711
|
+
this.syncStoreCompressionFromLive();
|
|
2712
|
+
}
|
|
2713
|
+
/**
|
|
2714
|
+
* Resyncs the store's cached open-time compression option from the live
|
|
2715
|
+
* database state after `setCompression()`. Also clears
|
|
2716
|
+
* `compressionForAllColumnFamilies`: that flag means "apply this option to
|
|
2717
|
+
* every column family opened for this database", which no longer describes
|
|
2718
|
+
* a live change scoped to a single column family — leaving it set would
|
|
2719
|
+
* restamp every other family with this algorithm on the next
|
|
2720
|
+
* same-instance `close()` + `open()`.
|
|
2721
|
+
*
|
|
2722
|
+
* The flag clear is unconditional (a plain assignment, so it cannot itself
|
|
2723
|
+
* fail) and comes first: a concurrent close racing the live-state read
|
|
2724
|
+
* below throws "Database not open" from the getter, and that read failing
|
|
2725
|
+
* must not also skip the flag clear -- otherwise the same restamp this
|
|
2726
|
+
* method exists to prevent survives on the next open().
|
|
2727
|
+
*/
|
|
2728
|
+
syncStoreCompressionFromLive() {
|
|
2729
|
+
this.store.compressionForAllColumnFamilies = false;
|
|
2730
|
+
try {
|
|
2731
|
+
this.store.compression = this.compression;
|
|
2732
|
+
} catch {}
|
|
2733
|
+
}
|
|
2734
|
+
/**
|
|
2651
2735
|
* The informational-log settings currently in effect for this database,
|
|
2652
2736
|
* read live from RocksDB, as `{ maxLogFileSize, infoLogLevel }`. These are
|
|
2653
2737
|
* database-wide settings (not per-column-family): `maxLogFileSize` bounds
|
|
@@ -3651,18 +3735,11 @@ function findResyncPosition(dataView, from, dataEnd) {
|
|
|
3651
3735
|
}
|
|
3652
3736
|
}
|
|
3653
3737
|
/**
|
|
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.
|
|
3738
|
+
* Builds the report for a broken frame, with the offset iteration should continue from.
|
|
3657
3739
|
*/
|
|
3658
3740
|
function corruptFrame(message, transactionLog, dataView, logBuffer, position, limit, size, readUncommitted) {
|
|
3659
3741
|
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
|
-
}
|
|
3742
|
+
if (readUncommitted) dataEnd = Math.min(logBuffer.length, logBuffer.size ?? readableExtent(logBuffer));
|
|
3666
3743
|
const resyncPosition = findResyncPosition(dataView, position + 1, dataEnd);
|
|
3667
3744
|
return {
|
|
3668
3745
|
error: new CorruptFrameError(message, logBuffer.logId, position, resyncPosition),
|
|
@@ -3692,7 +3769,7 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3692
3769
|
let logId = latestLogId;
|
|
3693
3770
|
let position = 0;
|
|
3694
3771
|
let dataView;
|
|
3695
|
-
let logBuffer = this._currentLogBuffer;
|
|
3772
|
+
let logBuffer = this._currentLogBuffer?.deref();
|
|
3696
3773
|
let foundExactStart = false;
|
|
3697
3774
|
if (start === void 0 && !startFromLastFlushed) {
|
|
3698
3775
|
position = size;
|
|
@@ -3710,7 +3787,7 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3710
3787
|
}
|
|
3711
3788
|
if (logBuffer === void 0 || logBuffer.logId !== logId) {
|
|
3712
3789
|
logBuffer = getLogMemoryMap(this, logId);
|
|
3713
|
-
if (logBuffer && latestLogId === logId && !readUncommitted) this._currentLogBuffer = logBuffer;
|
|
3790
|
+
if (logBuffer && latestLogId === logId && !readUncommitted && this._currentLogBuffer?.deref() !== logBuffer) this._currentLogBuffer = new WeakRef(logBuffer);
|
|
3714
3791
|
if (logBuffer === void 0) {
|
|
3715
3792
|
logBuffer = Buffer.alloc(0);
|
|
3716
3793
|
logBuffer.logId = 0;
|
|
@@ -3720,9 +3797,11 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3720
3797
|
}
|
|
3721
3798
|
}
|
|
3722
3799
|
dataView = logBuffer.dataView;
|
|
3723
|
-
if (latestLogId
|
|
3724
|
-
|
|
3725
|
-
|
|
3800
|
+
if (latestLogId < logId) size = 0;
|
|
3801
|
+
else if (latestLogId > logId) {
|
|
3802
|
+
const cachedSize = logBuffer.size;
|
|
3803
|
+
if (cachedSize === void 0) size = logBuffer.size = readableExtent(logBuffer);
|
|
3804
|
+
else size = cachedSize;
|
|
3726
3805
|
}
|
|
3727
3806
|
return {
|
|
3728
3807
|
[Symbol.iterator]() {
|
|
@@ -3732,15 +3811,15 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3732
3811
|
let timestamp;
|
|
3733
3812
|
if (position >= size) {
|
|
3734
3813
|
const { logId: latestLogId, size: latestSize } = loadLastPosition(transactionLog, !!readUncommitted);
|
|
3735
|
-
size = latestSize;
|
|
3814
|
+
size = latestLogId < logBuffer.logId ? 0 : latestSize;
|
|
3736
3815
|
if (latestLogId > logBuffer.logId) {
|
|
3737
|
-
size = logBuffer.size ?? (logBuffer.size =
|
|
3816
|
+
size = logBuffer.size ?? (logBuffer.size = readableExtent(logBuffer));
|
|
3738
3817
|
if (position >= size) {
|
|
3739
|
-
const nextLogBuffer =
|
|
3818
|
+
const nextLogBuffer = nextReadableLogBuffer(transactionLog, logBuffer.logId, latestLogId);
|
|
3740
3819
|
if (nextLogBuffer) {
|
|
3741
3820
|
dataView = nextLogBuffer.dataView;
|
|
3742
3821
|
logBuffer = nextLogBuffer;
|
|
3743
|
-
if (latestLogId > logBuffer.logId) size = logBuffer.size ?? (logBuffer.size =
|
|
3822
|
+
if (latestLogId > logBuffer.logId) size = logBuffer.size ?? (logBuffer.size = readableExtent(logBuffer));
|
|
3744
3823
|
else size = latestSize;
|
|
3745
3824
|
position = TRANSACTION_LOG_FILE_HEADER_SIZE;
|
|
3746
3825
|
}
|
|
@@ -3791,19 +3870,25 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3791
3870
|
};
|
|
3792
3871
|
if (position >= size) {
|
|
3793
3872
|
const { logId: latestLogId, size: latestSize } = loadLastPosition(transactionLog, !!readUncommitted);
|
|
3794
|
-
size = latestSize;
|
|
3873
|
+
size = latestLogId < logBuffer.logId ? 0 : latestSize;
|
|
3795
3874
|
if (latestLogId > logBuffer.logId) {
|
|
3796
|
-
|
|
3875
|
+
size = logBuffer.size ?? readableExtent(logBuffer);
|
|
3876
|
+
if (!readUncommitted) logBuffer.size = size;
|
|
3877
|
+
if (position < size) continue;
|
|
3878
|
+
const nextLogBuffer = nextReadableLogBuffer(transactionLog, logBuffer.logId, latestLogId);
|
|
3797
3879
|
if (!nextLogBuffer) return {
|
|
3798
3880
|
done: true,
|
|
3799
3881
|
value: void 0
|
|
3800
3882
|
};
|
|
3801
3883
|
logBuffer = nextLogBuffer;
|
|
3802
3884
|
dataView = logBuffer.dataView;
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
if (
|
|
3885
|
+
if (latestLogId === logBuffer.logId) size = latestSize;
|
|
3886
|
+
else {
|
|
3887
|
+
const cachedSize = logBuffer.size;
|
|
3888
|
+
if (cachedSize === void 0) {
|
|
3889
|
+
size = readableExtent(logBuffer);
|
|
3890
|
+
if (!readUncommitted) logBuffer.size = size;
|
|
3891
|
+
} else size = cachedSize;
|
|
3807
3892
|
}
|
|
3808
3893
|
position = TRANSACTION_LOG_FILE_HEADER_SIZE;
|
|
3809
3894
|
}
|
|
@@ -3816,6 +3901,35 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3816
3901
|
}
|
|
3817
3902
|
};
|
|
3818
3903
|
} });
|
|
3904
|
+
/**
|
|
3905
|
+
* Maps the next readable segment after `fromLogId`, skipping a run retention has deleted —
|
|
3906
|
+
* stopping at the hole wedges the iterator there for every later poll (invariant 30).
|
|
3907
|
+
*
|
|
3908
|
+
* The jump target is `_nextLogId()`, the successor over the store's registered segments — *not*
|
|
3909
|
+
* `_findPosition(0)`, which names the bottom of the contiguous run ending at the current segment
|
|
3910
|
+
* and so lands *past* a survivor sitting between two holes.
|
|
3911
|
+
*/
|
|
3912
|
+
function nextReadableLogBuffer(transactionLog, fromLogId, latestLogId) {
|
|
3913
|
+
let candidateLogId = fromLogId + 1;
|
|
3914
|
+
while (candidateLogId <= latestLogId) {
|
|
3915
|
+
const logBuffer = getLogMemoryMap(transactionLog, candidateLogId);
|
|
3916
|
+
if (logBuffer) {
|
|
3917
|
+
if (candidateLogId === latestLogId || readableExtent(logBuffer) > TRANSACTION_LOG_FILE_HEADER_SIZE) return logBuffer;
|
|
3918
|
+
} else if (transactionLog.getLogFileSize(candidateLogId) > TRANSACTION_LOG_FILE_HEADER_SIZE) return;
|
|
3919
|
+
candidateLogId = transactionLog._nextLogId(candidateLogId);
|
|
3920
|
+
if (candidateLogId === 0) return;
|
|
3921
|
+
}
|
|
3922
|
+
}
|
|
3923
|
+
/**
|
|
3924
|
+
* The bound for reading `logBuffer`. Native advances this append-owned extent only after a
|
|
3925
|
+
* successful write. It remains attached to the mapping after purge forgets the file object, so
|
|
3926
|
+
* readers retain committed history without interpreting a partial failed append as another frame.
|
|
3927
|
+
*/
|
|
3928
|
+
function readableExtent(logBuffer) {
|
|
3929
|
+
const extent = logBuffer.readableExtent;
|
|
3930
|
+
if (extent === void 0) throw new Error(`Transaction log buffer ${logBuffer.logId} has no readableExtent`);
|
|
3931
|
+
return Math.min(logBuffer.length, extent);
|
|
3932
|
+
}
|
|
3819
3933
|
function getLogMemoryMap(transactionLog, logId) {
|
|
3820
3934
|
if (logId <= 0) return;
|
|
3821
3935
|
let logBuffer = transactionLog._logBuffers.get(logId)?.deref();
|
|
@@ -3861,7 +3975,7 @@ function loadLastPosition(transactionLog, readUncommitted) {
|
|
|
3861
3975
|
//#region src/index.ts
|
|
3862
3976
|
const versions = {
|
|
3863
3977
|
rocksdb: version,
|
|
3864
|
-
"rocksdb-js": "2.
|
|
3978
|
+
"rocksdb-js": "2.11.0"
|
|
3865
3979
|
};
|
|
3866
3980
|
|
|
3867
3981
|
//#endregion
|
|
@@ -3879,6 +3993,7 @@ exports.constants = constants;
|
|
|
3879
3993
|
exports.coolTransactionLogs = coolTransactionLogs;
|
|
3880
3994
|
exports.currentThreadId = currentThreadId;
|
|
3881
3995
|
exports.fileLockRelease = fileLockRelease;
|
|
3996
|
+
exports.getRegistryStatus = getRegistryStatus;
|
|
3882
3997
|
exports.getWriteBufferManagerStats = getWriteBufferManagerStats;
|
|
3883
3998
|
exports.parseTransactionLog = parseTransactionLog;
|
|
3884
3999
|
exports.registryStatus = registryStatus;
|