@harperfast/rocksdb-js 2.0.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.
@@ -0,0 +1,279 @@
1
+ # Transaction Log File Format
2
+
3
+ ## Overview
4
+
5
+ The transaction log system provides an append-only, binary log format for recording database
6
+ transactions. The format is designed for:
7
+
8
+ - **Durability**: Fixed size index entries for fast traversal using binary search
9
+ - **Portability**: Big-endian encoding for platform independence
10
+ - **Efficiency**: Entry metadata and data are written to separate files
11
+ - **Scalability**: Automatic log file rotation
12
+
13
+ ## File Structure
14
+
15
+ Each transaction log store consists of one or more transaction log sequence files. These sequence
16
+ log files have a `.txnlog` extension and are rotated based on a configurable maximum size (default:
17
+ 16MB).
18
+
19
+ ### Naming Convention
20
+
21
+ Log files follow the pattern: `{name}.{sequenceNumber}.txnlog`
22
+
23
+ - `name`: The log store name
24
+ - `sequenceNumber`: Sequential integer starting from 1
25
+ - Example: `mylog.1.txnlog`, `mylog.2.txnlog`
26
+
27
+ ## Binary Format Specification
28
+
29
+ ```
30
+ +---------------------+
31
+ + File Header | 13 bytes
32
+ +---------------------+
33
+ + Transaction Header | 13 bytes
34
+ +---------------------+
35
+ + Transaction Data | variable
36
+ +---------------------+
37
+ + Transaction Header | 13 bytes
38
+ +---------------------+
39
+ + Transaction Data | variable
40
+ +---------------------+
41
+ | ... |
42
+ +---------------------+
43
+ ```
44
+
45
+ ### File Header (13 bytes)
46
+
47
+ ```
48
+ | Offset | Size | Type | Field | Description |
49
+ |--------|------|---------|------------|------------------------|
50
+ | 0 | 4 | uint32 | token | Transaction log token |
51
+ | 4 | 1 | uint8 | version | Format version |
52
+ | 5 | 8 | double | timestamp | The latest timestamp |
53
+ ```
54
+
55
+ #### `token`
56
+
57
+ The token is used to validate that the file is indeed a transaction log.
58
+
59
+ #### `version`
60
+
61
+ The transaction log file format version. Currently, version `1` is the latest.
62
+
63
+ #### `timestamp`
64
+
65
+ The timestamp of the most recent transaction log batch that has been written.
66
+
67
+ ### Transaction Header (13 bytes)
68
+
69
+ | Offset | Size | Type | Field | Description |
70
+ | ------ | ---- | ------ | --------- | ----------------------------- |
71
+ | 0 | 8 | double | timestamp | Timestamp transaction created |
72
+ | 8 | 4 | uint32 | entrySize | Size of the entry data |
73
+ | 12 | 1 | uint8 | flags | Transaction flags |
74
+
75
+ #### `timestamp`
76
+
77
+ The timestamp the associated transaction was created.
78
+
79
+ #### `entrySize`
80
+
81
+ The size of the data entry directly following the transaction header.
82
+
83
+ #### `flags`
84
+
85
+ Transaction entry related flags.
86
+
87
+ | Flag | Value | Description |
88
+ | --------------------------------- | ------ | ------------------------------------------ |
89
+ | `TRANSACTION_LOG_ENTRY_LAST_FLAG` | `0x01` | Indicates the last entry for a transaction |
90
+
91
+ ## Encoding Details
92
+
93
+ ### Endianness
94
+
95
+ All multi-byte numeric values are stored in **big-endian** (network byte order) format:
96
+
97
+ - **uint64**: Most significant byte first
98
+ - **uint32**: Most significant byte first
99
+ - **uint16**: Most significant byte first
100
+
101
+ This ensures the format is portable across different CPU architectures.
102
+
103
+ ### Timestamps
104
+
105
+ All timestamps are stored as 64-bit doubles representing milliseconds since the Unix epoch (January
106
+ 1, 1970 00:00:00 UTC).
107
+
108
+ ## Transaction Buffering
109
+
110
+ The transaction log system buffers multiple log entries before committing them when the associated
111
+ transaction is committed.
112
+
113
+ ```javascript
114
+ const log = db.useLog('example');
115
+ await db.transaction((txn) => {
116
+ log.addEntry(Buffer.from('some data'), txn.id);
117
+ log.addEntry(Buffer.from('some more data'), txn.id);
118
+ });
119
+ ```
120
+
121
+ ### Buffering Behavior
122
+
123
+ - Log entries are buffered in memory per transaction ID
124
+ - Multiple transactions can be buffered concurrently
125
+ - Buffered log entries are NOT written to disk until right before the transaction is committed
126
+ - If the transaction log handle is garbage collected, buffered (uncommitted) log entries are lost
127
+ - Calling `addEntry()` with an unknown transaction ID throws an error
128
+
129
+ ## Usage Examples
130
+
131
+ ### Basic Usage
132
+
133
+ ```javascript
134
+ import { RocksDatabase } from 'rocksdb-js';
135
+
136
+ const db = RocksDatabase.open('/tmp/mydb');
137
+ const log = db.useLog('example');
138
+
139
+ await db.transaction((txn) => {
140
+ log.addEntry(Buffer.from('some data'), txn.id);
141
+ });
142
+ ```
143
+
144
+ ### Manual Transaction
145
+
146
+ ```javascript
147
+ import { RocksDatabase, Transaction } from 'rocksdb-js';
148
+
149
+ const db = RocksDatabase.open('/tmp/mydb');
150
+ const log = db.useLog('example');
151
+ const txn = new Transaction(db);
152
+
153
+ log.add(Buffer.from('some data'), txn.id);
154
+ await txn.commit();
155
+ ```
156
+
157
+ ### Multi-Entry Transaction
158
+
159
+ ```javascript
160
+ const log1 = db.useLog('log1');
161
+ const log2 = db.useLog('log2');
162
+
163
+ await db.transaction((txn) => {
164
+ log1.addEntry(Buffer.from('some data'), txn.id);
165
+ log1.addEntry(Buffer.from('some more data'), txn.id);
166
+
167
+ log2.addEntry(Buffer.from('some data'), txn.id);
168
+ });
169
+ ```
170
+
171
+ ### Transaction Scoped Log
172
+
173
+ ```javascript
174
+ await db.transaction((txn) => {
175
+ const log = txn.useLog('log1');
176
+ log.addEntry(Buffer.from('some data'));
177
+ log.addEntry(Buffer.from('some more data'));
178
+ });
179
+ ```
180
+
181
+ ## Implementation Notes
182
+
183
+ ### Memory Management
184
+
185
+ - Transaction buffering is managed by the `TransactionLogHandle`, not the `TransactionLogStore`
186
+ - When a JavaScript `TransactionLog` object is garbage collected, its handle is destroyed and all
187
+ buffered transaction data is automatically freed
188
+ - The `TransactionLogStore` is long-lived and does not hold transaction buffers
189
+
190
+ ### Thread Safety
191
+
192
+ - All log operations are thread-safe
193
+ - Multiple worker threads can write to the same log store simultaneously
194
+ - Transactions are bound to a single worker thread
195
+
196
+ ### File Rotation
197
+
198
+ - Log files are automatically rotated when either the index or data file reaches their configured
199
+ maximum sizes
200
+ - Rotation happens on the next write after the size limit is exceeded
201
+ - Old log files can be automatically purged based on retention policy
202
+
203
+ ### Error Handling
204
+
205
+ - Write failures throw exceptions
206
+ - Commit with unknown transaction ID throws an error
207
+ - Invalid data is detected during read operations
208
+
209
+ ## Reading The Transaction Log
210
+
211
+ Log entries are not guaranteed to be in order, but are guaranteed to have a monotonic timestamp.
212
+ When reading the transaction log file, each transaction entry header must be read, then sorted and
213
+ indexed. Using this index, queries can find all entries within a time range using a binary search
214
+ and seek to get the associated entry data.
215
+
216
+ ### Sequential Read
217
+
218
+ 1. Parse file header to get version
219
+ 2. While there is bytes to read
220
+ 1. Parse first transaction entry header to identify the entry data length
221
+ 2. Read the entry data
222
+ 3. Next transaction entry header immediately follow the current entry data
223
+
224
+ ```
225
+ read file header
226
+ for each transaction entry in log file:
227
+ read transaction entry header
228
+ read transaction entry data
229
+ ```
230
+
231
+ ### Range Read
232
+
233
+ 1. Parse file header to get version
234
+ 2. Build index
235
+ 1. While there is bytes to read
236
+ 1. Parse first transaction entry header to identify the entry data length
237
+ 2. Next transaction entry header immediately follow the current entry data
238
+ 3. Query index with start and end timestamp range
239
+ 4. Extract entry data from transaction log file
240
+
241
+ ```
242
+ read file header
243
+
244
+ init index
245
+ for each transaction entry in log file:
246
+ add transaction entry header to index
247
+ search index for matching entries based on timestamp
248
+ for each log entry
249
+ extract log entry data
250
+ ```
251
+
252
+ ## Max Age and Automatic Rotation
253
+
254
+ In addition to max file size, if a log file hasn't been written to in more than a certain amount of
255
+ time, it will rotate to the next sequence log file. This max age is a percentage of the retention
256
+ period.
257
+
258
+ The default retention period is 3 days and the default max age is 75% of the retention period for a
259
+ threshold period of 18 hours. If a log file hasn't been written to in the past 18 hours, it will
260
+ start a new file.
261
+
262
+ ## Performance Considerations
263
+
264
+ - **Max File Size**: 16MB soft limit
265
+ - **Batching**: Use transactions to batch multiple actions into fewer disk writes using `writev()`
266
+ - **Zero-Copy**: The format supports memory-mapped I/O for efficient reading
267
+
268
+ ## Limitations
269
+
270
+ - Maximum single transaction size: ~4GB (uint32 limit)
271
+ - Maximum transaction size: Limited by available memory during buffering
272
+ - Maximum log file size: Configurable, default 16MB
273
+
274
+ ## Version History
275
+
276
+ - **Version 1.0**: Initial format specification
277
+ - File log format
278
+ - Big-endian encoding
279
+ - Transaction buffering
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@harperfast/rocksdb-js",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "RocksDB binding for Node.js",
5
5
  "homepage": "https://github.com/HarperFast/rocksdb-js",
