Eventora
Event ticketing platform: one Spring Boot deployable (modular monolith) and a Next.js frontend.
Source (opens an external site)Frontend (opens an external site)Project site (opens an external site)

Problem
Many buyers reach for the same seats at the same moment. The booking path has to refuse oversell, absorb retried payments, and keep Redis and PostgreSQL in agreement.
Source · Event-Ticketing-Platform/README.md:7,159-164 · as ofBooking lifecycle governed by a 10-state Spring State Machine.
Source · Event-Ticketing-Platform/src/main/java/com/ticketing/booking/model/BookingState.java · as ofThe read path is CPU-bound, not database-bound: at the 2-replica ceiling both replicas ran at 105% of their 1-CPU budget while PostgreSQL, Redis and the 5-connection pool kept headroom (local, Docker Compose).
Source · Event-Ticketing-Platform/PERFORMANCE.md:425-434; README.md:348 · as ofCI on GitHub Actions: 3 jobs (backend verify with JaCoCo, frontend Vitest and build, repo hygiene), green on 2026-09-07.
Source · Event-Ticketing-Platform/.github/workflows/main.yml:13-70; gh run list · as of
Architecture
Scroll sideways for the full diagram.
- 01Client → BookingServicePOST /api/v1/bookings
- 02BookingService → Redisacquire per-user distributed lock
- 03BookingService → Redisre-check availability (TOCTOU guard)
- 04BookingService → RedisreserveSeat(): atomic Lua floor guard
- 05Redis → BookingServicedecremented, success
- 06BookingService → PostgreSQLatomic conditional UPDATE (availableCount -= n)
- 07BookingService → PostgreSQLINSERT Booking (state=RESERVED, expires=+5m)
- 08BookingService → Client201 Created
- 09BookingService → Redisrelease lock
- Client
- POST /api/v1/bookings
- BookingService
- Spring Boot API
- Redis
- lock, availability, Lua floor guard
- PostgreSQL
- conditional UPDATE, booking row
Key decisions
- 01
A Redis Lua script checks availability and decrements in one atomic step. A test fires 100 concurrent threads at a 50-seat tier: exactly 50 succeed.
Source · Event-Ticketing-Platform/README.md:159 · as of - 02
PostgreSQL inventory is written with one conditional UPDATE ... WHERE available_count >= :qty instead of read-modify-write.
Source · Event-Ticketing-Platform/README.md:160 · as of - 03
Optimistic locking with @Version on Booking, TicketTier and Event; a lost update returns 409, not 500.
Source · Event-Ticketing-Platform/README.md:162 · as of - 04
Stripe webhooks are deduplicated by a database UNIQUE constraint; booking creation requires an Idempotency-Key header.
Source · Event-Ticketing-Platform/README.md:164,395 · as of - 05
QR codes and emails go through RabbitMQ with dead-letter queues, off the request thread.
Source · Event-Ticketing-Platform/README.md:52,170 · as of - 06
JWT with a jti claim and a Redis denylist revokes tokens on logout; @PreAuthorize role checks gate 18 of 30 endpoints.
Source · Event-Ticketing-Platform/README.md:392,396 · as of
Evidence
- Commits2026-03-21 to 2026-09-07
- 415
- Source · evidence-report.md:29 · as of
- JaCoCo instruction coveragegated packages, gate 80%
- 84.1%
- Source · Event-Ticketing-Platform/README.md:254 · as of
- Booking states
- 10
- Source · Event-Ticketing-Platform/src/main/java/com/ticketing/booking/model/BookingState.java · as of
- Endpoints31 mapping annotations in code
- 30+
- Source · Event-Ticketing-Platform/README.md:55; evidence-report.md:122 · as of
- Peak virtual usersRailway, ramp 10 to 200 VUs over 16 min, read path
- 200VUs
- Source · Event-Ticketing-Platform/PERFORMANCE.md:263-298 · as of
- Failed requestsRailway ramp, read path
- 0.00%
- Source · Event-Ticketing-Platform/PERFORMANCE.md:285 · as of
- p95 latencyRailway ramp, about 394 ms
- 394ms
- Source · Event-Ticketing-Platform/PERFORMANCE.md:288-291 · as of
- Requests in five capacity runs, 0 failedlocal, Docker Compose; 0 server errors
- 569,066
- Source · Event-Ticketing-Platform/PERFORMANCE.md:381-383,400-401; README.md:349 · as of
- Read ceiling, one 1-CPU replicalocal, Docker Compose; read path, 0 errors
- 660req/s
- Source · Event-Ticketing-Platform/PERFORMANCE.md:382,411 · as of
- Median latency at that ceilinglocal, Docker Compose; p95 511 ms
- 2.40ms
- Source · Event-Ticketing-Platform/PERFORMANCE.md:385-386 · as of
- p95 at ceiling, 1 replicalocal, Docker Compose
- 511ms
- Source · Event-Ticketing-Platform/PERFORMANCE.md:382,386 · as of
- Read ceiling, 2 replicaslocal, Docker Compose; 0 errors
- 870req/s
- Source · Event-Ticketing-Platform/PERFORMANCE.md:401 · as of
- Scaling factor, 1 to 2 replicaslocal, Docker Compose; sub-linear
- 1.32×
- Source · Event-Ticketing-Platform/PERFORMANCE.md:414 · as of
- Sustained rate, 2 replicas behind nginxlocal, Docker Compose; p95 9.0 ms
- 800req/s
- Source · Event-Ticketing-Platform/PERFORMANCE.md:400 · as of
- p95 at 800 req/s, 2 replicaslocal, Docker Compose
- 9.0ms
- Source · Event-Ticketing-Platform/PERFORMANCE.md:400,416 · as of
- Oversold seats, 100-VU inventory burstlocal, Docker Compose; 8-seat tier
- 0
- Source · Event-Ticketing-Platform/PERFORMANCE.md:321 · as of
- Booking creation p95 under contentionlocal, Docker Compose; 20 VUs, 0 server errors
- 55.4ms
- Source · Event-Ticketing-Platform/README.md:356; PERFORMANCE.md:320 · as of
| Configuration | throughput, req/s | p95 latency, ms | Note |
|---|---|---|---|
| 1 replica | 660 | 511 | its ceiling: p95 crossed 500 ms here |
| 2 replicas, nginx | 800 | 9 | held every stage without abort |
One replica's point is its ceiling; two replicas' point is a rate they held, not their ceiling (870 req/s, p95 568 ms).
Source · Event-Ticketing-Platform/PERFORMANCE.md:382,386,400-401,411-412,416 · as of| minutes | target virtual users |
|---|---|
| 0 | 0 |
| 2 | 10 |
| 5 | 25 |
| 8 | 50 |
| 11 | 100 |
| 14 | 200 |
| 16 | 0 |
- requests
- 32,577
- failed
- 0.00%
- p95
- 394 ms
The line is the k6 stage schedule (configured targets), not measured VUs. Results are whole-run aggregates.
Source · Event-Ticketing-Platform/src/test/k6/capacity-ramp.js@d103b56:16-23; PERFORMANCE.md:271,283-291 · as ofScreenshots
Stack
- Java 21
- Spring Boot 3.5.13
- Spring State Machine 4.0.0
- PostgreSQL 17
- Redis 7
- RabbitMQ 4
- Flyway
- Stripe
- Testcontainers
- k6
- Nginx 1.27
- Next.js 16.2.4
- React 19.2.4
- Tailwind CSS 4
- Vitest 3.2.4