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/services/redis_service.go and every caller. If you add a new key, add it here too — 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/session_service.go |
services/session_service.go |
register_device:{fingerprint} |
STRING | 30 days | Count of registrations from a device fingerprint. Throttles bulk-account abuse. | services/auth_service.go |
services/auth_service.go |
webauthn:reg:{user_id} |
STRING (JSON) | 5 min | WebAuthn registration session data (challenge + options). | handlers/auth_handler.go |
handlers/auth_handler.go |
webauthn:login:{user_id} |
STRING (JSON) | 5 min | WebAuthn login session data (challenge + options). | handlers/auth_handler.go |
handlers/auth_handler.go |
apple_public_keys |
STRING (JSON) | 24 h | Cached Apple JWKS used to verify Sign-in-with-Apple identity tokens. | services/auth_service.go |
services/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_service.go |
services/friend_service.go, services/auth_service.go (invalidate) |
Rate limiting & abuse¶
All rate-limit keys are written by the Lua INCR + EXPIRE script in services/redis_service.go::RateLimit. The shape is {prefix}:{identifier} where identifier is usually the client IP.
| 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 |
places:ratelimit:{user_id} |
INT | per-user window | Throttles Google Places API calls per user. | services/places_service.go |
services/places_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 — 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_service.go |
services/chat_service.go, services/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_service.go |
services/presence_service.go, services/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_service.go |
services/friend_service.go, services/discovery_service.go, sim workers |
Chat & messaging¶
| Key Pattern | Type | TTL | Purpose | Set By | Read By |
|---|---|---|---|---|---|
chat:messages:{chat_room_id} |
LIST | 24 h | Cached recent messages for a chat room (trimmed to 100). Read-through on hit, DB fallback on miss. | services/chat_service.go |
services/chat_service.go |
chat:online:{chat_room_id}:{user_id} |
STRING | 1 h | Marker that {user_id} is currently active in {chat_room_id}. Deleted on offline. |
services/chat_service.go |
services/chat_service.go |
chat:event:{chat_room_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. |
websocket.Hub.publishRemote (internal/websocket/hub.go) |
websocket.Hub.runSubscriber (PSUBSCRIBE chat:event:* on every pod) |
Friends¶
| Key Pattern | Type | TTL | Purpose | Set By | Read By |
|---|---|---|---|---|---|
user:friends:{user_id} |
STRING (JSON) | friendListCacheTTL |
Cached accepted-friend list for a user. Invalidated on accept/remove. | services/friend_service.go |
services/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. |
services/redis_service.go::AddEventLocation(s) |
services/redis_service.go::GetNearbyEventIDs |
H3 indexing
H3 hex indices are stored in Postgres (locations.h3_index), not Redis. Redis only holds the raw lat/lng geo set above. H3-based 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) | configurable | OpenGraph metadata cache for outbound URL previews. Keyed by SHA-256 of the URL. | handlers/link_preview_handler.go |
handlers/link_preview_handler.go |
Scheduler (distributed task queue)¶
The custom Redis-backed scheduler in backend/internal/scheduler runs on every backend instance. Constants live in internal/scheduler/client.go and internal/scheduler/scheduler.go.
| Key Pattern | Type | TTL | Purpose | Set By | Read By |
|---|---|---|---|---|---|
tasks:scheduled |
ZSET | none | Scheduled tasks. Score = epoch timestamp, member = task_id. |
scheduler/client.go::ScheduleTask |
scheduler leader |
tasks:payload |
HASH | none | Task payloads. Field = task_id, value = JSON. |
scheduler/client.go |
scheduler workers |
tasks:queue:default |
LIST | none | Ready queue — tasks waiting to be picked up by a worker. | scheduler leader | scheduler workers (BRPOPLPUSH) |
tasks:processing |
LIST | none | Reliable processing queue — tasks currently executing. Populated atomically with the LIST pop. | scheduler workers | reclaimer (on worker crash) |
tasks:active:{task_id} |
STRING | task lease | Active lock / heartbeat for a running task. Reclaimer uses absence to detect stalled workers. | scheduler/worker.go |
scheduler/reclaimer.go |
scheduler:leader |
STRING | leader lease | Leader-election lock. Only the holder polls tasks:scheduled. Prevents duplicate enqueues. |
scheduler/scheduler.go |
all backend instances |
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¶
- Define the key shape in the service that owns it. Prefer
domain:purpose:{id}(lowercase, colon-separated). - Document it in the table above with type, TTL, purpose, writers, readers.
- If it's user-scoped and ephemeral, add the prefix to the simulation cleanup list.
- If it's a rate-limit key, route it through
middleware/rate_limiter.goso the Lua atomic INCR script is used.