Skip to content

Redis for station availability

Field Value
status accepted
date 2026-03-12
decision-makers CityCycle engineering

Unlock latency and map freshness are the top two quality goals. Reading free-bike counts from PostgreSQL on every map tile and every unlock attempt will not meet the 3 s unlock and 30 s freshness scenarios under morning peak. How do we serve station availability?

  • Unlock p95 under 3 seconds
  • Map counts at most 30 seconds stale
  • Redis must not become the billing ledger
  • Operational simplicity for a small team
  • Redis as availability cache and short-lived unlock lock, PostgreSQL remains source of truth
  • Materialized view / read replica in PostgreSQL only
  • In-process cache per API replica
  • Separate “availability service” with its own store

Chosen option: “Redis as availability cache and short-lived unlock lock, PostgreSQL remains source of truth”, because it meets the latency and freshness goals without moving trip integrity out of PostgreSQL.

  • Good, because unlock can reserve a bike with a TTL lock before talking to the dock
  • Good, because the operator city-wide list reads the same snapshot
  • Bad, because we must define a clear fallback when Redis is down
  • Bad, because cache invalidation bugs can show a bike that is already gone (mitigated by the lock + dock ack)

Load test the morning-peak unlock scenario against staging with Redis. Chaos-test Redis downtime and confirm unlock still succeeds via PostgreSQL fallback with a degraded map banner.

  • Good, because low-latency reads and atomic reserve
  • Good, because well understood managed offering
  • Bad, because another moving part in production

PostgreSQL read replica / materialized view only

Section titled “PostgreSQL read replica / materialized view only”
  • Good, because one database technology
  • Bad, because unlock locking and sub-second map fan-out are a poor fit
  • Bad, because peak read load lands on the database we also need for billing
  • Good, because no extra infrastructure
  • Bad, because replicas disagree; unlock races across instances
  • Bad, because deploys flush the cache and stampede PostgreSQL
  • Good, because a clean boundary
  • Bad, because operational cost is too high for the current team
  • Bad, because it can wait until a real scaling need appears

Unlock flow: runtime view. Quality scenarios: section 10.