Problem
Keep one exam countdown in sync for an examiner and all students, even when they are connected to different backend instances.
My role: Personal project. Built the NestJS backend and the Next.js examiner and student pages.
Constraints
- WebSocket connections are sticky to one instance, but a start or pause from the examiner must reach students on every instance.
- Concurrent control actions (start, pause, reset) must not corrupt the shared timer state.
Architecture
Next.js examiner / student pages
socket.io-client on the /exam namespace
NestJS ExamGateway
joinExam, createAndStartExam, pauseExam, resetExam, restartExam
Redis + Redlock
Room state keyed by exam id, guarded by timer-lock:{examId}
Kafka topics
examStarted, examPaused, examReset, examTimerUpdate
Every backend instance
Consumes the topic and emits to its local socket room
What I Built
- NestJS
ExamGateway(Socket.IO),ExamService, andRedisServiceusing ioredis and Redlock. - Kafka consumers with a per-instance group id, so every instance receives every event (fan-out) and relays it to its own sockets.
- Shared timer state in Redis, decremented once per second by the instance that started the exam.
- Next.js 15 examiner and student views.
Technical Challenges
- Cross-instance broadcast
- Kafka is used as a bus between instances: a unique consumer group per instance turns each topic into a broadcast.
- Concurrent state changes
- Control actions take a Redlock lock (2s TTL, 10 retries) before reading and writing the room state.
Trade-offs & Limitations
- Scope is timer synchronisation only: no auth, questions or persistence.
- The ticking interval lives on one instance; if it crashes, the countdown stops until restarted.
- Timer ticks write Redis without the lock, trading strictness for simplicity.
