0002 — ETS counter substrate (not GenServer-per-tenant)
View Source- Status: Accepted
- Date: 2026-07-11
Context
The metering hot path must sustain high write throughput with correct concurrent increments, and must never become a per-tenant bottleneck.
Decision
Counters live in a shared, public ETS :set table and are incremented with
:ets.update_counter/4 (atomic, lock-free). A single Flusher process persists
snapshots to Postgres on an interval; a Broadcaster process fans live values
out over Phoenix.PubSub. There is no GenServer per tenant.
Consequences
- Increments are microsecond-scale and concurrency-safe without serialization through a process mailbox.
- Durability is eventual (≤ the flush interval). Billing-grade exactness is
opt-in via
:durablefeatures that also append to the events table (ADR 0003). - Hard-limit enforcement uses an atomic reserve (increment → compare → roll back on breach or on a raised function).