# betex
Deterministic, event-sourced matching engine for exchange odds and binary prediction markets.
## WAL Locking
Mutable WAL opens take a fail-fast single-writer lock on `WAL_ROOT/.writer.lock`.
This prevents two writer processes from accidentally opening the same WAL root at the same time.
The lock is held for the lifetime of the mutable journal handler and is released automatically
when the process exits. Read-only recovery via `JournalReader` does not take the writer lock.
This locking model is intended for local filesystems and single-attach block storage, including
typical Kubernetes deployments backed by `ReadWriteOnce` / `ReadWriteOncePod` persistent disks.
It is not a complete fencing solution for shared RWX storage such as GKE Filestore, NFS, SMB, or
other multi-node shared-disk environments. Those deployments need an external lease or fencing
mechanism in addition to any local file lock.
## WAL Sizing
`JournalConfig.max_segment_events` is a soft rotation hint, not a hard cap. The WAL poller commits
the full `poll_wait()` batch and only rotates after that commit on a tx boundary. In practice, the
true commit boundary is the available Disruptor backlog, bounded by ring size.
- Default live ring size is `32768`.
- Default `max_segment_events` is `5000`, so the nominal target is about `5000` events/segment.
- At roughly `500k` events/sec, `5000` events is about `10ms` of traffic.
- In live runs, Betex event payloads have been much smaller than the configured LMDB map:
observed segments were typically a few MiB, with the largest seen around `6.6 MiB`.
Sizing example for the current largest known batched-cancel payload:
- `OrderCancelledBatched` with `4096` cancelled orders serializes to about `32904` bytes.
- `5000 * 32904 ~= 157 MiB` raw payload.
- `32768 * 32904 ~= 1.004 GiB` raw payload for one full-ring `poll_wait()` commit.
Operational implication:
- If `poll_wait()` remains the effective commit boundary, size `segment_map_size_bytes` against the
largest expected poll batch, not just against `max_segment_events`.
- `max_segment_events` should be treated as a rotation hint for typical workloads, not as a strict
upper bound on segment occupancy.
## WAL Segment Retirement
When startup pruning or natural WAL rotation retires sealed segments, Betex moves
those segment directories into a fixed `retired/` directory under the WAL root with `rename`/`mv`
semantics. Betex does not delete from the retired directory; callers or operators own cleanup.
The runtime retention watermark is process-local and starts at `0` after restart. Callers that
persist an external recovery point should re-advance the watermark after opening the engine.
Betex checks that watermark only after a natural WAL segment rotation.