Redis & Caching
Redis serves as the ephemeral data layer: query caching, rate limiting, session preferences, and async event delivery via Streams. All data in Redis is expendable — losing it causes cache misses, not data loss.
Engine: Redis 7
Port: 6380 (offset from FalkorDB on 6379)
Redis (port 6380) is the application cache. FalkorDB (port 6379) is the graph database. They are separate Redis instances. Never confuse them.
Cache Key Format
All cache keys follow the pattern: gl:<resource>:<id>:<params>
Content Query Cache
| Key Pattern | TTL | Description |
|---|---|---|
gl:passage:<passageId>[:<suffix>] | 3600s (1h) | Single passage; suffix = witnesses, words, xrefs, commentary |
gl:chapter:<bookId>.<chapter>:<translation> | 3600s (1h) | Full chapter |
gl:topic:<topicId> | 3600s (1h) | Topic with passage list |
gl:lexicon:<wordId> | 86400s (24h) | Lexicon entry (rarely changes) |
Immutable scripture content is cached for 1 hour. Lexicon entries, which change even less frequently, are cached for 24 hours.
Entitlement Cache
| Key Pattern | TTL | Description |
|---|---|---|
gl:entitlements:<planId>:<feature> | 60s | Plan feature access ("1" or "0") |
Short TTL ensures plan changes propagate within a minute.
AI Response Cache
| Key Pattern | TTL | Description |
|---|---|---|
gl:ai:cache:<promptHash> | 3600s (1h) | Cached LLM response (semantic caching) |
Avoids redundant LLM API calls for similar prompts. The prompt hash covers the system prompt, user messages, and context.
Session Preferences
| Key Pattern | TTL | Description |
|---|---|---|
gl:session:<userId>:prefs | 604800s (7d) | User display preferences (JSON) |
Clerk manages primary sessions; Redis stores device-specific preferences (font size, reading mode, etc.).
Rate Limiting
Rate limit state is stored in Redis using a token bucket algorithm:
gl:ratelimit:<userId>:<endpoint>:<window_start> → INT (request count)
Rate Limit Tiers
| Endpoint | Free | Paid (10×) | AI |
|---|---|---|---|
| Search | 20/min | 200/min | — |
| Passages | 60/min | 600/min | — |
| Lexicon | 40/min | 400/min | — |
| AI requests | 5/hour | 50/hour | 200/hour |
The gateway checks rate limits before proxying. Limits are per-user when authenticated, per-IP for unauthenticated requests.
Redis Streams (Event Queue)
Redis Streams provide at-least-once delivery for async events between services. Each stream has one or more consumer groups.
Streams
| Stream | Publishers | Consumers | Purpose |
|---|---|---|---|
gl:events:notifications | Any service | Notifications | Push/email dispatch |
gl:events:users | Auth service | Multiple | User lifecycle (created, updated) |
gl:events:ingest | Ingest pipeline | Content | Index refresh triggers |
Consumer Group Pattern
XREADGROUP GROUP notifications-workers consumer-1
STREAMS gl:events:notifications >
COUNT 10
BLOCK 5000
Each notification worker reads up to 10 messages, processes them (one goroutine per message), and ACKs on success. Failed messages are retried via the pending entries list (PEL).
Distributed Locks
The ingest pipeline uses Redis locks to prevent concurrent writes:
gl:lock:ingest:<schemaName> → STRING (120s TTL)
Locks auto-expire after 120 seconds to prevent deadlocks if the ingest process crashes.
Hosting Progression
| Phase | Provider | Instance |
|---|---|---|
| Phase 1 | Docker | Single instance |
| Phase 2 | ElastiCache | cache.r6g.large |
| Phase 3 | ElastiCache Cluster Mode | Multi-node |
Related Pages
- Data Architecture Overview — When to use Redis vs other stores
- Service Communication — Redis Streams for async events
- Entitlements — Entitlement cache design