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:
- Check slot availability in authoritative DB.
- Acquire distributed lock (Redis / DB row lock) on slot.
- Create reservation with status =
PENDING_PAYMENT. - 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:
slotsreservationspayments
- Tables:
- 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:
- Reservation created (
PENDING_PAYMENT) - Backend creates payment intent via Stripe
- Client completes payment
- 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:
- Row-level locking (
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 availability | Redis (TTL 2 min) |
| Fully booked optimization | Redis 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:
- Validate request (slot_id, time_range, user_id)
- Check idempotency key (prevent duplicate requests)
- Start DB transaction
- Acquire lock:
- Option A: Redis distributed lock
- Option B: DB row-level lock (
SELECT ... FOR UPDATE)
- Check availability (authoritative DB)
- Insert reservation:
status = PENDING_PAYMENT
expires_at = now + 2 minutes
- Commit transaction
- 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 locking | Strong consistency | Higher latency, contention |
| Redis-only locking | Fast | Risk of inconsistency |
| Hybrid | Balanced | Slight 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:
- Verify signature (security)
- Lookup reservation_id
- Start DB transaction
- Update:
reservation.status = CONFIRMED
- 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-driven | Eventually consistent |
| Synchronous verification | Higher latency |
| Queue-based processing | Adds 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 caching | Fast | Stale data risk |
| No caching | Accurate | High DB load |
| Short TTL + invalidation | Balanced | Slight complexity |