@harperfast/rocksdb-js 2.13.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 +246 -42
- package/dist/index.cjs +307 -124
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +109 -20
- package/dist/index.d.mts +109 -20
- package/dist/index.mjs +306 -125
- package/dist/index.mjs.map +1 -1
- package/docs/performance/transaction-state-2026-11-09.csv +28 -0
- package/docs/performance/transaction-state.md +67 -0
- package/docs/transaction-log.md +34 -3
- package/package.json +16 -16
package/dist/index.cjs
CHANGED
|
@@ -30,6 +30,7 @@ let node_fs = require("node:fs");
|
|
|
30
30
|
let node_module = require("node:module");
|
|
31
31
|
let node_path = require("node:path");
|
|
32
32
|
let node_url = require("node:url");
|
|
33
|
+
let node_worker_threads = require("node:worker_threads");
|
|
33
34
|
let node_fs_promises = require("node:fs/promises");
|
|
34
35
|
let ordered_binary = require("ordered-binary");
|
|
35
36
|
ordered_binary = __toESM(ordered_binary, 1);
|
|
@@ -79,6 +80,18 @@ function locateBinding() {
|
|
|
79
80
|
throw new Error("Unable to locate rocksdb-js native binding");
|
|
80
81
|
}
|
|
81
82
|
const binding = req(locateBinding());
|
|
83
|
+
if (node_worker_threads.isMainThread) {
|
|
84
|
+
let exitShutdownRan = false;
|
|
85
|
+
const shutdownOnExit = () => {
|
|
86
|
+
if (exitShutdownRan) return;
|
|
87
|
+
exitShutdownRan = true;
|
|
88
|
+
if (!binding.shutdownOnExit() && !process.exitCode) process.exitCode = 1;
|
|
89
|
+
};
|
|
90
|
+
process.on("exit", shutdownOnExit);
|
|
91
|
+
process.on("uncaughtExceptionMonitor", () => {
|
|
92
|
+
if (process._exiting) shutdownOnExit();
|
|
93
|
+
});
|
|
94
|
+
}
|
|
82
95
|
/**
|
|
83
96
|
* The native `BackgroundError` constructor (a real `Error` subclass). Exported
|
|
84
97
|
* as both a value — for `err instanceof BackgroundError` — and, via declaration
|
|
@@ -141,7 +154,7 @@ const getRegistryStatus = binding.registryStatus;
|
|
|
141
154
|
*/
|
|
142
155
|
const registryStatus = getRegistryStatus;
|
|
143
156
|
/**
|
|
144
|
-
* Flushes every open database and
|
|
157
|
+
* Flushes every open database and cancels its outstanding compactions, then
|
|
145
158
|
* releases the registry.
|
|
146
159
|
*
|
|
147
160
|
* **Throws** the first failure it encounters: a close whose native teardown
|
|
@@ -151,19 +164,10 @@ const registryStatus = getRegistryStatus;
|
|
|
151
164
|
* `database:closeFailed`; a timeout has no event, so the throw is its only
|
|
152
165
|
* signal.
|
|
153
166
|
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
* ```typescript
|
|
159
|
-
* process.on('exit', () => {
|
|
160
|
-
* try {
|
|
161
|
-
* shutdown();
|
|
162
|
-
* } catch (error) {
|
|
163
|
-
* console.error('rocksdb-js shutdown failed', error);
|
|
164
|
-
* }
|
|
165
|
-
* });
|
|
166
|
-
* ```
|
|
167
|
+
* You do not need to call it at exit: when rocksdb-js loads on the main
|
|
168
|
+
* thread it registers a `process.on('exit')` listener that does the same and
|
|
169
|
+
* reports failures to stderr and the exit code instead of throwing (see the
|
|
170
|
+
* `shutdownOnExit` config option). Calling it yourself as well is safe.
|
|
167
171
|
*/
|
|
168
172
|
const shutdown = binding.shutdown;
|
|
169
173
|
const currentThreadId = binding.currentThreadId;
|
|
@@ -183,6 +187,30 @@ const currentThreadId = binding.currentThreadId;
|
|
|
183
187
|
*/
|
|
184
188
|
const steadyClockNow = binding.steadyClockNow;
|
|
185
189
|
/**
|
|
190
|
+
* Raises the process-wide monotonic timestamp clock — the one behind
|
|
191
|
+
* `db.getMonotonicTimestamp()`, every transaction's initial timestamp and so
|
|
192
|
+
* every transaction-log batch key — to at least `timestamp` (Unix epoch
|
|
193
|
+
* milliseconds). Every timestamp the process issues after the call is strictly
|
|
194
|
+
* greater than `timestamp`, across every database and `worker_threads` worker.
|
|
195
|
+
* Timestamps already issued and timestamps adopted with `txn.setTimestamp()`
|
|
196
|
+
* are unaffected. No open database is needed.
|
|
197
|
+
*
|
|
198
|
+
* Use it to carry a floor the caller persisted across a restart, which a
|
|
199
|
+
* backward wall-clock step would otherwise let the new process reissue. The
|
|
200
|
+
* floor is in memory only and is not a fence: restore it before the process
|
|
201
|
+
* starts any transaction it originates, and pass only floors this node
|
|
202
|
+
* persisted itself, since the clock stays that far ahead until wall time
|
|
203
|
+
* catches up.
|
|
204
|
+
*
|
|
205
|
+
* @returns `true` if the clock was raised: `timestamp` was ahead of both the
|
|
206
|
+
* wall clock and every timestamp already issued. `false` if the clock was
|
|
207
|
+
* already at or above it.
|
|
208
|
+
* @throws {TypeError} If `timestamp` is not a number.
|
|
209
|
+
* @throws {RangeError} If `timestamp` is not finite, is not greater than 0, is
|
|
210
|
+
* not less than 8.64e15, or is more than ten years ahead of the wall clock.
|
|
211
|
+
*/
|
|
212
|
+
const raiseTimestampFloor = binding.raiseTimestampFloor;
|
|
213
|
+
/**
|
|
186
214
|
* Advises the kernel that the file-backed pages of every mapped transaction log
|
|
187
215
|
* are cold (Linux MADV_COLD), so they are reclaimed first under memory pressure
|
|
188
216
|
* without being freed — useful during replication catch-up, where a full read of
|
|
@@ -1821,6 +1849,10 @@ var Store = class {
|
|
|
1821
1849
|
*/
|
|
1822
1850
|
arenaBlockSize;
|
|
1823
1851
|
/**
|
|
1852
|
+
* Bits per key of the whole-key filter in each SST; `0` disables filters.
|
|
1853
|
+
*/
|
|
1854
|
+
bloomFilterBitsPerKey;
|
|
1855
|
+
/**
|
|
1824
1856
|
* The function used to encode keys using the shared `keyBuffer`.
|
|
1825
1857
|
*/
|
|
1826
1858
|
writeKey;
|
|
@@ -1874,6 +1906,7 @@ var Store = class {
|
|
|
1874
1906
|
this.verificationTable = options?.verificationTable;
|
|
1875
1907
|
this.writeBufferSize = options?.writeBufferSize;
|
|
1876
1908
|
this.arenaBlockSize = options?.arenaBlockSize;
|
|
1909
|
+
this.bloomFilterBitsPerKey = options?.bloomFilterBitsPerKey;
|
|
1877
1910
|
this.writeKey = writeKey;
|
|
1878
1911
|
this.#columnFamilyOptions = options ? { ...options } : void 0;
|
|
1879
1912
|
}
|
|
@@ -2033,7 +2066,7 @@ var Store = class {
|
|
|
2033
2066
|
const result = context.getSync(keyParam, flags | ONLY_IF_IN_MEMORY_CACHE_FLAG, txnId, expectedVersion);
|
|
2034
2067
|
if (typeof result === "number") {
|
|
2035
2068
|
if (result === NOT_IN_MEMORY_CACHE_FLAG) return new Promise((resolve, reject) => {
|
|
2036
|
-
context.get(keyParam, resolve, reject, txnId, expectedVersion);
|
|
2069
|
+
context.get(keyParam, resolve, reject, txnId, expectedVersion, flags);
|
|
2037
2070
|
});
|
|
2038
2071
|
if (result === FRESH_VERSION_FLAG) return result;
|
|
2039
2072
|
VALUE_BUFFER.end = result;
|
|
@@ -2241,7 +2274,8 @@ var Store = class {
|
|
|
2241
2274
|
timestampFloorLog: this.timestampFloorLog,
|
|
2242
2275
|
verificationTable: this.verificationTable,
|
|
2243
2276
|
writeBufferSize: this.writeBufferSize,
|
|
2244
|
-
arenaBlockSize: this.arenaBlockSize
|
|
2277
|
+
arenaBlockSize: this.arenaBlockSize,
|
|
2278
|
+
bloomFilterBitsPerKey: this.bloomFilterBitsPerKey
|
|
2245
2279
|
});
|
|
2246
2280
|
this.identityPath = this.db.identityPath;
|
|
2247
2281
|
return false;
|
|
@@ -3797,6 +3831,40 @@ function parseTransactionLog(path, options = {}) {
|
|
|
3797
3831
|
const FLOAT_TO_UINT32 = /* @__PURE__ */ new Float64Array(1);
|
|
3798
3832
|
const UINT32_FROM_FLOAT = new Uint32Array(FLOAT_TO_UINT32.buffer);
|
|
3799
3833
|
const EXACT_START_SPAN = /* @__PURE__ */ new Float64Array(3);
|
|
3834
|
+
const ITERATOR_TOKEN = Symbol("transactionLogIterator");
|
|
3835
|
+
const logEquals = TransactionLog.prototype.equals;
|
|
3836
|
+
var LogIterator = class {
|
|
3837
|
+
#sourceLog;
|
|
3838
|
+
#control;
|
|
3839
|
+
next;
|
|
3840
|
+
constructor(token, sourceLog, next, control) {
|
|
3841
|
+
if (token !== ITERATOR_TOKEN) throw new TypeError("Transaction log iterators require query()");
|
|
3842
|
+
this.#sourceLog = sourceLog;
|
|
3843
|
+
this.next = next;
|
|
3844
|
+
this.#control = control;
|
|
3845
|
+
}
|
|
3846
|
+
static cursor(value) {
|
|
3847
|
+
if (typeof value === "object" && value !== null && #control in value) return value;
|
|
3848
|
+
}
|
|
3849
|
+
static isFrom(value, sourceLog) {
|
|
3850
|
+
return logEquals.call(sourceLog, value.#sourceLog);
|
|
3851
|
+
}
|
|
3852
|
+
[Symbol.iterator]() {
|
|
3853
|
+
return this;
|
|
3854
|
+
}
|
|
3855
|
+
peek() {
|
|
3856
|
+
return this.#control("peek");
|
|
3857
|
+
}
|
|
3858
|
+
close() {
|
|
3859
|
+
this.#control("close");
|
|
3860
|
+
}
|
|
3861
|
+
static reached(value, logId, position, atSegmentEnd) {
|
|
3862
|
+
return value.#control("reached", logId, position, atSegmentEnd);
|
|
3863
|
+
}
|
|
3864
|
+
static scanEnd(value, logId, size) {
|
|
3865
|
+
return value.#control("scanEnd", logId, size);
|
|
3866
|
+
}
|
|
3867
|
+
};
|
|
3800
3868
|
const { TRANSACTION_LOG_FILE_HEADER_SIZE, TRANSACTION_LOG_ENTRY_HEADER_SIZE, TRANSACTION_LOG_ENTRY_LAST_FLAG, TRANSACTION_LOG_ENTRY_ABORTED_FLAG } = constants;
|
|
3801
3869
|
/**
|
|
3802
3870
|
* Number of consecutive well-formed frames that, on their own, signal that real log data has
|
|
@@ -3858,6 +3926,17 @@ var TransactionLogGapError = class extends Error {
|
|
|
3858
3926
|
this.unavailableLogCount = unavailableLogCount;
|
|
3859
3927
|
}
|
|
3860
3928
|
};
|
|
3929
|
+
/** Readable input ended before the iterator's physical `until` bound. Retry after input is available. */
|
|
3930
|
+
var TransactionLogUntilUnreachedError = class extends Error {
|
|
3931
|
+
logId;
|
|
3932
|
+
position;
|
|
3933
|
+
constructor(logId, position) {
|
|
3934
|
+
super(`Transaction log reader stopped at position ${position.toString(16)} of log ${logId}, short of the iterator it reads until`);
|
|
3935
|
+
this.name = "TransactionLogUntilUnreachedError";
|
|
3936
|
+
this.logId = logId;
|
|
3937
|
+
this.position = position;
|
|
3938
|
+
}
|
|
3939
|
+
};
|
|
3861
3940
|
/**
|
|
3862
3941
|
* Whether a complete, in-bounds frame begins at `pos`. The only sane bound on an entry's length is
|
|
3863
3942
|
* the written extent: a single entry can legitimately exceed the rotation threshold (the first
|
|
@@ -3920,7 +3999,9 @@ function corruptFrame(message, transactionLog, dataView, logBuffer, position, li
|
|
|
3920
3999
|
* exactly matches the start timestamp, and then return all subsequent transactions in the log
|
|
3921
4000
|
* regardless of whether their timestamp is before or after the start
|
|
3922
4001
|
*/
|
|
3923
|
-
Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, exactStart, startFromLastFlushed, readUncommitted, exclusiveStart } = {}) {
|
|
4002
|
+
Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, exactStart, startFromLastFlushed, readUncommitted, exclusiveStart, until } = {}) {
|
|
4003
|
+
let untilCursor = LogIterator.cursor(until);
|
|
4004
|
+
if (until !== void 0 && (!untilCursor || !LogIterator.isFrom(untilCursor, this))) throw new TypeError("until must be an iterator from query() on the same transaction log");
|
|
3924
4005
|
if (!this._lastCommittedPosition) {
|
|
3925
4006
|
const lastCommittedPosition = this._getLastCommittedPosition();
|
|
3926
4007
|
this._lastCommittedPosition = new Float64Array(lastCommittedPosition.buffer);
|
|
@@ -3967,7 +4048,7 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3967
4048
|
if (position === 0) position = TRANSACTION_LOG_FILE_HEADER_SIZE;
|
|
3968
4049
|
}
|
|
3969
4050
|
const positionedAt = position;
|
|
3970
|
-
if (stopLogId !== 0 && pastStop(logId, position, stopLogId, stopPosition)) {
|
|
4051
|
+
if (stopLogId !== 0 && pastStop(logId, position, stopLogId, stopPosition) && mayJumpTo(untilCursor, tailLogId, tailPosition)) {
|
|
3971
4052
|
stopLogId = 0;
|
|
3972
4053
|
if (skipToTail(this, logId, position, tailLogId, tailPosition, segmentRemovals)) {
|
|
3973
4054
|
logId = tailLogId;
|
|
@@ -3977,95 +4058,144 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
3977
4058
|
tailLogBuffer = void 0;
|
|
3978
4059
|
}
|
|
3979
4060
|
let gapFloor = logId;
|
|
4061
|
+
let startUnmapped = false;
|
|
4062
|
+
let startMissing = false;
|
|
3980
4063
|
if (logBuffer === void 0 || logBuffer.logId !== logId) {
|
|
3981
4064
|
logBuffer = getLogMemoryMap(this, logId);
|
|
3982
4065
|
if (logBuffer && latestLogId === logId && !readUncommitted && this._currentLogBuffer?.deref() !== logBuffer) this._currentLogBuffer = new WeakRef(logBuffer);
|
|
3983
4066
|
if (logBuffer === void 0) {
|
|
3984
4067
|
logBuffer = Buffer.alloc(0);
|
|
3985
|
-
logBuffer.logId =
|
|
4068
|
+
logBuffer.logId = logId;
|
|
3986
4069
|
logBuffer.size = 0;
|
|
3987
4070
|
logBuffer.dataView = new DataView(logBuffer.buffer);
|
|
3988
4071
|
size = 0;
|
|
4072
|
+
startUnmapped = logId > 0;
|
|
3989
4073
|
if (positionedAt <= TRANSACTION_LOG_FILE_HEADER_SIZE || logId > 0 && this.getLogFileSize(logId) > 0) gapFloor = logId - 1;
|
|
3990
4074
|
}
|
|
3991
4075
|
}
|
|
3992
4076
|
dataView = logBuffer.dataView;
|
|
3993
4077
|
if (latestLogId < logId) size = 0;
|
|
3994
|
-
else if (latestLogId > logId) {
|
|
4078
|
+
else if (latestLogId > logId && !startUnmapped) {
|
|
3995
4079
|
const cachedSize = logBuffer.size;
|
|
3996
4080
|
if (cachedSize === void 0) size = logBuffer.size = readableExtent(logBuffer);
|
|
3997
4081
|
else size = cachedSize;
|
|
3998
4082
|
}
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
const nextLogBuffer = nextReadableLogBuffer(transactionLog, fromLogId, latestLogId, gapFloor);
|
|
4013
|
-
if (nextLogBuffer) {
|
|
4014
|
-
const unavailable = unavailableLogCount;
|
|
4015
|
-
dataView = nextLogBuffer.dataView;
|
|
4016
|
-
logBuffer = nextLogBuffer;
|
|
4017
|
-
if (latestLogId > logBuffer.logId) size = logBuffer.size ?? (logBuffer.size = readableExtent(logBuffer));
|
|
4018
|
-
else size = latestSize;
|
|
4019
|
-
position = TRANSACTION_LOG_FILE_HEADER_SIZE;
|
|
4020
|
-
previousEndedTransaction = true;
|
|
4021
|
-
if (unavailable > 0) throw new TransactionLogGapError(Math.max(fromLogId, gapFloor), logBuffer.logId, unavailable);
|
|
4022
|
-
}
|
|
4023
|
-
}
|
|
4083
|
+
let peeked;
|
|
4084
|
+
let peekedLogId = 0;
|
|
4085
|
+
let peekedPosition = 0;
|
|
4086
|
+
let closed = false;
|
|
4087
|
+
let needsCursorCheck = untilCursor !== void 0 || startUnmapped;
|
|
4088
|
+
const iterator = new LogIterator(ITERATOR_TOKEN, transactionLog, function next() {
|
|
4089
|
+
let scanEnd = size;
|
|
4090
|
+
if (needsCursorCheck) {
|
|
4091
|
+
if (peeked) {
|
|
4092
|
+
const result = peeked;
|
|
4093
|
+
if (!closed) {
|
|
4094
|
+
peeked = void 0;
|
|
4095
|
+
needsCursorCheck = untilCursor !== void 0;
|
|
4024
4096
|
}
|
|
4097
|
+
return result;
|
|
4025
4098
|
}
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4099
|
+
if (untilCursor) {
|
|
4100
|
+
const limit = LogIterator.scanEnd(untilCursor, logBuffer.logId, size);
|
|
4101
|
+
if (limit === void 0) {
|
|
4102
|
+
untilCursor = void 0;
|
|
4103
|
+
needsCursorCheck = startUnmapped;
|
|
4104
|
+
} else {
|
|
4105
|
+
if (position >= limit && LogIterator.reached(untilCursor, logBuffer.logId, position, position >= size && logBuffer.length > 0 && position >= readableExtent(logBuffer))) return {
|
|
4106
|
+
done: true,
|
|
4107
|
+
value: void 0
|
|
4108
|
+
};
|
|
4109
|
+
scanEnd = limit;
|
|
4032
4110
|
}
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
const length = dataView.getUint32(position + 8);
|
|
4045
|
-
if (length === 0 || position + TRANSACTION_LOG_ENTRY_HEADER_SIZE + length > limit) {
|
|
4046
|
-
const broken = corruptFrame(length === 0 ? `Corrupt transaction log entry at position ${position.toString(16)} of log ${logBuffer.logId}: zero-length entry` : `Corrupt transaction log entry at position ${position.toString(16)} of log ${logBuffer.logId}: declared length ${length} overruns the log (limit=${limit})`, transactionLog, dataView, logBuffer, position, limit, size, !!readUncommitted);
|
|
4047
|
-
position = broken.nextPosition;
|
|
4048
|
-
previousEndedTransaction = true;
|
|
4049
|
-
throw broken.error;
|
|
4111
|
+
}
|
|
4112
|
+
if (startUnmapped) {
|
|
4113
|
+
const mapped = getLogMemoryMap(transactionLog, logBuffer.logId);
|
|
4114
|
+
if (mapped) {
|
|
4115
|
+
logBuffer = mapped;
|
|
4116
|
+
dataView = mapped.dataView;
|
|
4117
|
+
} else {
|
|
4118
|
+
const latestLogId = loadLastPosition(transactionLog, !!readUncommitted).logId;
|
|
4119
|
+
const fileSize = transactionLog.getLogFileSize(logBuffer.logId);
|
|
4120
|
+
if (logBuffer.logId >= latestLogId || fileSize > position) return endOfReadable(untilCursor, logBuffer, position);
|
|
4121
|
+
if (fileSize === 0) startMissing = true;
|
|
4050
4122
|
}
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4123
|
+
startUnmapped = false;
|
|
4124
|
+
needsCursorCheck = untilCursor !== void 0;
|
|
4125
|
+
size = 0;
|
|
4126
|
+
}
|
|
4127
|
+
}
|
|
4128
|
+
let timestamp;
|
|
4129
|
+
if (position >= size) {
|
|
4130
|
+
const { logId: latestLogId, size: latestSize } = loadLastPosition(transactionLog, !!readUncommitted);
|
|
4131
|
+
size = latestLogId < logBuffer.logId ? 0 : latestSize;
|
|
4132
|
+
if (latestLogId > logBuffer.logId) {
|
|
4133
|
+
size = logBuffer.size ?? (logBuffer.size = readableExtent(logBuffer));
|
|
4134
|
+
if (position >= size) {
|
|
4135
|
+
if (untilCursor && LogIterator.reached(untilCursor, logBuffer.logId, position, logBuffer.length > 0 && position >= readableExtent(logBuffer))) return {
|
|
4136
|
+
done: true,
|
|
4137
|
+
value: void 0
|
|
4138
|
+
};
|
|
4139
|
+
const fromLogId = logBuffer.logId - (startMissing ? 1 : 0);
|
|
4140
|
+
const nextLogBuffer = nextReadableLogBuffer(transactionLog, fromLogId, latestLogId, gapFloor);
|
|
4141
|
+
if (nextLogBuffer) {
|
|
4142
|
+
startMissing = false;
|
|
4143
|
+
const unavailable = unavailableLogCount;
|
|
4144
|
+
dataView = nextLogBuffer.dataView;
|
|
4145
|
+
logBuffer = nextLogBuffer;
|
|
4146
|
+
if (latestLogId > logBuffer.logId) size = logBuffer.size ?? (logBuffer.size = readableExtent(logBuffer));
|
|
4147
|
+
else size = latestSize;
|
|
4148
|
+
position = TRANSACTION_LOG_FILE_HEADER_SIZE;
|
|
4149
|
+
previousEndedTransaction = true;
|
|
4150
|
+
if (unavailable > 0) throw new TransactionLogGapError(Math.max(fromLogId, gapFloor), logBuffer.logId, unavailable);
|
|
4066
4151
|
}
|
|
4067
|
-
}
|
|
4068
|
-
|
|
4152
|
+
}
|
|
4153
|
+
}
|
|
4154
|
+
scanEnd = untilCursor ? LogIterator.scanEnd(untilCursor, logBuffer.logId, size) ?? size : size;
|
|
4155
|
+
}
|
|
4156
|
+
if (position >= scanEnd) return endOfReadable(untilCursor, logBuffer, position);
|
|
4157
|
+
let jumpRefused = false;
|
|
4158
|
+
let limit = Math.min(size, logBuffer.length);
|
|
4159
|
+
do {
|
|
4160
|
+
try {
|
|
4161
|
+
timestamp = dataView.getFloat64(position);
|
|
4162
|
+
} catch (error) {
|
|
4163
|
+
error.message += ` at position ${position.toString(16)} of log ${logBuffer.logId} (size=${size}, log buffer length=${logBuffer.length})`;
|
|
4164
|
+
throw error;
|
|
4165
|
+
}
|
|
4166
|
+
if (!timestamp) return endOfReadable(untilCursor, logBuffer, position);
|
|
4167
|
+
if (position + TRANSACTION_LOG_ENTRY_HEADER_SIZE > limit) {
|
|
4168
|
+
const broken = corruptFrame(`Corrupt transaction log: truncated entry header at position ${position.toString(16)} of log ${logBuffer.logId} (available=${limit - position})`, transactionLog, dataView, logBuffer, position, limit, size, !!readUncommitted);
|
|
4169
|
+
position = broken.nextPosition;
|
|
4170
|
+
previousEndedTransaction = true;
|
|
4171
|
+
throw broken.error;
|
|
4172
|
+
}
|
|
4173
|
+
const length = dataView.getUint32(position + 8);
|
|
4174
|
+
if (length === 0 || position + TRANSACTION_LOG_ENTRY_HEADER_SIZE + length > limit) {
|
|
4175
|
+
const broken = corruptFrame(length === 0 ? `Corrupt transaction log entry at position ${position.toString(16)} of log ${logBuffer.logId}: zero-length entry` : `Corrupt transaction log entry at position ${position.toString(16)} of log ${logBuffer.logId}: declared length ${length} overruns the log (limit=${limit})`, transactionLog, dataView, logBuffer, position, limit, size, !!readUncommitted);
|
|
4176
|
+
position = broken.nextPosition;
|
|
4177
|
+
previousEndedTransaction = true;
|
|
4178
|
+
throw broken.error;
|
|
4179
|
+
}
|
|
4180
|
+
position += TRANSACTION_LOG_ENTRY_HEADER_SIZE;
|
|
4181
|
+
const flags = logBuffer[position - 1];
|
|
4182
|
+
const aborted = (flags & TRANSACTION_LOG_ENTRY_ABORTED_FLAG) !== 0;
|
|
4183
|
+
if (previousEndedTransaction || timestamp !== previousTimestamp || aborted) skippingAborted = aborted;
|
|
4184
|
+
previousEndedTransaction = (flags & TRANSACTION_LOG_ENTRY_LAST_FLAG) !== 0;
|
|
4185
|
+
previousTimestamp = timestamp;
|
|
4186
|
+
let matchesRange;
|
|
4187
|
+
if (skippingAborted) matchesRange = false;
|
|
4188
|
+
else if (foundExactStart) matchesRange = (!exclusiveStart || timestamp !== start) && timestamp < end;
|
|
4189
|
+
else if (exactStart) {
|
|
4190
|
+
matchesRange = false;
|
|
4191
|
+
if (timestamp === start) {
|
|
4192
|
+
matchesRange = !exclusiveStart;
|
|
4193
|
+
foundExactStart = true;
|
|
4194
|
+
stopLogId = 0;
|
|
4195
|
+
}
|
|
4196
|
+
} else matchesRange = (exclusiveStart ? timestamp > start : timestamp >= start) && timestamp < end;
|
|
4197
|
+
if (!matchesRange && stopLogId !== 0 && !jumpRefused && pastStop(logBuffer.logId, position + length, stopLogId, stopPosition)) {
|
|
4198
|
+
if (mayJumpTo(untilCursor, tailLogId, tailPosition)) {
|
|
4069
4199
|
stopLogId = 0;
|
|
4070
4200
|
position += length;
|
|
4071
4201
|
if (skipToTail(transactionLog, logBuffer.logId, position, tailLogId, tailPosition, segmentRemovals)) {
|
|
@@ -4080,57 +4210,108 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
|
|
|
4080
4210
|
tailLogBuffer = void 0;
|
|
4081
4211
|
return iterator.next();
|
|
4082
4212
|
}
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4213
|
+
jumpRefused = true;
|
|
4214
|
+
}
|
|
4215
|
+
const entryStart = position;
|
|
4216
|
+
position += length;
|
|
4217
|
+
if (matchesRange) return {
|
|
4218
|
+
done: false,
|
|
4219
|
+
value: {
|
|
4220
|
+
timestamp,
|
|
4221
|
+
endTxn: previousEndedTransaction,
|
|
4222
|
+
data: logBuffer.subarray(entryStart, position)
|
|
4223
|
+
}
|
|
4224
|
+
};
|
|
4225
|
+
if (position >= scanEnd) {
|
|
4226
|
+
if (untilCursor && LogIterator.reached(untilCursor, logBuffer.logId, position)) return {
|
|
4227
|
+
done: true,
|
|
4228
|
+
value: void 0
|
|
4092
4229
|
};
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4230
|
+
const { logId: latestLogId, size: latestSize } = loadLastPosition(transactionLog, !!readUncommitted);
|
|
4231
|
+
size = latestLogId < logBuffer.logId ? 0 : latestSize;
|
|
4232
|
+
if (latestLogId > logBuffer.logId) {
|
|
4233
|
+
size = logBuffer.size ?? readableExtent(logBuffer);
|
|
4234
|
+
if (!readUncommitted) logBuffer.size = size;
|
|
4235
|
+
if (position < size) {
|
|
4236
|
+
scanEnd = untilCursor ? LogIterator.scanEnd(untilCursor, logBuffer.logId, size) ?? size : size;
|
|
4237
|
+
limit = Math.min(size, logBuffer.length);
|
|
4238
|
+
continue;
|
|
4239
|
+
}
|
|
4240
|
+
if (untilCursor && LogIterator.reached(untilCursor, logBuffer.logId, position, logBuffer.length > 0 && position >= readableExtent(logBuffer))) return {
|
|
4241
|
+
done: true,
|
|
4242
|
+
value: void 0
|
|
4243
|
+
};
|
|
4244
|
+
const fromLogId = logBuffer.logId;
|
|
4245
|
+
const nextLogBuffer = nextReadableLogBuffer(transactionLog, fromLogId, latestLogId, gapFloor);
|
|
4246
|
+
if (!nextLogBuffer) return endOfReadable(untilCursor, logBuffer, position);
|
|
4247
|
+
const unavailable = unavailableLogCount;
|
|
4248
|
+
logBuffer = nextLogBuffer;
|
|
4249
|
+
dataView = logBuffer.dataView;
|
|
4250
|
+
if (latestLogId === logBuffer.logId) size = latestSize;
|
|
4251
|
+
else {
|
|
4252
|
+
const cachedSize = logBuffer.size;
|
|
4253
|
+
if (cachedSize === void 0) {
|
|
4254
|
+
size = readableExtent(logBuffer);
|
|
4255
|
+
if (!readUncommitted) logBuffer.size = size;
|
|
4256
|
+
} else size = cachedSize;
|
|
4120
4257
|
}
|
|
4258
|
+
position = TRANSACTION_LOG_FILE_HEADER_SIZE;
|
|
4259
|
+
previousEndedTransaction = true;
|
|
4260
|
+
if (unavailable > 0) throw new TransactionLogGapError(Math.max(fromLogId, gapFloor), logBuffer.logId, unavailable);
|
|
4121
4261
|
}
|
|
4262
|
+
scanEnd = untilCursor ? LogIterator.scanEnd(untilCursor, logBuffer.logId, size) ?? size : size;
|
|
4263
|
+
limit = Math.min(size, logBuffer.length);
|
|
4264
|
+
}
|
|
4265
|
+
} while (position < scanEnd);
|
|
4266
|
+
return endOfReadable(untilCursor, logBuffer, position);
|
|
4267
|
+
}, function control(operation, fromLogId, fromPosition, atSegmentEnd) {
|
|
4268
|
+
if (operation === "peek") {
|
|
4269
|
+
if (peeked) return peeked;
|
|
4270
|
+
const result = iterator.next();
|
|
4271
|
+
if (!result.done) {
|
|
4272
|
+
peeked = result;
|
|
4273
|
+
needsCursorCheck = true;
|
|
4274
|
+
peekedLogId = logBuffer.logId;
|
|
4275
|
+
peekedPosition = position - result.value.data.length - TRANSACTION_LOG_ENTRY_HEADER_SIZE;
|
|
4122
4276
|
}
|
|
4123
|
-
return
|
|
4277
|
+
return result;
|
|
4278
|
+
}
|
|
4279
|
+
if (operation === "close") {
|
|
4280
|
+
closed = true;
|
|
4281
|
+
needsCursorCheck = true;
|
|
4282
|
+
peeked = {
|
|
4124
4283
|
done: true,
|
|
4125
4284
|
value: void 0
|
|
4126
4285
|
};
|
|
4286
|
+
logBuffer = void 0;
|
|
4287
|
+
dataView = void 0;
|
|
4288
|
+
untilCursor = void 0;
|
|
4289
|
+
return;
|
|
4127
4290
|
}
|
|
4128
|
-
|
|
4291
|
+
if (closed) return;
|
|
4292
|
+
const boundLogId = peeked ? peekedLogId : logBuffer.logId;
|
|
4293
|
+
const boundPosition = peeked ? peekedPosition : position;
|
|
4294
|
+
if (operation === "scanEnd") {
|
|
4295
|
+
if (boundLogId !== fromLogId) return boundLogId > fromLogId ? fromPosition : 0;
|
|
4296
|
+
return boundPosition < fromPosition ? boundPosition : fromPosition;
|
|
4297
|
+
}
|
|
4298
|
+
return pastStop(fromLogId, fromPosition, boundLogId, boundPosition) || !!atSegmentEnd && boundLogId === fromLogId + 1 && boundPosition === TRANSACTION_LOG_FILE_HEADER_SIZE;
|
|
4299
|
+
});
|
|
4129
4300
|
return iterator;
|
|
4130
4301
|
} });
|
|
4302
|
+
function endOfReadable(untilCursor, logBuffer, position) {
|
|
4303
|
+
if (untilCursor && LogIterator.reached(untilCursor, logBuffer.logId, position, logBuffer.length > 0 && position >= readableExtent(logBuffer)) === false) throw new TransactionLogUntilUnreachedError(logBuffer.logId, position);
|
|
4304
|
+
return {
|
|
4305
|
+
done: true,
|
|
4306
|
+
value: void 0
|
|
4307
|
+
};
|
|
4308
|
+
}
|
|
4131
4309
|
function pastStop(logId, position, stopLogId, stopPosition) {
|
|
4132
4310
|
return logId > stopLogId || logId === stopLogId && position >= stopPosition;
|
|
4133
4311
|
}
|
|
4312
|
+
function mayJumpTo(untilCursor, logId, position) {
|
|
4313
|
+
return !untilCursor || LogIterator.reached(untilCursor, logId, position - 1) !== true;
|
|
4314
|
+
}
|
|
4134
4315
|
let tailLogBuffer;
|
|
4135
4316
|
/**
|
|
4136
4317
|
* Whether an exact-start lookup at `position` of `fromLogId` may take its one-shot jump to the tail.
|
|
@@ -4229,7 +4410,7 @@ function loadLastPosition(transactionLog, readUncommitted) {
|
|
|
4229
4410
|
// src/index.ts
|
|
4230
4411
|
const versions = {
|
|
4231
4412
|
rocksdb: version,
|
|
4232
|
-
"rocksdb-js": "2.
|
|
4413
|
+
"rocksdb-js": "2.14.0"
|
|
4233
4414
|
};
|
|
4234
4415
|
|
|
4235
4416
|
exports.BackgroundError = BackgroundError;
|
|
@@ -4244,6 +4425,7 @@ exports.Transaction = Transaction;
|
|
|
4244
4425
|
exports.TransactionAbortUnresolvedError = TransactionAbortUnresolvedError;
|
|
4245
4426
|
exports.TransactionLog = TransactionLog;
|
|
4246
4427
|
exports.TransactionLogGapError = TransactionLogGapError;
|
|
4428
|
+
exports.TransactionLogUntilUnreachedError = TransactionLogUntilUnreachedError;
|
|
4247
4429
|
exports.backups = backups;
|
|
4248
4430
|
exports.constants = constants;
|
|
4249
4431
|
exports.coolTransactionLogs = coolTransactionLogs;
|
|
@@ -4252,6 +4434,7 @@ exports.fileLockRelease = fileLockRelease;
|
|
|
4252
4434
|
exports.getRegistryStatus = getRegistryStatus;
|
|
4253
4435
|
exports.getWriteBufferManagerStats = getWriteBufferManagerStats;
|
|
4254
4436
|
exports.parseTransactionLog = parseTransactionLog;
|
|
4437
|
+
exports.raiseTimestampFloor = raiseTimestampFloor;
|
|
4255
4438
|
exports.registryStatus = registryStatus;
|
|
4256
4439
|
exports.shutdown = shutdown;
|
|
4257
4440
|
exports.stats = stats;
|