SapixDBSapixDB/Docs
Home

Manual · Storage

Strand Storage

SapixDB persists data as a sequence of append-only binary segment files backed by a write-ahead log. This page explains the on-disk layout, how segments are sealed, how the WAL enables crash recovery, and how chain integrity is verified.

On-disk directory layout

Three root directories hold all persistent data. Each is configurable via an environment variable.

Directory tree
$SAPIX_STRAND_DIR/          # default: /data/strand
  <agent_id>/
    segment_00000.bin       ← sealed, immutable (~4 MB)
    segment_00001.bin       ← sealed, immutable
    segment_00002.bin       ← active (current writes land here)
    wal.bin                 ← write-ahead log (active)
    chain.bin               ← chain head pointer + metadata

$SAPIX_GRAPH_DIR/           # default: /data/graph
  edges/                    ← directional typed edges between agents
  meta/                     ← graph metadata (FTS indexes, etc.)

$SAPIX_BLOB_DIR/            # default: /data/blobs
  <sha256-hash>             ← content-addressed blob storage
Environment variableDefaultContents
SAPIX_STRAND_DIR/data/strandStrand segments, WAL, and chain metadata for all agents.
SAPIX_GRAPH_DIR/data/graphGraph edges and graph-layer metadata keys.
SAPIX_BLOB_DIR/data/blobsContent-addressed blobs, keyed by SHA-256 hash.

Segment files

Each agent's strand is stored as a sequence of numbered segment files (segment_00000.bin, segment_00001.bin, …). Segments are pure append-only binary files — writes only ever extend the tail of the active segment.

Active vs. sealed segments

At any moment there is exactly one active segment per agent — the one currently receiving new records. When the active segment reaches approximately 4 MB it is sealed: its content is finalised, a new segment file is created, and all subsequent writes go to the new file.

StateMutable?Safe to copy / back up?
Active (last segment)Yes — records are appendedSnapshot only — may be mid-write
Sealed (all earlier segments)No — immutableYes — safe to copy, replicate, or archive at any time
Sealed segments are immutable by designOnce sealed, a segment file is never modified. This makes sealed segments safe to copy to object storage (S3, R2, etc.) for cheap archival or replication without locking or coordination.

Write-ahead log (WAL)

Before any record write is acknowledged to the caller, it is first appended to wal.bin in the same agent directory. Only after the WAL entry is durable does the API return a 200 OK.

Crash recovery

On startup the agent checks whether wal.bin contains uncommitted entries. If it does, it replays them into the active segment before accepting new writes. This guarantees that no acknowledged write is ever lost, even if the process crashes between the WAL write and the segment write.

Recovery sequence
1. Agent starts
2. Open wal.bin for agent_id
3. If wal.bin has uncommitted entries → replay into active segment
4. Clear / truncate wal.bin
5. Begin accepting API requests
WAL entries are plaintextEven when payload encryption is enabled, WAL entries are stored in plaintext. The WAL is cleared when a segment seals, so the exposure window is bounded by segment size (~4 MB of writes). Ensure SAPIX_STRAND_DIR resides on an encrypted volume if full at-rest protection is required.

WAL entry contents

FieldNotes
timestamp_hlcHybrid logical clock timestamp assigned at write time.
flagsBitmask (e.g. 0x20 for encrypted payload).
payloadMessagePack-encoded record data. Always plaintext in the WAL.

Chain integrity

Every record stores a cryptographic hash that chains it to its predecessor. This makes tampering with any past record detectable without replaying the entire data set.

Hash formula
content_hash = BLAKE3(
  parent_hash    ‖   // hash of the previous record (zeros for first)
  timestamp_hlc  ‖   // 8-byte HLC timestamp
  flags          ‖   // 1-byte flags
  payload_msgpack    // raw MessagePack bytes
)

When verifying integrity, the agent recomputes content_hash for each record and checks that the parent_hash stored in the next record matches. A mismatch at any position indicates that the segment file has been modified after sealing.

Chain verification is key-independentBecause content_hash and parent_hash are stored in plaintext, chain integrity can be verified without the master seed even on an encrypted data directory.

Storage sizing

Use the following figures to estimate disk usage:

ComponentSize
Record overhead (without encryption)~80 bytes per record (record_id + content_hash + parent_hash + timestamp_hlc + flags)
Encryption overhead+28 bytes per record (12-byte nonce + 16-byte auth tag)
PayloadMessagePack-encoded JSON — typically 20–50% smaller than raw JSON
Segment seal threshold~4 MB per segment file

As a rough rule: for 1 million records with an average payload of 200 bytes, expect approximately 250–280 MB of segment data with encryption enabled.

Configuring storage paths

Set these environment variables before starting the agent to override the default data directories:

Environment
SAPIX_STRAND_DIR=/mnt/fast-nvme/strand
SAPIX_GRAPH_DIR=/mnt/fast-nvme/graph
SAPIX_BLOB_DIR=/mnt/object-store/blobs
Use a fast local disk for SAPIX_STRAND_DIRStrand segments and the WAL are write-critical paths. Use a local NVMe or SSD for SAPIX_STRAND_DIR. Sealed segments can be tiered to slower or network-attached storage after the fact since they are immutable.

Backup strategy

Because sealed segments are immutable, an incremental backup only needs to copy newly sealed segments since the last backup run. The active segment and WAL require a coordinated snapshot or a brief agent pause for consistency.

  1. Copy all sealed segment files (segment_00000.bin through segment_N-1.bin) — these are safe to copy at any time.
  2. Pause writes briefly (or use the snapshot API) to capture the active segment and chain.bin.
  3. Back up SAPIX_MASTER_SEED separately in a secrets manager.