@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 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
- An array of internal histogram stat names.
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
- #### `stats.tickers`
824
+ ### `stats`
829
825
 
830
- An array of internal ticker stat names.
831
-
832
- ```typescript
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, REUSE_BUFFER_MODE | RESET_BUFFER_MODE);
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(all);
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 & ~VALID_FLAGS_MASK) !== 0) entryAnomalies.push(`flags 0x${flags.toString(16).padStart(2, "0")} contains undefined bits (expected 0x00 or 0x01)`);
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.5.0"
2181
+ "rocksdb-js": "2.1.0"
2194
2182
  };
2195
2183
 
2196
2184
  //#endregion