@harperfast/rocksdb-js 1.5.0 → 2.1.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 +80 -19
- package/dist/index.cjs +4 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +443 -19
- package/dist/index.d.mts +443 -19
- package/dist/index.mjs +4 -16
- package/dist/index.mjs.map +1 -1
- package/docs/stats.md +459 -0
- package/docs/transaction-log.md +279 -0
- package/package.json +25 -24
package/README.md
CHANGED
|
@@ -804,35 +804,28 @@ same column family-level properties, but includes all internal tickers and histo
|
|
|
804
804
|
Column family and ticker stat values are 64-bit unsigned integers and histogram values are
|
|
805
805
|
`StatsHistogramData` objects.
|
|
806
806
|
|
|
807
|
+
The result also always includes a summarized, aggregate set of `txnlog.*` keys covering all of
|
|
808
|
+
the database's transaction logs. These are present regardless of whether statistics are enabled
|
|
809
|
+
and can be fetched individually with `db.getStat('txnlog.…')`. For detailed, per-log statistics —
|
|
810
|
+
including memory-map usage — use [`log.getStats()`](#loggetstats-transactionlogstats). All stat
|
|
811
|
+
names are documented in [docs/stats.md](docs/stats.md).
|
|
812
|
+
|
|
807
813
|
```typescript
|
|
808
814
|
// get essential stats
|
|
809
815
|
console.log(db.getStats());
|
|
810
816
|
|
|
811
817
|
// get all stats
|
|
812
818
|
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
819
|
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
```typescript
|
|
824
|
-
import { stats } from '@harperfast/rocksdb-js';
|
|
825
|
-
console.log(stats.histograms);
|
|
820
|
+
// transaction log bytes across all logs
|
|
821
|
+
console.log(db.getStats()['txnlog.totalSizeBytes']);
|
|
826
822
|
```
|
|
827
823
|
|
|
828
|
-
|
|
824
|
+
### `stats`
|
|
829
825
|
|
|
830
|
-
An
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
import { stats } from '@harperfast/rocksdb-js';
|
|
834
|
-
console.log(stats.tickers);
|
|
835
|
-
```
|
|
826
|
+
An object containing stat-specific constants. The full catalog of available stat names (RocksDB
|
|
827
|
+
tickers, histograms, internal properties, and transaction log stats) is documented in
|
|
828
|
+
[docs/stats.md](docs/stats.md).
|
|
836
829
|
|
|
837
830
|
#### `stats.StatsLevel`
|
|
838
831
|
|
|
@@ -968,6 +961,46 @@ log gets, puts, and deletes, but also arbitrary entries.
|
|
|
968
961
|
Transaction logs are isolated by the database path allowing different column families in the same
|
|
969
962
|
database to share the transaction log store, but not other databases.
|
|
970
963
|
|
|
964
|
+
### Memory-Map Handling
|
|
965
|
+
|
|
966
|
+
Transaction log files are read through read-only memory maps. Understanding how these maps interact
|
|
967
|
+
with system memory helps when interpreting [`log.getStats()`](#loggetstats-transactionlogstats)
|
|
968
|
+
figures and process memory usage:
|
|
969
|
+
|
|
970
|
+
- **Maps are created lazily and live until the file is closed.** Writing log entries does not map
|
|
971
|
+
anything; a log file is mapped the first time a `log.query()` reads from it. The native layer
|
|
972
|
+
holds each file's map for the life of the file — it is released when the log file is purged or
|
|
973
|
+
the database is closed, not by garbage collection. JS `Buffer` views over the map (including
|
|
974
|
+
`entry.data`) hold an additional reference, so the underlying memory is never unmapped while a
|
|
975
|
+
view is still reachable. `stats.memory.activeMaps` counts the maps currently held by the native
|
|
976
|
+
layer.
|
|
977
|
+
- **Mapped bytes are virtual, not resident.** Creating a map reserves address space only. A page
|
|
978
|
+
consumes physical RAM (RSS) when it is first read (demand paging). Querying a multi-gigabyte log
|
|
979
|
+
can show `memory.mappedBytes` in the gigabytes while actual memory usage barely moves.
|
|
980
|
+
- **Queries are zero-copy.** The iterator returned by `log.query()` reads only each entry's small
|
|
981
|
+
header; `entry.data` is a `Buffer` view directly into the map (no copy). Payload pages are
|
|
982
|
+
faulted into memory only if and when the entry data is actually read.
|
|
983
|
+
- **Resident pages are reclaimable.** Because the maps are read-only and file-backed, every
|
|
984
|
+
resident page is "clean" — the kernel can evict it at any time under memory pressure and re-read
|
|
985
|
+
it from disk on the next access. Mapped log data therefore lives in the page cache and cannot
|
|
986
|
+
exhaust memory the way heap allocations can; a full scan of a log larger than RAM will simply
|
|
987
|
+
cycle pages through the cache.
|
|
988
|
+
|
|
989
|
+
OS-specific differences:
|
|
990
|
+
|
|
991
|
+
- **POSIX (Linux and macOS):** The active write file is mapped at the full configured
|
|
992
|
+
`transactionLogMaxSize` (an anonymous reservation with the file's contents overlaid on top), so
|
|
993
|
+
`memory.mappedBytes` over-reports the active file; `memory.overlayBytes` is the file-backed
|
|
994
|
+
portion and is the closer proxy for real consumption.
|
|
995
|
+
- **macOS:** Activity Monitor's "Memory" column reports the physical footprint, which excludes
|
|
996
|
+
clean file-backed pages — mapped log data is essentially invisible there even when resident. Use
|
|
997
|
+
process RSS (e.g. `process.memoryUsage().rss`, `ps`, or `vmmap <pid>`) to observe it.
|
|
998
|
+
- **Linux:** Resident map pages are visible in process RSS, and `/proc/<pid>/smaps` reports exact
|
|
999
|
+
per-file `Rss`/`Pss` for each mapped `.txnlog` file.
|
|
1000
|
+
- **Windows:** Maps are created with `CreateFileMapping`/`MapViewOfFile` at the file's current
|
|
1001
|
+
size. There is no overlay mechanism, so `memory.overlayBytes` is always `0`, and the active
|
|
1002
|
+
write file's map is not cached because it is not growable.
|
|
1003
|
+
|
|
971
1004
|
### `db.listLogs(): string[]`
|
|
972
1005
|
|
|
973
1006
|
Returns an array of log store names.
|
|
@@ -1116,6 +1149,34 @@ for (const entry of rangeIter) {
|
|
|
1116
1149
|
Returns the size of the given transaction log sequence file in bytes. Omit the sequence number to
|
|
1117
1150
|
get the total size of all the transaction log sequence files for this log.
|
|
1118
1151
|
|
|
1152
|
+
#### `log.getStats(): TransactionLogStats`
|
|
1153
|
+
|
|
1154
|
+
Returns a detailed statistics snapshot for this transaction log, including file/transaction
|
|
1155
|
+
gauges, memory-map usage, recovery positions, purge/retention gauges, and lifetime counters. All
|
|
1156
|
+
sizes are in bytes; timestamps are milliseconds since the Unix epoch.
|
|
1157
|
+
|
|
1158
|
+
```typescript
|
|
1159
|
+
const log = db.useLog('replication');
|
|
1160
|
+
const stats = log.getStats();
|
|
1161
|
+
|
|
1162
|
+
stats.fileCount; // number of sequence files on disk
|
|
1163
|
+
stats.totalSizeBytes; // total bytes across all sequence files
|
|
1164
|
+
stats.memory.mappedBytes; // bytes mapped into memory (virtual address space)
|
|
1165
|
+
stats.memory.overlayBytes; // POSIX file-backed overlay portion (0 on Windows)
|
|
1166
|
+
stats.replayGapBytes; // bytes between the last flushed position and the write head
|
|
1167
|
+
stats.purge.retainedUnflushedFiles; // files past retention but kept (not yet flushed to RocksDB)
|
|
1168
|
+
stats.totals.transactionsWritten; // lifetime count of transactions written
|
|
1169
|
+
```
|
|
1170
|
+
|
|
1171
|
+
> **Memory note:** `memory.mappedBytes` is virtual address space — the active write file is mapped
|
|
1172
|
+
> at the full configured `transactionLogMaxSize` on POSIX, so it does not reflect resident memory.
|
|
1173
|
+
> `memory.overlayBytes` (POSIX only) is the file-backed portion and is the closer proxy for real
|
|
1174
|
+
> consumption.
|
|
1175
|
+
|
|
1176
|
+
The `purge.retainedUnflushedFiles` gauge is useful for diagnosing why logs are not being cleaned
|
|
1177
|
+
up: a file can be older than the retention period but still retained because its transactions have
|
|
1178
|
+
not yet been flushed to RocksDB (purging it would be unsafe for crash recovery).
|
|
1179
|
+
|
|
1119
1180
|
### Transaction Log Initialization
|
|
1120
1181
|
|
|
1121
1182
|
When a database is opened, `rocksdb-js` will automatically discover the transaction log files. If
|
package/dist/index.cjs
CHANGED
|
@@ -599,8 +599,6 @@ const VALUE_BUFFER = createFixedBuffer(64 * 1024);
|
|
|
599
599
|
const ITERATOR_STATE_BUFFER = Buffer.allocUnsafeSlow(8);
|
|
600
600
|
const ITERATOR_STATE = new Uint32Array(ITERATOR_STATE_BUFFER.buffer, ITERATOR_STATE_BUFFER.byteOffset, 2);
|
|
601
601
|
const MAX_KEY_SIZE = 1024 * 1024;
|
|
602
|
-
const RESET_BUFFER_MODE = 1024;
|
|
603
|
-
const REUSE_BUFFER_MODE = 512;
|
|
604
602
|
const SAVE_BUFFER_SIZE = 8192;
|
|
605
603
|
/**
|
|
606
604
|
* A store wraps the `NativeDatabase` binding and database settings so that a
|
|
@@ -890,7 +888,7 @@ var Store = class {
|
|
|
890
888
|
encodeValue(value) {
|
|
891
889
|
if (value && value["binary-data"]) return value["binary-data"];
|
|
892
890
|
if (typeof this.encoder?.encode === "function") {
|
|
893
|
-
if (this.encoder.copyBuffers) return this.encoder.encode(value,
|
|
891
|
+
if (this.encoder.copyBuffers) return this.encoder.encode(value, 1536);
|
|
894
892
|
const valueBuffer = this.encoder.encode(value);
|
|
895
893
|
if (typeof valueBuffer === "string") return Buffer.from(valueBuffer);
|
|
896
894
|
return valueBuffer;
|
|
@@ -1546,17 +1544,8 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
1546
1544
|
getStat(statName) {
|
|
1547
1545
|
return this.store.db.getStat(statName);
|
|
1548
1546
|
}
|
|
1549
|
-
/**
|
|
1550
|
-
* Gets the RocksDB statistics. Requires statistics to be enabled.
|
|
1551
|
-
*
|
|
1552
|
-
* @example
|
|
1553
|
-
* ```typescript
|
|
1554
|
-
* const db = RocksDatabase.open('/path/to/database');
|
|
1555
|
-
* const stats = db.getStats();
|
|
1556
|
-
* ```
|
|
1557
|
-
*/
|
|
1558
1547
|
getStats(all = false) {
|
|
1559
|
-
return this.store.db.getStats(
|
|
1548
|
+
return all ? this.store.db.getStats(true) : this.store.db.getStats(false);
|
|
1560
1549
|
}
|
|
1561
1550
|
/**
|
|
1562
1551
|
* Gets or creates a buffer that can be shared across worker threads.
|
|
@@ -1919,7 +1908,6 @@ var RocksDatabase = class RocksDatabase extends DBI {
|
|
|
1919
1908
|
//#region src/parse-transaction-log.ts
|
|
1920
1909
|
const { TRANSACTION_LOG_TOKEN, TRANSACTION_LOG_ENTRY_HEADER_SIZE: TRANSACTION_LOG_ENTRY_HEADER_SIZE$1 } = constants;
|
|
1921
1910
|
const MIN_VALID_TIMESTAMP = Date.UTC(2026, 0, 27);
|
|
1922
|
-
const VALID_FLAGS_MASK = 1;
|
|
1923
1911
|
/**
|
|
1924
1912
|
* Loads an entire transaction log file into memory.
|
|
1925
1913
|
* @param path - The path to the transaction log file.
|
|
@@ -1973,7 +1961,7 @@ function parseTransactionLog(path, options = {}) {
|
|
|
1973
1961
|
const data = read(length);
|
|
1974
1962
|
const entryAnomalies = [];
|
|
1975
1963
|
if (!Number.isFinite(timestamp) || timestamp < MIN_VALID_TIMESTAMP) entryAnomalies.push(`timestamp ${timestamp} predates 2026-01-27 (possible corruption)`);
|
|
1976
|
-
if ((flags &
|
|
1964
|
+
if ((flags & -2) !== 0) entryAnomalies.push(`flags 0x${flags.toString(16).padStart(2, "0")} contains undefined bits (expected 0x00 or 0x01)`);
|
|
1977
1965
|
const entry = {
|
|
1978
1966
|
timestamp,
|
|
1979
1967
|
length,
|
|
@@ -2190,7 +2178,7 @@ function loadLastPosition(transactionLog, readUncommitted) {
|
|
|
2190
2178
|
//#region src/index.ts
|
|
2191
2179
|
const versions = {
|
|
2192
2180
|
rocksdb: version,
|
|
2193
|
-
"rocksdb-js": "1.
|
|
2181
|
+
"rocksdb-js": "2.1.0"
|
|
2194
2182
|
};
|
|
2195
2183
|
|
|
2196
2184
|
//#endregion
|