Skip to content

PoC Ticket System with Concurrency & Race Conditions

Notifications You must be signed in to change notification settings

F9Uf/poc-ticket-system

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Ticket System: Concurrency & Race Conditions PoC

A comprehensive proof-of-concept demonstrating different approaches to handling concurrency and race conditions in a ticketing system. This project explores various locking strategies to prevent double-booking scenarios when multiple users attempt to reserve the same seat simultaneously.

Based on: Building a Ticketing System: Concurrency, Locks, and Race Conditions

Overview

When thousands of users try to book the same concert ticket at exactly 10:00 AM, race conditions become a critical problem. This system demonstrates four different strategies to handle concurrent seat reservations:

  • Naive Approach: Shows the problem with race conditions
  • Pessimistic Locking: Uses database row-level locking with SELECT FOR UPDATE
  • Optimistic Locking: Implements version-based conflict detection
  • Redis Distributed Locking: Uses Redis for distributed coordination

Features

  • 🎯 Multiple Concurrency Strategies: Compare different approaches side-by-side
  • Load Testing: Built-in stress testing with 200 concurrent requests
  • 🐳 Containerized Setup: PostgreSQL + Redis Cluster with Docker Compose
  • 🔄 Race Condition Simulation: Artificial delays to trigger concurrent scenarios
  • 📊 Performance Metrics: Success/failure rates and response time analysis

Quick Start

Prerequisites

Setup

  1. Clone and install dependencies

    git clone <repository-url>
    cd poc-ticket-system
    bun install
  2. Start the infrastructure

    docker compose up -d
  3. Run the application

    bun run dev
  4. Execute load tests

    bun run test

Architecture

Database Schema

-- Events table
CREATE TABLE events (
  id          SERIAL PRIMARY KEY,
  name        TEXT NOT NULL
);

-- Seats with optimistic locking version
CREATE TABLE seats (
  id          SERIAL PRIMARY KEY,
  event_id    INT NOT NULL REFERENCES events(id),
  seat_number TEXT NOT NULL,
  is_booked   BOOLEAN NOT NULL DEFAULT false,
  version     INT NOT NULL DEFAULT 0  -- For optimistic locking
);

-- Booking records
CREATE TABLE bookings (
  id          SERIAL PRIMARY KEY,
  event_id    INT NOT NULL REFERENCES events(id),
  seat_id     INT NOT NULL REFERENCES seats(id),
  user_id     TEXT NOT NULL,
  created_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);

Concurrency Strategies

1. Naive Strategy ⚠️

Problem: Multiple requests can read the same seat as "available" simultaneously, leading to double bookings.

// Race condition: Both requests see seat as available
const seat = await client.query('SELECT is_booked FROM seats WHERE id = $1');
if (!seat.rows[0].is_booked) {
  // Delay allows race condition to occur
  await delay(randomMs);
  await client.query('UPDATE seats SET is_booked = true WHERE id = $1');
}

2. Pessimistic Strategy 🔒

Solution: Lock the row immediately to prevent concurrent access.

// Lock the seat row - only one transaction can proceed
const seat = await client.query(
  'SELECT id, is_booked FROM seats WHERE id = $1 FOR UPDATE'
);

Pros: Guarantees no race conditions Cons: Lower concurrency, potential deadlocks

3. Optimistic Strategy ✨

Solution: Use version numbers to detect concurrent modifications.

// Read current version
const seat = await client.query('SELECT version FROM seats WHERE id = $1');
const currentVersion = seat.rows[0].version;

// Update only if version hasn't changed
const result = await client.query(
  'UPDATE seats SET is_booked = true, version = version + 1 
   WHERE id = $1 AND version = $2',
  [seatId, currentVersion]
);

if (result.rowCount === 0) {
  throw new ConcurrentModificationException();
}

Pros: Better performance, no deadlocks Cons: Retry logic needed for conflicts

4. Redis Distributed Strategy 🚀

Solution: Use Redis for distributed locking across multiple service instances.

// Acquire distributed lock
const lockKey = `lock:booking:${seatNumber}`;
const acquired = await redisCluster.set(lockKey, uuid, { 
  NX: true,  // Only set if not exists
  PX: 500    // 500ms TTL
});

if (!acquired) {
  throw new SeatNotAvailableException();
}

Pros: Works across multiple servers, fault-tolerant Cons: Network dependency, complexity

API Reference

Book a Seat

POST /book?strategy={strategy_name}
Content-Type: application/json

{
  "eventId": "1",
  "seatNumber": "A1",
  "userId": "user_123"
}

Strategies: naive, pessimistic, optimistic, redis

Response:

{
  "status": true
}

Reset Bookings

POST /book/reset
Content-Type: application/json

{
  "eventId": "1",
  "seatNumber": "A1"
}

Load Testing Results

The built-in load test simulates 200 concurrent users trying to book the same seat:

bun run test

Example Output:

{
  strategy: "naive",
  durationMs: 54,
  successCount: 10,       # ❌ Multiple bookings (race condition)
  failCount: 190,
  reasons: {
    HTTP_200: 10,
    "Seat already booked": 190,
  },
}

{
  strategy: "pessimistic",
  durationMs: 54,
  successCount: 1,        # ✅ Exactly one booking
  failCount: 199,
  reasons: {
    HTTP_200: 1,
    "Seat already booked": 199,
  },
}

{
  strategy: "optimistic",
  durationMs: 50,
  successCount: 1,        # ✅ Exactly one booking
  failCount: 199,
  reasons: {
    "Concurrent modification detected": 9,
    HTTP_200: 1,
    "Seat already booked": 190,
  },
}

{
  strategy: "redis",
  durationMs: 25,
  successCount: 1,        # ✅ Exactly one booking + fastest!
  failCount: 199,
  reasons: {
    HTTP_200: 1,
    "Seat not available due to could not acquire lock": 199,
  },
}

Key Learnings

  1. Race Conditions: Even simple operations can have complex concurrency issues
  2. Lock Granularity: Row-level vs table-level vs distributed locks
  3. CAP Theorem: Trade-offs between consistency, availability, and partition tolerance
  4. Performance vs Correctness: Different strategies for different requirements

About

PoC Ticket System with Concurrency & Race Conditions

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published