Skip to content

Concurrency

The contention model is a single writer serialized per Bucket, event-sourced. The queue is what “takes the beating”; the event log is, at once, the FSM audit and the recovery mechanism.

A single writer per Bucket (sector × Unit type).

  • QUANTITATIVE: the Bucket’s capacity is a single-owner counter → serialized decrement, no oversell, no lock.
  • QUALITATIVE: locks on the Units inside the Bucket, naturally serialized by the writer.
  • A derby’s beating spreads across dozens of Buckets (one queue per Bucket) instead of hammering a single writer.

Cluster is domain, not a protocol primitive

Section titled “Cluster is domain, not a protocol primitive”

Two cases that look like a cluster:

  • A whole camarote table = domain granularity: the table is the Unit (a tables/restaurant catalog materializes “Table 5” as one Unit; the N seats are an attribute/capacity). Reserving = a Hold of 1 Unit. Zero cluster machinery.
  • Co-reservation of distinct Units (a wheelchair space + companion, possibly in different Buckets): declaration in the Catalog (a cluster_id, exposed in the ListUnits metadata) + a Reservations policy (“a Hold with a clustered Unit requires its mates → else POLICY_VIOLATION”) + the existing ATOMIC for all-or-nothing.

The writer keeps the Bucket’s state hot in memory and applies commands 1-to-1. Each effect is an append-only event persisted to Postgres → enabling replay to rebuild the Bucket state on recovery/failover. The event log unifies what the park kept separate: the FSM, the audit (fsmevent), and the recovery WAL.

  • Intra-Bucket (a family buying 4 seats together) → atomic for free: one command, one writer, all-or-nothing.
  • Cross-Bucket (seats in different sectors, or ticket + parking) → a 2-phase reservation between the writers involved (reserve → commit; if any refuses, roll the holds back). Coordinated by the service, not the Channel.
  • Every command carries an idempotency-key (from the Channel). A replay returns the prior result (IDEMPOTENT_REPLAY) — killing oversell-by-reissue and batch cleanup jobs.
  • Hold carries a TTL; the writer arms the timer and auto-Releases on expiry. Extend extends it. Fulfill is idempotent (booking token).