Goal
Eliminate DB writes from the session heartbeat (/api/sessions/activity). Currently each call does 2-3 SQL operations (INSERT/UPDATE + COUNT + occasional DELETE). Move to Redis sorted set — 3 commands pipelined into 1 HTTP call, zero DB writes.
Scope
- Add
heartbeatViaRedis(sessionId) — Redis sorted set online_sessions with pipeline (ZADD + ZREMRANGEBYSCORE + ZCARD)
- Extract existing DB logic into
heartbeatViaDb(sessionId) as fallback
- POST handler: Redis first, DB fallback via nullish coalescing (
??)
SESSION_TIMEOUT_MS constant replacing 2 duplicate calculations
- Pattern: same as
lib/quiz/quiz-answers-redis.ts — getRedisClient() null check + try/catch
Expected impact
- Zero DB writes in normal operation;
active_sessions table untouched when Redis is available
- 2-3 SQL queries per navigation replaced by 1 Redis HTTP call
Out of scope
What was implemented
heartbeatViaRedis(sessionId) — Redis sorted set online_sessions with pipeline (ZADD + ZREMRANGEBYSCORE + ZCARD = 1 HTTP call)
heartbeatViaDb(sessionId) — existing DB logic extracted as fallback
- POST handler: Redis first, DB fallback via
?? (nullish coalescing)
SESSION_TIMEOUT_MS constant replacing 2 duplicate calculations
- Zero DB writes in normal operation;
active_sessions table untouched when Redis is available
- Verified in Upstash CLI: sorted set populating correctly, scores updating on navigation
Files changed:
frontend/app/api/sessions/activity/route.ts — rewritten with Redis-first pattern
Goal
Eliminate DB writes from the session heartbeat (
/api/sessions/activity). Currently each call does 2-3 SQL operations (INSERT/UPDATE + COUNT + occasional DELETE). Move to Redis sorted set — 3 commands pipelined into 1 HTTP call, zero DB writes.Scope
heartbeatViaRedis(sessionId)— Redis sorted setonline_sessionswith pipeline (ZADD + ZREMRANGEBYSCORE + ZCARD)heartbeatViaDb(sessionId)as fallback??)SESSION_TIMEOUT_MSconstant replacing 2 duplicate calculationslib/quiz/quiz-answers-redis.ts—getRedisClient()null check + try/catchExpected impact
active_sessionstable untouched when Redis is availableOut of scope
active_sessionstable (kept as fallback)What was implemented
heartbeatViaRedis(sessionId)— Redis sorted setonline_sessionswith pipeline (ZADD + ZREMRANGEBYSCORE + ZCARD = 1 HTTP call)heartbeatViaDb(sessionId)— existing DB logic extracted as fallback??(nullish coalescing)SESSION_TIMEOUT_MSconstant replacing 2 duplicate calculationsactive_sessionstable untouched when Redis is availableFiles changed:
frontend/app/api/sessions/activity/route.ts— rewritten with Redis-first pattern