Skip to content

Redis Keys

Inventory of every Redis key pattern the backend reads or writes. Keys are grouped by domain. Patterns use {var} for runtime-substituted values.

This list is reconstructed by auditing backend/internal/platform/cache/cache.go (the Redis abstraction) and every caller across the domain packages under backend/internal/services/. If you add a new key, add it here too, since drift makes incident response painful.

Asynq queues

The background-task queues used by hibiken/asynq (asynq:* keys, including queue lists, scheduled sets, processing zsets, and unique-task locks) are managed internally by the library. They are not enumerated below — treat them as opaque. The library's own dashboard tools (asynq stats) are the right inspection path.

Authentication & sessions

Key Pattern Type TTL Purpose Set By Read By
refresh_token:{token} STRING refresh token expiry Maps a refresh-token string to its owning user_id. Deleted on logout. services/auth/session_service.go services/auth/session_service.go
register_device:{fingerprint} STRING 30 days Count of registrations from a device fingerprint. Throttles bulk-account abuse. services/auth/auth_service.go services/auth/auth_service.go
webauthn:reg:{user_id} STRING (JSON) 5 min WebAuthn registration session data (challenge + options). services/auth/auth_handler.go services/auth/auth_handler.go
webauthn:login:{user_id} STRING (JSON) 5 min WebAuthn login session data (challenge + options). services/auth/auth_handler.go services/auth/auth_handler.go
apple_public_keys STRING (JSON) 24 h Cached Apple JWKS used to verify Sign-in-with-Apple identity tokens. services/auth/auth_service.go services/auth/auth_service.go
user:privacy:{user_id} STRING ("0"/"1") 24 h Cached is_location_shared flag, avoids a DB hit on every location update. Invalidated when user toggles sharing. services/friend/friend_service.go services/friend/friend_service.go, services/auth/auth_service.go (invalidate)

Rate limiting & abuse

Most rate-limit keys are written by the Lua INCR + EXPIRE script in platform/cache/cache.go::RateLimit, keyed as {prefix}:{identifier} where identifier is the client IP (for the middleware presets) or a user id (for enrich:*). item:rl:* is the exception: a plain GetCache/SetCache counter rather than the atomic Lua limiter.

