Requirements


Functional Requirements:


  • Allow reservation of a parking spot.
  • Process payment for the reservation.
  • Enable parking of a car in the reserved spot.
  • Support early departure before reservation time expires.
  • Gate check-in/out.
  • Handle no show.



Non-Functional Requirements:


  • Strong Consistency (To avoid double booking)
  • High availability (So there is no server crash at peak hours)
  • High scalability (SO thousands or lac of people can book)


API Design

/available :Checks for all the available parking route for that time including if someone has early checkout


/Reserve:Proceed to book the slot includes one more route

/payment: With intent of successs and failure


/show-reservation:For user to check his current reservation And his previous reservation


/check-in: When user arrives and checks in

/expiry:when the time slot get expires

/check-out:If user does early checkout before expiry



1. User Journey with Clear Service Handoffs

Step 1: Search Available Slots

Flow:

User → API Gateway → Slot Service → Cache (Redis) → DB (if cache miss)

Details:

  • User searches for parking slots.
  • Slot Service first checks Redis cache (TTL ~2 min).
  • If cache miss → fetch from DB (indexed on location + time).
  • If all slots are full:
    • Cache a “fully booked” flag (TTL ~10–15 min) to avoid repeated DB hits.
  • Early checkout updates availability asynchronously (event-driven).

Step 2: Select Slot & Reserve (Critical Path)

Flow:

User → API Gateway → Reservation Service → DB (authoritative) → Lock

Details:

  • No caching here (must be strongly consistent).
  • Apply:
    • Rate limiting (5–10 req/sec/user) at API Gateway.
    • Idempotency key to avoid duplicate requests.

Reservation Flow:

  1. Check slot availability in authoritative DB.
  2. Acquire distributed lock (Redis / DB row lock) on slot.
  3. Create reservation with status = PENDING_PAYMENT.
  4. Set TTL (e.g., 1–2 minutes) for lock expiry.

2. Authoritative Data Store (Source of Truth)

  • Primary DB (PostgreSQL / MySQL) = Source of truth
    • Tables:
      • slots
      • reservations
      • payments
  • Redis = Advisory cache only (never final authority)

Important principle:

All final booking decisions happen via DB transactions, NOT cache

3. Payment Flow (Clear Handoff with Stripe)

Flow:

Reservation Service → Payment Service → Stripe → Webhook → Reservation Service

Detailed Steps:

  1. Reservation created (PENDING_PAYMENT)
  2. Backend creates payment intent via Stripe
  3. Client completes payment
  4. Stripe sends webhook → Payment Service

Payment Outcomes:

Success:

  • Verify webhook signature (security)
  • Update:
    • reservation.status = CONFIRMED
    • release lock
  • Emit event → notify user

Failure:

  • Reservation remains PENDING
  • User gets retry window (~1 min)
  • If timeout:
    • reservation.status = EXPIRED
    • lock released automatically

4. Preventing Double Booking (Concurrency Handling)

Mechanisms:

1. DB-Level Guarantee (Authoritative)

  • Use:
    • Row-level locking (SELECT FOR UPDATE)
    • OR unique constraint:
UNIQUE(slot_id, time_range)

2. Distributed Lock (Fast Layer)

  • Redis lock with TTL (1–2 min)

3. Idempotency Keys

  • Prevent duplicate reservation/payment requests

4. Final Commit Rule

Only DB commit decides booking success — cache is advisory

5. Handling Edge Cases (Important)

Simultaneous Booking Requests

  • First request acquires lock
  • Others fail fast or retry
  • DB constraint ensures no duplicates

Payment Failure / Network Issues

  • Webhook-driven final state (not client response)
  • Retry mechanism for webhook failures

Lock Expiry

  • If user abandons:
    • Lock auto-expires
    • Slot becomes available again

Early Checkout

Flow:

Checkout Service → DB update → Event → Cache invalidation

  • Slot marked available immediately
  • Cache refreshed asynchronously

6. Cache Strategy (Advisory Only)

Use CaseStrategy
Slot availabilityRedis (TTL 2 min)
Fully booked optimizationRedis flag (TTL 10–15 min)
Reservation❌ No cache (DB only)

Cache is never trusted for final booking

7. Real-Time Updates (Polling / Push)

  • Use short polling / WebSockets
  • When:
    • Lock expires
    • Slot becomes free
  • Ensures UI stays fresh

8. Services Overview

Core Services:

  • API Gateway
  • Slot Service
  • Reservation Service
  • Payment Service
  • Checkout Service

9. Scalability Considerations

  • Horizontal scaling (stateless services)
  • Redis for fast reads
  • DB indexing:
    • location + time
  • Read replicas for heavy traffic
  • Queue (Kafka/SQS) for async updates

10. Final Booking Lifecycle

AVAILABLE LOCKED (temporary) PENDING_PAYMENT ↓ ↓ CONFIRMED EXPIRED





Detailed Component Design


1. Reservation Service (Core Consistency Layer)

Responsibilities

  • Create reservations
  • Enforce “no double booking”
  • Manage slot locking lifecycle
  • Coordinate with Payment Service

Internal Flow

Request: Reserve Slot

