Skip to content

Redis

Redis is the backend's general-purpose hot store. It handles work that Postgres is too slow for, work that must coordinate across instances, and ephemeral state that doesn't deserve a table.

Source files referenced on this page:

A single Redis database (DB 0) holds everything. Keys are namespaced by feature.

Connection

There are two Redis clients in the process:

  1. The shared *redis.Client provided by Wire (wiring.ProvideRedisClient) — used by middleware, the scheduler, and any service that needs raw Redis access via the redis.Cmdable interface.
  2. A second client owned by RedisService (services.NewRedisService) — wraps the same Redis instance with typed helpers and the rate-limit Lua script.

Both connect to RedisConfig.Address() with the configured password. There's no Sentinel or Cluster wiring today — production runs against a single managed Redis instance.

Use cases

Cache

Generic key/value cache with TTL via RedisService.SetCache / GetCache. Used for:

  • Chat message history snippets — recent N messages per room are pushed onto a Redis list and trimmed (LPush + LTrim + Expire)
  • Profile snippets — discovery cards and short-lived denormalised reads
  • Geocoding results — Photon / Google Places responses cached for popular queries
  • Link previews — full OG metadata cached by SHA-256 of the URL (link-preview:<hash>)

Rate limiting

Per-route, per-IP rate limits run a Lua script in RedisService.RateLimit:

local current = redis.call("INCR", KEYS[1])
if tonumber(current) == 1 then
    redis.call("EXPIRE", KEYS[1], ARGV[1])
end
return current

Counters use route-specific key prefixes (api_ip:<ip> baseline, register_ip:<ip>, login_ip:<ip>, reset_password_ip:<ip>). See API → Rate Limiting and Security → Rate Limiting.

IP blocking

Two layers backed by Redis strings:

  • blocked_ip:<ip> — sticky block with a TTL (set by suspicious-activity middleware or the auto-blocker on repeated rate-limit violations)
  • violations:<ip> — rolling violation counter feeding the auto-blocker

Presence heartbeats

The presence service writes a TTL'd key per user every time the client heartbeats. Friends' clients read these to display online state. Active location-sharing sessions are stored as separate keys with their own TTL.

Device registration limits

register_device:<fingerprint> counts how many accounts have been created from one device fingerprint in a rolling 30-day window. See Security → Device Fingerprint.

Refresh token lookup

Refresh tokens are stored in Postgres for durability, but a parallel Redis lookup keeps the POST /api/v1/auth/refresh hot path off the DB.

Asynq task queue

Asynq uses Redis as its broker. Queues:

Queue Weight
critical 6
default 3
low 1

The Asynq server runs on every backend instance and consumes from all three. See Workers & Scheduler.

Distributed scheduler

The home-grown scheduler (separate from Asynq) uses Redis for:

  • tasks:scheduled — ZSET of pending tasks, score = epoch timestamp, member = task ID
  • tasks:payload — HASH of task payloads
  • tasks:queue:default — LIST of due tasks awaiting execution
  • tasks:processing — LIST of in-flight tasks (BRPOPLPUSH-managed)
  • tasks:active:<id> — STRING heartbeat lock for the reclaimer
  • scheduler:leader — leader-election lock (10s TTL)

This is not Asynq; it's our own engine that powers periodic jobs (status updates, account cleanups, message expiry). See Workers & Scheduler.

Pub/Sub

RedisService.Publish is the only Pub/Sub touchpoint — used sparingly to broadcast events across backend replicas where the WebSocket Hub can't reach (multi-instance broadcast is not yet implemented; see the WebSocket Hub page for the single-process caveat).

Key reference

The canonical list of Redis keys lives at Reference → Redis Keys. Every new feature that touches Redis should add its keys there with the schema, TTL, and owning service.

Failure mode

RedisService.RateLimit and the rate-limit middleware fail open — if Redis returns an error, the request is allowed through. This is intentional: a Redis outage shouldn't lock everyone out of the API. Other read paths (cache misses) silently fall back to the DB. Write paths (sessions, presence) will return an error to the caller.

Operational impact of a Redis outage

A Redis outage degrades the system but does not stop it: rate limiting becomes a no-op, presence and active-location features stop updating, scheduled tasks stop firing, and the chat cache cold-misses to Postgres. Restore Redis quickly — the system is designed to tolerate brief outages, not extended ones.