Key Pattern Type TTL Purpose Set By Read By
register_ip:{ip} INT 1 h Registration attempts per IP (default limit: 5/h). middleware/rate_limiter.go middleware/rate_limiter.go
login_ip:{ip} INT 15 min Login attempts per IP (default limit: 10/15 min). middleware/rate_limiter.go middleware/rate_limiter.go
api_ip:{ip} INT 1 min Baseline API requests per IP on /api/v1/* (limit: 600/min). middleware/rate_limiter.go middleware/rate_limiter.go
reset_password_ip:{ip} INT 1 h Password-reset token attempts per IP (limit: 5/h, prevents brute force). middleware/rate_limiter.go middleware/rate_limiter.go
item:rl:{user_id}:{minute} INT 2 min Per-user capture-item throttle (limit: 60/min). Windowed by unix-minute suffix; a plain INCR-style counter, not the Lua limiter. services/plan/item_service.go services/plan/item_service.go
enrich:parse:{user_id} INT 24 h Per-user/day budget for billable link-enrichment passes (CAPTURE_DAILY_PARSE_LIMIT, default 50). Dev/admin accounts bypass. services/plan/item_service.go services/plan/item_service.go
enrich:upgrade:google INT 24 h Global daily budget for upgrading Serper-minted google_places rows via the billed Places details API (limit 200/day). services/location/location_service.go services/location/location_service.go
blocked_ip:{ip} STRING (blocked:{reason}) configurable Hard-blocked IPs (set by IP-blocker middleware). middleware/ip_blocker.go middleware/ip_blocker.go
violations:{ip} INT configurable Cumulative violation counter, a graduated response before hard-blocking. middleware/ip_blocker.go middleware/ip_blocker.go

Presence & active location

Key Pattern Type TTL Purpose Set By Read By
presence:{user_id} STRING ("1") 3 min Heartbeat marker, user is online if key exists. Refreshed by client ping. services/presence/presence_service.go services/chat/service.go, services/friend/friend_service.go (via MGet)
active_location:{user_id} STRING (JSON) configurable User's currently-active shared location (the live pin). Deleted when user toggles off. services/presence/presence_service.go services/presence/presence_service.go, services/friend/friend_service.go (via MGet)
user:location:{user_id} STRING (JSON: {lat, lng, updated_at, arrived_at}) 24 h Latest location + stay-duration tracking. Backbone of nearby-friends discovery. services/friend/friend_service.go services/friend/friend_service.go, services/discovery/service.go, sim workers

Chat & messaging

Key Pattern Type TTL Purpose Set By Read By
chat:messages:{chat_id} LIST 24 h Cached recent messages for a chat (trimmed to 100). Read-through on hit, DB fallback on miss. services/chat/service.go services/chat/service.go
chat:online:{event_id}:{user_id} STRING 1 h Marker that {user_id} is currently active in event {event_id}. Deleted on offline. services/chat/service.go services/chat/service.go
chat:event:{event_id} Pub/Sub channel n/a Cross-pod chat fanout channel. Envelope {opid, xuid?, p}: opid is the publishing pod's UUID (subscriber drops own echoes), xuid skips a UserID on receive, p is the raw WS payload. SessionHub.publishRemote (internal/platform/ws/session_hub.go) SessionHub.runSubscriber (PSUBSCRIBE chat:event:* on every pod)

Friends

Key Pattern Type TTL Purpose Set By Read By
user:friends:{user_id} STRING (JSON) friendListCacheTTL (10 min) Cached accepted-friend list for a user. Invalidated on accept/remove. services/friend/friend_service.go services/friend/friend_service.go

Geo discovery

Key Pattern Type TTL Purpose Set By Read By
events:geo GEO (ZSET) none Geospatial index of all event locations. Backs GetNearbyEventIDs via GEORADIUS. platform/cache/cache.go::AddEventLocation(s) platform/cache/cache.go::GetNearbyEventIDs

Spatial data

Location coordinates live in Postgres (PostGIS geometry + denormalized lat/lng columns), not Redis. Redis only holds the raw lat/lng geo set above; viewport clustering happens in SQL inside services/discovery/service.go.

Utility caches

Key Pattern Type TTL Purpose Set By Read By
link-preview:{sha256(url)} STRING (JSON) 1 h OpenGraph metadata cache for outbound URL previews. Keyed by SHA-256 hex of the URL. services/media/link_preview_handler.go services/media/link_preview_handler.go
enrich:place:{location_id} STRING (JSON) 14 d A place's curated enrichment (discovered links, CID/FID, thumbnail) reused by later captures of the same place without a Serper spend. services/plan/item_service.go services/plan/item_service.go
enrich:item:lock:{item_id} STRING ("1") 5 min Per-item work lock (SETNX) so a double-fired link:enrich task can't double-spend the billable pass. Released on completion. services/plan/item_service.go services/plan/item_service.go
semantic:extract:{hash} / semantic:select:{hash} STRING (JSON) 14 d Memoized positive LLM answers, keyed by the first 16 bytes of the SHA-256 of the rendered prompt (hex); identical re-captures skip the model calls. platform/llm/semantic_resolver.go platform/llm/semantic_resolver.go
loc:area:{normalized_name} STRING (JSON, - = negative) 1 h Gazetteer area lookup by NFKC-folded name, so repeated area-scoped searches skip the DB; - caches a miss. services/location/location_area.go services/location/location_area.go
loc:qvec:{normalized_query} STRING (vector) 1 h Memoized query embedding for semantic location search, so repeat queries skip the embeddings call. services/location/location_embedding.go services/location/location_embedding.go

Async (Asynq)

Background work — both event-driven tasks and cron-driven tasks — runs through Asynq. All of Asynq's Redis keys (asynq:*, including queue lists, scheduled sets, processing zsets, archived/dead-letter, and asynq.Unique locks) are managed internally by the library and treated as opaque per the note at the top of this doc. See backend/infrastructure/async.md for the queue and scheduler model.

Cleanup keys

Used by the test-data seeder (cmd/test/data/main.go) to wipe state between simulation runs:

presence:*
user:location:*
active_location:*
user:friends:*
user:privacy:*

If you add a new user-scoped Redis key, add its prefix to that cleanup list too — otherwise stale data leaks between test runs.

Adding a new key

  1. Define the key shape in the service that owns it. Prefer domain:purpose:{id} (lowercase, colon-separated).
  2. Document it in the table above with type, TTL, purpose, writers, readers.
  3. If it's user-scoped and ephemeral, add the prefix to the simulation cleanup list.
  4. If it's a rate-limit key, route it through middleware/rate_limiter.go so the Lua atomic INCR script is used.