2026WebFeatured

ApexTick - Real-Time Ticket Reservation Platform

A distributed, real-time ticket reservation platform engineered to survive a high-contention flash sale - thousands of people rushing the same seats in seconds, where exactly one buyer must win each seat with zero double-booking. Built as a right-sized Spring Boot microservices system with Keycloak identity, an event-driven backbone, an API gateway, and load-tested proof of correctness.

ApexTick - Real-Time Ticket Reservation Platform - Web project by Kalana Sandakelum

ApexTick is a distributed, real-time ticket reservation platform built around the single hardest moment in ticketing: the flash sale. The instant a major concert or cricket final goes on sale, thousands of fans tap "book seat A12" in the same second, and only one can win it. If the system gets this wrong, two people walk away believing they own the same seat. I built ApexTick to get that exactly right, at speed, under load, while still being a complete product where people sign up, browse events, pay, and walk into the venue with a scannable ticket.

Every decision in the project serves one hard problem: when thousands of users try to book the same seat at the same moment, exactly one must succeed, the same seat must never be sold twice, and the system must stay fast and stay up. Concurrency control, horizontal scaling, asynchronous processing, caching, and edge rate limiting all exist to serve that one sentence. That is what makes it an enterprise-grade engineering project rather than another CRUD application.

The technical heart is how it defeats the check-then-act race condition. With naive code, two requests for the same seat can both read "available" and both write "booked." Because the booking service runs as multiple stateless instances behind a load balancer, an in-JVM lock protects nothing - two instances would each acquire their own lock and double-book. So correctness lives in the one place every instance shares: the database. A single atomic conditional update flips a seat from AVAILABLE to HELD only where it is still available, and PostgreSQL serializes every concurrent request against that one row. The first to commit affects one row and wins; everyone else affects zero rows and gets a clean rejection. There are no application mutexes and no distributed locks - the database row is the synchronization point, so the guarantee holds no matter how many copies of the service are running.

Real ticketing does not book instantly, it holds a seat while the user pays. On selection a seat moves atomically to HELD with a Redis key carrying a time-to-live; on successful payment it becomes BOOKED, and if the user abandons checkout or payment fails, a Redis key-expiry event releases the seat back to AVAILABLE with no polling loop on the happy path. This hold-then-confirm flow mirrors how real platforms behave and guarantees seats are never locked forever.

Around that core sits a layered defense for extreme load. The booking service is fully stateless so it can scale horizontally during a drop. Non-critical work - confirmation emails, QR ticket PDFs, analytics - is offloaded to RabbitMQ and handled by a separate notification service, so the booking transaction does the minimum and returns fast. To keep that messaging reliable I used the Transactional Outbox pattern: each event is written into an outbox table in the same transaction as the order, and a relay publishes it, so an event is sent if and only if the booking actually committed - eliminating the "booked but never notified" failure. A small HikariCP connection pool serves thousands of requests per second because each transaction is short and recycles fast, availability reads are cached in Redis to keep browse traffic off the database, and a WSO2 API Manager gateway applies rate limiting, throttling, and spike arrest at the edge, with an optional virtual waiting room that queues users in fair batches for the largest drops.

I treated the concurrency claim as something to prove, not assert. A k6 load test fires 5,000 hold attempts from 200 concurrent virtual users at an event with exactly 200 seats. The result: 200 of 200 seats sold, each exactly once, 4,800 losing requests cleanly rejected, zero double-bookings, roughly 3,900 requests per second of throughput, a p95 latency of 146 ms, and a 0.00 percent failure rate. That turns "I think it handles load" into "I measured it handling load," and the same test runs headlessly in CI on every push so the core guarantee is re-proven automatically.

The architecture is deliberately right-sized rather than maximally split. There are two business services: booking-inventory-order is kept together because a booking is only valid if a seat is claimed and an order written in one atomic transaction, and the notification service is kept separate because generating PDFs and sending email is naturally independent and asynchronous. Splitting the booking core would have forced a distributed transaction or a Saga that was not needed - knowing when not to over-engineer was as important as the engineering itself.

Identity is never hand-rolled. Keycloak centralizes registration, email verification, password reset, and social login, with Google wired in as an identity provider so the rest of the system stays unaware of it. The browser uses Authorization Code with PKCE, and the backend acts as an OAuth2 resource server that validates Keycloak's signed JWTs on every request without any shared session store. Realm roles - customer, organizer, and admin - travel inside the token and are enforced with method-level Spring Security guards, cleanly separating buyer endpoints, organizer dashboards, and admin tools.

It is a full product across three audiences. Customers register or sign in with Google, discover events with search and filtering, pick seats on an interactive live seat map with pricing tiers and a hold timer, pay through an external gateway (Stripe internationally, PayHere and Onepay for Sri Lanka) so card data never touches my servers, and receive QR-coded PDF tickets by email and in their account. Payment is finalized by an idempotent gateway webhook rather than the browser redirect, so a closed tab or a retried webhook never corrupts an order. Organizers create and schedule events, build seat maps and pricing tiers, and watch revenue and sell-through on a sales dashboard. Admins manage users and refunds and, at the gate, scan each ticket's QR to validate it and reject duplicate scans.

The stack is a modern enterprise one: Java 21 and Spring Boot for the services, Spring Data JPA and Spring Security, PostgreSQL as the source of truth, Liquibase for versioned schema migrations, Redis for holds and caching, RabbitMQ for event-driven messaging with retries and dead-letter queues, Keycloak for identity, WSO2 API Manager at the edge, and a Next.js frontend using TanStack Query and Axios with auth interceptors. Quality rests on a testing pyramid: JUnit and Mockito for unit tests, Testcontainers for integration tests that run against real PostgreSQL, RabbitMQ, and Redis in Docker rather than mocks, and k6 for load and correctness verification.

The whole system is reproducible and shippable. It runs locally with a single docker compose up, and in the cloud it is provisioned entirely through Terraform as version-controlled infrastructure that can be created or torn down with one command to control cost. For demos the full stack runs on a single EC2 instance behind a Caddy reverse proxy that terminates TLS and serves everything from one secure HTTPS origin, which is also what lets browser OIDC and PKCE login work. At production scale each piece moves onto a managed service - RDS, ElastiCache, Amazon MQ, S3, and ECS or Fargate behind a load balancer - and only the part under pressure scales out, because correctness is enforced in the database rather than in any instance's memory.

What I am most proud of is that ApexTick is understood end to end and defensible line by line. It solves a genuinely hard distributed-systems problem, backs the solution with measured evidence, demonstrates real architectural judgment about where to split services and where not to, and covers the full lifecycle of building and shipping a real system rather than assembling a demo from tutorials.

Built with

Java 21Spring BootPostgreSQLRedisRabbitMQKeycloakWSO2 API ManagerNext.jsDockerTerraformAWSk6TestcontainersGitHub Actions

More work

All projects