Skip to content

Storage Architecture

Chronacta is a single-node append-only event store. The authoritative data path is:

storage.Storage -> engine.Engine -> wal.WAL + segment.Segment

A single commit mutex serializes validation, optimistic version checking, assignment of stream versions and global positions, deterministic encoding, WAL write, segment write, sync policy, in-memory indexes, and subscription notification. A response is successful only after the selected durability policy completes.

pkg/storage/record is the only record codec. Records contain a total length, magic, format version, checksum, batch ID, event ID, global position, stream version, nanosecond timestamp, schema information, bounded field lengths, deterministic metadata, and payload. Maximum record size is 32 MiB; payloads are limited to 16 MiB. Unknown versions, malformed lengths, checksum failures, and truncated records are explicit errors.

WAL files contain versioned checksummed batch frames. Each frame carries a batch ID and the same encoded record bytes used by segments. Startup reads frames sequentially. A partial final frame is truncated to its last complete boundary; a malformed complete frame or corruption is fatal. A batch already represented in segments is compared by batch ID and event contents instead of being appended again.

Segments live under data/segments as paired zero-padded .log and .index files. Index entries contain global position, file offset, and record length. Reads use ReadAt and an in-memory index, so concurrent readers do not share seek state. Segment files are ordered by base global position. The active segment rotates before a batch would exceed the configured maximum and sealed segments reject appends.

Startup requires complete log/index pairs and validates indexed records, checksums, positions, stream versions, event IDs, and unindexed tails. VerifyStorage repeats these checks across all segments and WAL frames and returns counts, sizes, last position, and diagnostics.

Stream reads use inclusive stream versions; version 0 means the beginning for forward reads and the current end for backward reads. $all reads use inclusive global positions; position 0 means the beginning. next_version and next_position are the next cursors.

Two delivery models:

  • Transient (Subscribe): catch-up from an inclusive stream version, then live events. When the client falls behind live delivery, the server enters catch-up mode and continues delivery instead of disconnecting. Legacy slow-subscriber disconnect is available with CHRONACTA_SUBSCRIPTION_CATCHUP_MODE=false. SubscribeResponse.control may carry SUBSCRIBE_CONTROL_CATCHING_UP / SUBSCRIBE_CONTROL_LIVE.
  • Durable: persistent checkpoint with pull/ack/nack and competing consumers on one subscription id. See ../operations/subscriptions.md.

Projections read $all through a separate manager and keep isolated checkpoints under data/projections/.