6
6
  "bugs": [
@@ -18,7 +18,8 @@
18
18
  "rocksdb-js": "./bin/rocksdb-js.mjs"
19
19
  },
20
20
  "files": [
21
- "dist"
21
+ "dist",
22
+ "docs"
22
23
  ],
23
24
  "type": "module",
24
25
  "main": "./dist/index.cjs",
@@ -32,22 +33,22 @@
32
33
  },
33
34
  "dependencies": {
34
35
  "@harperfast/extended-iterable": "1.0.3",
35
- "msgpackr": "2.0.2",
36
+ "msgpackr": "2.0.4",
36
37
  "ordered-binary": "1.6.1"
37
38
  },
38
39
  "devDependencies": {
39
40
  "@rollup/plugin-replace": "6.0.3",
40
- "@types/node": "25.9.2",
41
+ "@types/node": "25.9.3",
41
42
  "@vitest/coverage-v8": "4.1.8",
42
43
  "cross-env": "10.1.0",
43
44
  "dotenv": "17.4.2",
44
45
  "lefthook": "2.1.9",
45
46
  "lmdb": "3.5.5",
46
47
  "node-gyp": "12.4.0",
47
- "oxfmt": "0.53.0",
48
- "oxlint": "1.68.0",
48
+ "oxfmt": "0.54.0",
49
+ "oxlint": "1.69.0",
49
50
  "prebuildify": "6.0.1",
50
- "semver": "7.8.2",
51
+ "semver": "7.8.4",
51
52
  "tar": "7.5.16",
52
53
  "tsdown": "0.22.2",
53
54
  "tslib": "2.8.1",
@@ -56,14 +57,14 @@
56
57
  "vitest": "4.1.8"
57
58
  },
