Skip to content
Back to Projects
Exam-Sync logo

Exam-Sync

Synchronised exam timer across multiple backend instances using Socket.IO, Redis with Redlock, and Kafka fan-out.

WebSocketsRedisKafkaEvent-Driven ArchitectureNext.jsTypeScript

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

  1. Next.js examiner / student pages

    socket.io-client on the /exam namespace

  2. NestJS ExamGateway

    joinExam, createAndStartExam, pauseExam, resetExam, restartExam

  3. Redis + Redlock

    Room state keyed by exam id, guarded by timer-lock:{examId}

  4. Kafka topics

    examStarted, examPaused, examReset, examTimerUpdate

  5. Every backend instance

    Consumes the topic and emits to its local socket room

What I Built

  • NestJS ExamGateway (Socket.IO), ExamService, and RedisService using 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.