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
§4
Key decisions
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.
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.
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.
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.
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.
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