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:{sha256_hex} STRING refresh token expiry Maps a refresh token to its owning user_id, keyed by the hex SHA-256 of the token so the keyspace never carries a usable credential. Written on issue, deleted on logout and every revocation path. 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)
usercontext:{user_id} STRING (JSON) 5 min Cached UserContext for search personalization: category affinities, saved/dismissed places, interaction edges. Invalidate drops it after a save/dismiss/affinity change; else the short TTL bounds staleness. services/personalization/service.go services/personalization/service.go
affinity:reconciled:{user_id} STRING ("1") 7 days Lazy category-affinity reconcile watermark. Key present means the profile was recomputed within the window (skip); absent (expired or never) means a reconcile is due. Set after each reconcile, including the dormant-skip case. services/maps/service.go services/maps/service.go
feed:seen:{user_id} STRING (JSON [momentId,…]) 14 days Home-feed seen-set: moment ids the viewer has scrolled past, so the moments pager leads with unseen cards. Bounded to 500 ids (oldest trimmed). Written by POST /discovery/feed/seen, read when assembling the feed. services/discovery/feed.go services/discovery/feed.go
feed:snap:{user_id} STRING (JSON [{id,tier,seen},…]) 15 min Frozen ranked order for feed pagination: written on the first /discovery/feed request, read by ?cursor= requests so later pages window into a stable order instead of re-ranking against an advancing seen-set. services/discovery/feed.go services/discovery/feed.go

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. Deleted when the user turns is_location_shared off or stops an active-location session; readers re-check is_location_shared anyway, since the TTL can outlive the toggle. 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

Live activity (anonymous)

Anonymous aggregate powering the public marketing globe (GET /api/v1/stats/live). {w} is a 10-minute window (unix / 600); {bucket} is a coarse 1-degree cell ("lat_lng", values from math.Round). User IDs are fed into HyperLogLog only (irreversible distinct-count), never stored retrievably, so no ID or precise coordinate is kept. Read path merges the current + previous windows and drops any bucket with fewer than 5 distinct users (k-anonymity).

Key Pattern Type TTL Purpose Set By Read By
stats:live:g:{w} HLL 1500 s (25 min) Global distinct active users in window w. services/friend/friend_service.go services/friend/friend_service.go
stats:live:b:{w}:{bucket} HLL 1500 s (25 min) Distinct active users in a 1-degree bucket for window w. services/friend/friend_service.go services/friend/friend_service.go
stats:live:set:{w} SET 1500 s (25 min) Enumeration of active buckets in window w (which HLLs to read). services/friend/friend_service.go services/friend/friend_service.go
stats:live:v1 STRING (JSON) 45 s Read-through cache of the assembled aggregate served to the globe. services/friend/friend_service.go services/friend/friend_service.go
stats:live:tmp:g, stats:live:tmp:b HLL (temp) deleted after use PFMERGE scratch keys for cross-window distinct counts. 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.