From b93c20d1f659f3d51a758902faacf2100255547b Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 6 Oct 2026 10:42:44 +0000 Subject: [PATCH] docs: replace README with TICKET_README content Co-authored-by: Ribhu S --- README.md | 157 +++++++++++++++++++++++++----------------------------- 1 file changed, 72 insertions(+), 85 deletions(-) diff --git a/README.md b/README.md index a5a9efb..672277a 100644 --- a/README.md +++ b/README.md @@ -1,115 +1,102 @@ # Ticket Engine -A high-concurrency ticket booking backend built with Go, Redis, and MySQL. - -## Table of Contents -- [Why this project](#why-this-project) -- [Current architecture](#current-architecture) -- [Key capabilities](#key-capabilities) -- [API surface](#api-surface) -- [Quick start (Docker)](#quick-start-docker) -- [Local development](#local-development) -- [Configuration](#configuration) -- [Testing and verification](#testing-and-verification) -- [Load testing and reconciliation](#load-testing-and-reconciliation) -- [V2 roadmap and docs](#v2-roadmap-and-docs) - -## Why this project -Traditional databases struggle when very large numbers of users attempt to book at the same time. Ticket Engine moves allocation pressure to Redis, keeps booking allocation atomic, and persists booking state asynchronously to MySQL. - -## Current architecture -![Architecture](./architecture.png) +High-concurrency **ticket booking** backend in **Go**, with **Redis** for atomic allocation and **MySQL** as the system of record. -Flow (current implementation): -1. Client sends `POST /book` with `X-User-Id`. -2. Redis Lua allocation is used for atomic allocation + idempotent replay behavior. -3. Booking events are written to a Redis Stream and consumed by a worker. -4. Worker persists sold ticket ownership to MySQL. +## Overview -This design gives fast allocation while preserving eventual consistency in the system of record. +When many users hit `/book` at once, classic row-level booking patterns contend hard. Ticket Engine keeps inventory in a Redis list, allocates with a **Lua script** (atomic + per-user idempotent), **XADD**s booking events to a Redis Stream, and a consumer persists sold ownership to MySQL. -## Key capabilities -- **Atomic booking allocation** in Redis to prevent duplicate ticket assignment. -- **Idempotent user booking behavior** so repeated requests return consistent outcomes. -- **Asynchronous persistence** using Redis Streams + consumer processing. -- **Operational endpoints** for health, readiness, counters, and ticket inventory visibility. -- **Explicit inventory seeding** via command (`go run . seed`) instead of startup auto-minting. +## Architecture -## API surface -| Method | Endpoint | Purpose | -|---|---|---| -| `POST` | `/book` | Attempt booking for one user (`X-User-Id` required) | -| `GET` | `/tickets/count` | Remaining tickets in Redis queue | -| `GET` | `/metrics` | Request/success/failure counters | -| `GET` | `/healthz` | Liveness probe | -| `GET` | `/readyz` | Readiness probe (Redis + MySQL + consumer checks) | +![Architecture](./architecture.png) -Example booking request: +1. Client `POST /book` with `X-User-Id` (optional `X-API-Key` if configured) +2. Redis Lua: `HGET` prior booking → or `LPOP` + `HSET` + `XADD` +3. Stream consumer writes sold state to MySQL +4. Replay path re-enqueues persist if Redis says booked but MySQL lags + +## Tech stack + +| Area | Tech | +|------|------| +| API | Go 1.22, `net/http` | +| Allocation | Redis 7, embedded Lua (`internal/booking`) | +| Persist | Redis Streams consumer (`internal/persist`), MySQL 8 via GORM | +| Ops | Docker Compose (mysql, redis, seed, server), Makefile test targets | +| Load | k6 (`test.js`), legacy wrk Lua (`test.lua`) | +| UI | React + Vite dashboards under `frontend/` (Dashboard, Login, Queue Simulation) | + +## Key engineering decisions + +- **Redis owns hot allocation**; MySQL owns durable sold state (eventual consistency by design) +- **Idempotent user booking**: same `user_id` retries return the same `ticket_id` with HTTP 200 +- **Explicit seed**: `go run . seed` mints inventory (default 15,000); server start does not auto-mint +- **Race-aware metrics**: counters protected for concurrent `/metrics` under load +- **V2 phased roadmap** documented under `docs/` — phases 1–2 complete; later phases (full recovery/observability/security/CI bench gate) tracked in `docs/V2_PROGRESS.md` + +## Features + +- Atomic booking allocation (no double-assign of the same ticket id under concurrent clients — covered by unit/integration tests) +- Idempotent replay behavior +- Async MySQL persistence via stream consumer +- Health / readiness / inventory count / metrics endpoints +- Docker Compose local stack +- k6 staged load profile (ramp toward multi-thousand concurrent VUs — **results not yet recorded** in `docs/BENCHMARKS.md`) + +## API + +| Method | Path | Purpose | +|--------|------|---------| +| POST | `/book` | Book one ticket for `X-User-Id` | +| GET | `/tickets/count` | Remaining Redis queue length | +| GET | `/metrics` | Request / success / failure counters | +| GET | `/healthz` | Liveness | +| GET | `/readyz` | Readiness (Redis + MySQL + consumer checks) | ```bash -curl -X POST http://localhost:8080/book \ - -H "X-User-Id: 101" +curl -X POST http://localhost:8080/book -H "X-User-Id: 101" ``` -## Quick start (Docker) - -```bash -docker compose up -``` +## Performance / results -This starts: -- `mysql` (MySQL 8) -- `redis` (Redis 7) -- one-shot `seed` service (`go run . seed`) -- `server` service (`go run .`) +**No published throughput or latency numbers yet.** `docs/BENCHMARKS.md` is a measurement plan only — append runs with git SHA when executed. The repo includes k6 scripts and correctness checks (unique ticket↔user mapping, inventory conservation) to support that work. -Default API endpoint: `http://localhost:8080` +Do **not** treat the old GitHub description claim of “100k requests” as a verified result. -## Local development -If you already run MySQL and Redis locally: +## Getting started ```bash -go run . seed # run once on empty DB -go run . # start API server +docker compose up +# API: http://localhost:8080 ``` -Useful command: +Local (MySQL + Redis already running): ```bash -go run . reconcile +go run . seed +go run . +go run . reconcile # optional ``` -## Configuration -Environment variables used by runtime and compose setup: - -| Variable | Purpose | -|---|---| -| `HTTP_PORT` | HTTP server port (default `8080`) | -| `MYSQL_HOST` | MySQL host:port | -| `MYSQL_USER` | MySQL username | -| `MYSQL_PASSWORD` | MySQL password | -| `MYSQL_DATABASE` | MySQL database name | -| `REDIS_HOST` | Redis host:port | -| `TICKET_API_KEY` | Optional API key for protected booking access | -| `TICKET_CORS_ORIGIN` | Allowed CORS origin (default `*`) | - -## Testing and verification +## Testing ```bash make test make test-race -make test-integration # requires MySQL + Redis +make test-integration # needs MySQL + Redis ``` -## Load testing and reconciliation -- k6 script: [`test.js`](test.js) -- post-run SQL reconcile script: [`scripts/post-k6-reconcile.sql`](scripts/post-k6-reconcile.sql) -- legacy V1 script: [`test.lua`](test.lua) +## Project structure + +```text +internal/booking/ Lua allocator +internal/persist/ stream consumer +migrations/ SQL schema +docs/ V2 spec, progress, failure matrix, benchmark plan +frontend/ React/Vite UI experiments +test.js k6 load script +``` -## V2 roadmap and docs -- V2 target spec: [`docs/V2_SPEC.md`](docs/V2_SPEC.md) -- V2 phase progress: [`docs/V2_PROGRESS.md`](docs/V2_PROGRESS.md) -- failure/recovery matrix: [`docs/FAILURE_MATRIX.md`](docs/FAILURE_MATRIX.md) -- benchmark plan: [`docs/BENCHMARKS.md`](docs/BENCHMARKS.md) +## Future improvements -Current phase status is tracked in `docs/V2_PROGRESS.md`. +See `docs/V2_PROGRESS.md` for remaining phases: reconciliation rules, DLQ/backoff, richer observability, security hardening, CI benchmark gate, and recorded load results.