← work
web · 2024

Vote4GOAT — real-time election monitoring

Django app with Keycloak SSO, Kafka event streaming, Redis cache, Postgres of record, fronted by Nginx. Full Docker Compose. Tally and audit log streamed live to authenticated observers.

roleBackend Engineer
timespan2024
stackdjangopostgresrediskafkakeycloakdockernginx
statuscomplete

Overview

Vote4GOAT is a real-time election monitoring system built for the 2024 academic election cycle. The system needed to handle concurrent vote submissions, provide live tally updates to authenticated observers, and maintain an immutable audit log.

Architecture

Seven services in Docker Compose:

| Service | Role | |---|---| | Django | Application server, REST API, admin | | Postgres | Source of truth for votes and users | | Kafka | Event stream for vote events and tally updates | | Redis | Cache for live tally aggregates | | Keycloak | SSO and RBAC (voters, observers, admins) | | Nginx | Reverse proxy and static file serving | | Celery | Background workers for tally computation |

Event flow

  1. Voter authenticates via Keycloak (OIDC)
  2. Vote submission → Django validates eligibility → writes to Postgres → emits Kafka event
  3. Celery consumer reads Kafka event → updates Redis tally aggregate
  4. Observer dashboard polls Redis for live counts (1-second intervals)
  5. All events written to immutable audit log in Postgres (append-only table, no UPDATE/DELETE)

Key decisions

Kafka for vote events — even at this scale, the event log was valuable for post-election audit and replay. The operational vocabulary (consumer groups, offsets) matched what the audit requirements needed.

Keycloak for SSO — role separation (voter / observer / admin) was a hard requirement. Hand-rolling it would have been risky; Keycloak got us RBAC and a proper token flow in an afternoon.

Redis for live tallies — Postgres can handle the write load, but the observer dashboard at peak had ~50 concurrent pollers. Caching the aggregates in Redis reduced DB load by ~80%.