Skip to content
All projects
Case studyCS/02
StatusPersonal project
TypeSystems
Year2026

Foyer

A fair, open-source virtual waiting room for ticket drops, Tatkal and admission portals

When millions arrive at once, Foyer holds the crowd, gives everyone who came early an equal chance through a lottery anyone can verify, makes bots pay a proof-of-work toll for every place, and lets people through at exactly the rate the site can take. Admitted visitors carry Ed25519 passes the site checks offline.

  • 0 errors in 72,600 requests
  • Status p50 1.4 ms
  • Verifiable lottery
  • 4 s leader failover

§1

The problem

About 13 million people tried to buy Coldplay's Mumbai tickets and the site went down; Indian Railways said more than half of Tatkal attempts were bots. Sites crash, bots take the stock, and nobody can check whether the queue was fair. The commercial answer is quote-only enterprise software.

§2

My role

Personal project, end to end: the queue, room and insight services, the dependency-free foyer-core library, the Spring Boot guard plugin, the Angular waiting room and console, load and chaos tests, and Kubernetes manifests with KEDA.

§3

Architecture

Fig. 1 · Foyer architecture
outboxsigned passVISITORSwaiting room · PoWORGANISERSAngular consoleEDGE-GATEWAYper-IP limits · JWTQUEUE-SERVICE ×Njoin · status · passesROOM-SERVICErooms · seeds · outboxREDIS 8frontier · leasesKAFKArooms · eventsYOUR SITEfoyer-guard · Ed25519INSIGHT-SERVICEfairness report
clientservicedataeventexternal

§4

Key decisions

  1. D/01

    A frontier counter, not a sorted set

    Position is arithmetic (place − served), so a status poll is a single Redis MGET whatever the crowd size, and the queue is never scanned or rewritten.

  2. D/02

    A permutation, not a shuffle

    At opening the lobby is shuffled with a Feistel network over HMAC-SHA256. Each place is computed on demand, so a 10-million-person lobby costs nothing extra, and the browser recomputes the same places as Java.

  3. D/03

    Commit-reveal seed

    The seed's SHA-256 is published before the lobby opens, so the organiser can't pick it after seeing who joined. A public page lets anyone recompute their own place.

  4. D/04

    Proof of work instead of CAPTCHA

    Joining costs about 65,000 hashes solved in a Web Worker: a fraction of a second on a phone, real CPU for a bot farm. Suspicious networks pay up to 64× more; challenges are single-use and signed.

  5. D/05

    Passes verified offline

    A Spring Boot filter or nginx auth_request checks the Ed25519 pass without calling Foyer, so the waiting room can never become the site's bottleneck or single point of failure.

  6. D/06

    Kafka off the visitor's path

    Room config arrives over a compacted topic; queue events are published asynchronously with a bounded retry buffer. With Kafka down, the sale doesn't notice.

§5

Challenges

  • One admission leader per room through a 4-second Redis lease. Killing the leader moved the lease to the other instance in 4 s and admission carried on.
  • Slow ticks under load lost admissions. The ticker now admits by elapsed time (capped at 15 s), reads inputs in parallel and commits its clock only after admitting: 1,254–1,259 admitted against a 1,200/min target.
  • At 200 joins/s plus 2,000 polls/s one instance handled about 2,100 req/s with 0 errors. Tails grew, so the manifests scale out with KEDA.
  • The lottery was tested as a bijection for every size from 1 to 2,000, with chi-square and Spearman tests for uniformity. A flaky test where a deliberately wrong proof of work sometimes passed by luck was fixed.

§6

Stack

  • Java 21
  • Spring Boot 4
  • WebFlux
  • Kafka
  • Redis 8 (Lua)
  • PostgreSQL
  • Ed25519
  • Angular
  • Web Workers
  • Kubernetes
  • KEDA

Next case study

FraudShield

Real-time UPI fraud and money-mule detection, with an AI copilot for investigators