Client → API Gateway → Reservation Service

Steps:

  1. Validate request (slot_id, time_range, user_id)
  2. Check idempotency key (prevent duplicate requests)
  3. Start DB transaction
  4. Acquire lock:
    • Option A: Redis distributed lock
    • Option B: DB row-level lock (SELECT ... FOR UPDATE)
  5. Check availability (authoritative DB)
  6. Insert reservation:
status = PENDING_PAYMENT expires_at = now + 2 minutes
  1. Commit transaction
  2. Return reservation_id + payment intent

Data Model (Simplified)

reservations

id (PK) slot_id (FK) user_id start_time end_time status (PENDING_PAYMENT, CONFIRMED, EXPIRED) expires_at created_at

Constraint (Critical):

UNIQUE(slot_id, start_time, end_time)

or more realistically:

  • Use exclusion constraints (PostgreSQL) for overlapping intervals

Concurrency Control

Approach 1: DB-First (Strong Consistency)

  • Use:
SELECT * FROM slots WHERE id = ? FOR UPDATE
  • Guarantees serialization
  • Prevents race conditions

Approach 2: Hybrid (Recommended)

  • Redis lock (fast fail)
  • DB constraint (final guarantee)

Why both?

  • Redis reduces contention
  • DB ensures correctness

Expiry Handling

  • Background worker scans:
WHERE status = PENDING_PAYMENT AND expires_at < now()
  • Marks as EXPIRED
  • Releases slot

Alternative:

  • Use delayed queue (e.g., Kafka/SQS delay)

Scaling Strategy

Horizontal Scaling

  • Stateless service
  • Multiple instances behind load balancer

DB Scaling

  • Read replicas for availability queries
  • Writes always go to primary

Partitioning (if needed)

  • Partition reservations by:
    • location_id
    • or time (monthly tables)

Capacity Estimation (Example)

Assume:

  • 1M users/day
  • 5% reserve → 50K reservations/day
  • Peak: 2K requests/sec

DB Writes:

  • ~2K TPS (manageable with proper indexing + sharding)

Locks:

  • Redis handles high concurrency (~100K ops/sec easily)

Tradeoffs

ApproachProsCons
DB-only lockingStrong consistencyHigher latency, contention
Redis-only lockingFastRisk of inconsistency
HybridBalancedSlight complexity

2. Payment Service (Third-Party Integration Layer)

Responsibilities

  • Initiate payment
  • Verify payment via webhook
  • Update reservation state

Flow

Step 1: Create Payment

Reservation Service → Payment Service → Stripe
  • Create payment intent
  • Store mapping:
payment_id → reservation_id

Step 2: Payment Completion

Stripe → Webhook → Payment Service → Reservation Service

Webhook Handling:

  1. Verify signature (security)
  2. Lookup reservation_id
  3. Start DB transaction
  4. Update:
reservation.status = CONFIRMED
  1. Commit

Failure Handling

Case 1: Payment Failed

  • Keep reservation as PENDING_PAYMENT
  • Allow retry until expiry

Case 2: Webhook Delay

  • Reservation remains pending
  • Background reconciliation job:
    • Poll Stripe API
    • Fix inconsistent states

Idempotency

  • Use:
    • Stripe idempotency keys
    • Internal idempotency table

Reason:

  • Webhooks can be retried multiple times

Scaling

  • Webhook consumers horizontally scalable
  • Use message queue:
Webhook → Queue → Worker

Capacity

Assume:

  • 50K payments/day
  • Peak: 1K/sec webhooks

Solution:

  • Queue buffering
  • Async workers

Tradeoffs

Design ChoiceTradeoff
Webhook-drivenEventually consistent
Synchronous verificationHigher latency
Queue-based processingAdds complexity but improves resilience

3. Slot Availability Service (Read-Optimized Layer)

Responsibilities

  • Serve available slots quickly
  • Handle high read traffic
  • Reflect near-real-time availability

Read Flow

Client → API Gateway → Slot Service → Redis → DB (fallback)

Cache Design

Key Structure

slots:{location_id}:{time_bucket}

Value

  • List of available slot_ids
  • Metadata (price, type)

TTL

  • 1–2 minutes

Cache Update Strategy

Write Path (Important)

  • On reservation:
    • Do NOT trust cache
    • Update DB first
    • Invalidate cache

Early Checkout

Checkout → DB update → Event → Cache invalidation

Avoiding Cache Stampede

  • Use:
    • Request coalescing
    • Mutex lock per key
    • Stale-while-revalidate

Data Structures

In Memory (Redis)

  • Sorted Sets:
ZSET: slots by price or distance
  • Hash:
slot_id → metadata

Scaling

Read Scaling

  • Redis cluster (sharded)
  • CDN (if applicable)

DB Optimization

  • Index:
(location_id, start_time, end_time)

Capacity

Assume:

  • 1M searches/day
  • Peak: 10K QPS

Redis:

  • Handles easily (<1 ms latency)

DB fallback:

  • ~5–10% traffic only

Tradeoffs

ApproachProsCons
Aggressive cachingFastStale data risk
No cachingAccurateHigh DB load
Short TTL + invalidationBalancedSlight complexity