58
59
  "optionalDependencies": {
59
- "@harperfast/rocksdb-js-darwin-arm64": "2.0.0",
60
- "@harperfast/rocksdb-js-darwin-x64": "2.0.0",
61
- "@harperfast/rocksdb-js-linux-arm64-glibc": "2.0.0",
62
- "@harperfast/rocksdb-js-linux-arm64-musl": "2.0.0",
63
- "@harperfast/rocksdb-js-linux-x64-glibc": "2.0.0",
64
- "@harperfast/rocksdb-js-linux-x64-musl": "2.0.0",
65
- "@harperfast/rocksdb-js-win32-arm64": "2.0.0",
66
- "@harperfast/rocksdb-js-win32-x64": "2.0.0"
60
+ "@harperfast/rocksdb-js-darwin-arm64": "2.1.0",
61
+ "@harperfast/rocksdb-js-darwin-x64": "2.1.0",
62
+ "@harperfast/rocksdb-js-linux-arm64-glibc": "2.1.0",
63
+ "@harperfast/rocksdb-js-linux-arm64-musl": "2.1.0",
64
+ "@harperfast/rocksdb-js-linux-x64-glibc": "2.1.0",
65
+ "@harperfast/rocksdb-js-linux-x64-musl": "2.1.0",
66
+ "@harperfast/rocksdb-js-win32-arm64": "2.1.0",
67
+ "@harperfast/rocksdb-js-win32-x64": "2.1.0"
67
68
  },
68
69
  "engines": {
69
70
  "node": "^22.18.0 || >=24.0.0"