Archetype 8 — Time-Bounded Exclusive Allocation #
What this archetype is #
A user gets a temporary exclusive hold over a scarce resource. The hold expires unless confirmed.
Examples: ticket hold, appointment hold, waitlist claim window.
We will use ticket hold as the running example.
Layer 1: Entities and Postgres table design #
HoldState
BookingState
InventoryState
create table ticket_holds (
hold_id uuid primary key,
seat_id bigint not null,
user_id bigint not null,
status text not null default 'ACTIVE',
expires_at timestamptz not null,
created_at timestamptz not null default now(),
unique (seat_id, status) deferrable initially immediate
);
create index ticket_holds_expires_idx
on ticket_holds (status, expires_at);
In practice, the uniqueness is often implemented as:
- one active hold row per seat
- or seat inventory row plus
hold_id
Layer 2: Write path mechanics #
Create hold #
insert into ticket_holds (
hold_id, seat_id, user_id, status, expires_at
) values (
$1, $2, $3, 'ACTIVE', now() + interval '10 minutes'
);
Confirm hold #
begin;
update ticket_holds
set status = 'CONFIRMED'
where hold_id = $1
and status = 'ACTIVE'
and expires_at > now();
insert into bookings (...);
commit;
Expire hold #
update ticket_holds
set status = 'EXPIRED'
where status = 'ACTIVE'
and expires_at <= now();
Layer 3: Fault tolerance #
- duplicate hold
- expired hold not released
- confirm-after-expiry race
- cancel/confirm race
- stale availability after hold expiry
Layer 4: Scale #
Default hotspots:
- hot hold key
- hold storms
- expiry scan pressure
- hold-store contention
Common mitigations:
- partition expiry scans by time bucket
- keep active holds in narrow indexed table
- queue in front of hold creation for flash sales