Back to docs
Recipe

Ticket queue design

A bounded FIFO queue with priority lanes, expiry, and circuit breakers — the backbone of every licensing backend.

Core shape

A ticket queue is a fixed-capacity FIFO with three priority lanes — high, normal, low — drained by a pool of workers. Each ticket carries a TTL; expired tickets are silently dropped on dequeue. A circuit breaker halts enqueue when the queue reaches 80% capacity, returning HTTP 503.

Data model

ticket {
  id:        uuid v7
  lane:      high | normal | low
  payload:   jsonb
  created:   timestamp
  expires:   timestamp
  attempts:  uint8 (max 3)
}

Worker loop

  1. Poll high lane → normal → low (strict priority).
  2. Skip expired tickets; increment dead-letter counter.
  3. Process payload; on failure, increment attempts.
  4. If attempts < 3, re-enqueue with exponential backoff.
  5. If attempts ≥ 3, move to dead-letter store.

Circuit breaker

A sliding-window counter tracks enqueue rate. When the window exceeds 80% of capacity for 3 consecutive sampling intervals, the breaker opens. Enqueue returns 503 with a Retry-After header. The breaker half-opens after 30s, allowing one probe request. Success closes it; failure re-opens.

Storage backends

Redis

Sorted sets per lane, ZPOPMIN for dequeue, TTL via score. Lua scripts for atomic dequeue + re-enqueue.

PostgreSQL

SKIP LOCKED queries, partial indexes on lane + expiry, advisory locks for worker coordination.

Observability

  • queue.depth — gauge per lane
  • queue.enqueue.rate — counter
  • queue.expired — counter
  • queue.dead_letter — counter
  • circuit.breaker.state — gauge (0 closed, 1 open, 2 half-open)