This document describes the binary format used to store audit/transaction log entries in Harper. The format is designed for efficient storage and retrieval of database operation records with support for CRDT operations, replication, and change tracking.
Each transaction log entry consists of two parts:
- Transaction Log Header (stored in RocksDB transaction log)
- Audit Entry Data (the binary payload)
Located at the beginning of each transaction log entry before the audit entry data:
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 4 bytes | Structure Version + Header Flags | Lower 24 bits: version number, Upper 8 bits: flags |
| 4 | 4 bytes | Previous Residency ID | Optional, present if HAS_PREVIOUS_RESIDENCY_ID (0x40) flag is set |
| 8/12 | 8 bytes | Previous Version | Optional, present if HAS_PREVIOUS_VERSION (0x20) flag is set. IEEE 754 double |
Header Flags:
0x80- HAS_32_BIT_FLAG (reserved for future use)0x40- HAS_PREVIOUS_RESIDENCY_ID0x20- HAS_PREVIOUS_VERSION
The audit entry data follows the transaction log header and contains the actual operation details.
| Section | Size | Description |
|---|---|---|
| Previous Local Time | 0 or 8 bytes | Optional previous version timestamp |
| Action + Flags | 1, 2, or 4 bytes | Operation type and extended flags |
| Node ID | 1, 2, 4, or 5 bytes | Node identifier (variable-length integer) |
| Table ID | 1, 2, 4, or 5 bytes | Table identifier (variable-length integer) |
| Record ID Length | 1, 2, 4, or 5 bytes | Length of record ID |
| Record ID | Variable | Binary-encoded record identifier |
| Version | 8 bytes | IEEE 754 double timestamp |
| Current Residency ID | 0, 1, 2, 4, or 5 bytes | Optional, if HAS_CURRENT_RESIDENCY_ID flag |
| Previous Residency ID | 0, 1, 2, 4, or 5 bytes | Optional, if HAS_PREVIOUS_RESIDENCY_ID flag |
| Expiration Time | 0 or 8 bytes | Optional, if HAS_EXPIRATION_EXTENDED_TYPE flag |
| Originating Operation | 0, 1, 2, 4, or 5 bytes | Optional, if HAS_ORIGINATING_OPERATION flag |
| Username Length | 1 or 2 bytes | Length of username (0 if no username) |
| Username | Variable | Binary-encoded username string |
| Record Value | Variable | Optional, encoded record data (not present for deletes) |
- Present when: The first byte is
0x42(66 decimal, which is the first byte of an IEEE 754 double) - Format: 8-byte IEEE 754 double-precision float
- Purpose: Stores the previous version timestamp for change tracking
Variable-length integer encoding the operation type and extended type flags.
Base Action Types (bits 0-3):
| Value | Name | Description |
|---|---|---|
| 1 | PUT | Insert or update a record |
| 2 | DELETE | Delete a record |
| 3 | MESSAGE | Message/event record |
| 4 | INVALIDATE | Invalidate a record (CRDT) |
| 5 | PATCH | Partial update (CRDT) |
| 6 | RELOCATE | Move record to different residency |
| 7 | STRUCTURES | Schema/structure change |
| 11 | REMOTE_SEQUENCE_UPDATE | Remote local time update |
| 14 | ACTION_32_BIT | 32-bit action flag |
| 15 | ACTION_64_BIT | 64-bit action flag |
Record Content Flags (bits 4-5):
| Value | Name | Description |
|---|---|---|
| 16 (0x10) | HAS_RECORD | Full record data included |
| 32 (0x20) | HAS_PARTIAL_RECORD | Partial record data (for CRDTs) |
Additional Flags (upper bits):
| Value | Name | Description |
|---|---|---|
| 64 (0x40) | HAS_PREVIOUS_VERSION | Previous version timestamp included |
| 128 (0x80) | HAS_EXTENDED_TYPE | Extended type information |
| 512 (0x200) | HAS_CURRENT_RESIDENCY_ID | Current residency ID included |
| 1024 (0x400) | HAS_PREVIOUS_RESIDENCY_ID | Previous residency ID included |
| 2048 (0x800) | HAS_ORIGINATING_OPERATION | Originating operation type included |
| 4096 (0x1000) | HAS_EXPIRATION_EXTENDED_TYPE | Expiration timestamp included |
| 8192 (0x2000) | HAS_BLOBS | Binary blob data included |
Integers (Node ID, Table ID, lengths) use a variable-length encoding scheme:
| First Byte | Total Size | Value Range | Encoding |
|---|---|---|---|
| 0x00-0x7F | 1 byte | 0-127 | Direct value |
| 0x80-0xBF | 2 bytes | 128-16,383 | (uint16 & 0x7FFF) |
| 0xC0-0xFE | 4 bytes | 16,384-1,073,741,823 | (uint32 & 0x3FFFFFFF) |
| 0xFF | 5 bytes | 1,073,741,824+ | Next 4 bytes = uint32 value |
Both Record ID and Username use a length-prefixed encoding:
Length Encoding:
- 1 byte: If length ≤ 127 (0x7F)
- Value: direct length
- 2 bytes: If 128 ≤ length ≤ 16,383 (0x3FFF)
- Value:
(uint16 & 0x7FFF) | 0x8000
- Value:
- Maximum: 16,383 bytes
Data Encoding:
- Uses
ordered-binaryencoding for proper sorting and comparison - Binary data follows immediately after length prefix
When HAS_ORIGINATING_OPERATION flag is set, indicates the SQL operation that created this entry:
| Value | Operation |
|---|---|
| 1 | insert |
| 2 | update |
| 3 | upsert |
- Present for: PUT, MESSAGE, PATCH, INVALIDATE operations
- Absent for: DELETE, RELOCATE operations
- Format: Binary-encoded using the same encoding as the primary record store
- Location: Starts at
decoder.positionafter all header fields - Size: Extends to end of entry buffer
Transaction log keys are timestamps encoded as IEEE 754 doubles:
- Format: 8-byte IEEE 754 double-precision float
- Value: Milliseconds since Unix epoch (Date.now())
- Special Values:
LAST_TIMESTAMP_PLACEHOLDER: Reserved placeholder valuePREVIOUS_TIMESTAMP_PLACEHOLDER: Reserved placeholder value
- Maximum key/username size: 16,383 bytes (2-byte length header)
- Entry header buffer size: 2,816 bytes (accommodates max key size + large usernames)
- Total entry size: Limited by RocksDB/LMDB constraints
Byte sequence for a delete operation:
[0x02] [0x05] [0x0A] [0x04] [0x61,0x62,0x63,0x64] [64-bit timestamp] [0x00]
│ │ │ │ │ │ │
│ │ │ │ │ │ └─ No username (0 length)
│ │ │ │ │ └─ Version timestamp
│ │ │ │ └─ Record ID: "abcd"
│ │ │ └─ Record ID length: 4
│ │ └─ Table ID: 10
│ └─ Node ID: 5
└─ Action: DELETE (2)
[0x42,...] [0x11] [0x05] [0x0A] [...] [...] [...] [0x04] [0x75,0x73,0x65,0x72] [...]
│ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ └─ Encoded record value
│ │ │ │ │ │ │ │ └─ Username: "user"
│ │ │ │ │ │ │ └─ Username length: 4
│ │ │ │ │ │ └─ Version timestamp
│ │ │ │ │ └─ Record ID
│ │ │ │ └─ Record ID length
│ │ │ └─ Table ID: 10
│ │ └─ Node ID: 5
│ └─ Action: PUT (1) | HAS_RECORD (16) = 17
└─ Previous local time (8-byte double starting with 0x42)
- Endianness: All multi-byte integers use big-endian encoding
- Buffer Reuse: Implementation uses a reusable 2,816-byte buffer (
ENTRY_HEADER) to reduce allocations - Lazy Decoding: Record IDs, usernames, and values use lazy getters to decode only when accessed
- Sorting: Transaction log entries are naturally sorted by timestamp key
- Cleanup: Old entries are periodically removed based on audit retention policies
- Structure Version: Stored in lower 24 bits of transaction log header
- Compatibility: Extended type flags allow backward-compatible schema evolution