@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
|
@@ -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": "1.
|
|
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,41 +33,41 @@
|
|
|
32
33
|
},
|
|
33
34
|
"dependencies": {
|
|
34
35
|
"@harperfast/extended-iterable": "1.0.3",
|
|
35
|
-
"msgpackr": "2.0.
|
|
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.
|
|
41
|
-
"@vitest/coverage-v8": "4.1.
|
|
41
|
+
"@types/node": "25.9.3",
|
|
42
|
+
"@vitest/coverage-v8": "4.1.8",
|
|
42
43
|
"cross-env": "10.1.0",
|
|
43
44
|
"dotenv": "17.4.2",
|
|
44
|
-
"lefthook": "2.1.
|
|
45
|
-
"lmdb": "3.5.
|
|
46
|
-
"node-gyp": "12.
|
|
47
|
-
"oxfmt": "0.
|
|
48
|
-
"oxlint": "1.
|
|
45
|
+
"lefthook": "2.1.9",
|
|
46
|
+
"lmdb": "3.5.5",
|
|
47
|
+
"node-gyp": "12.4.0",
|
|
48
|
+
"oxfmt": "0.54.0",
|
|
49
|
+
"oxlint": "1.69.0",
|
|
49
50
|
"prebuildify": "6.0.1",
|
|
50
|
-
"semver": "7.8.
|
|
51
|
-
"tar": "7.5.
|
|
52
|
-
"tsdown": "0.
|
|
51
|
+
"semver": "7.8.4",
|
|
52
|
+
"tar": "7.5.16",
|
|
53
|
+
"tsdown": "0.22.2",
|
|
53
54
|
"tslib": "2.8.1",
|
|
54
|
-
"tsx": "4.
|
|
55
|
+
"tsx": "4.22.4",
|
|
55
56
|
"typescript": "6.0.3",
|
|
56
|
-
"vitest": "4.1.
|
|
57
|
+
"vitest": "4.1.8"
|
|
57
58
|
},
|
|
58
59
|
"optionalDependencies": {
|
|
59
|
-
"@harperfast/rocksdb-js-darwin-arm64": "1.
|
|
60
|
-
"@harperfast/rocksdb-js-darwin-x64": "1.
|
|
61
|
-
"@harperfast/rocksdb-js-linux-arm64-glibc": "1.
|
|
62
|
-
"@harperfast/rocksdb-js-linux-arm64-musl": "1.
|
|
63
|
-
"@harperfast/rocksdb-js-linux-x64-glibc": "1.
|
|
64
|
-
"@harperfast/rocksdb-js-linux-x64-musl": "1.
|
|
65
|
-
"@harperfast/rocksdb-js-win32-arm64": "1.
|
|
66
|
-
"@harperfast/rocksdb-js-win32-x64": "1.
|
|
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
|
-
"node": ">=
|
|
70
|
+
"node": "^22.18.0 || >=24.0.0"
|
|
70
71
|
},
|
|
71
72
|
"gypfile": true,
|
|
72
73
|
"rocksdb": {
|