check_capacity(lot_id, vehicle_type, start_time, end_time): Returns available spot count and price for the requested window. Reads from Redis cache for fast response. If cache is stale or unavailable, falls back to database. The response includes both the count and the price so users can make an informed decision before committing. The response does not reveal which specific spots are available, only the count. This prevents users from racing to specific spots and simplifies the API.
reserve_spot(user_id, lot_id, vehicle_type, start_time, end_time): Finds an available spot, creates a TENTATIVE reservation, and returns the reservation_id plus a payment redirect URL. The spot is held for 15 minutes while the user completes payment. This is the most critical endpoint, it must be atomic, consistent, and handle concurrent access correctly. The response includes the assigned spot number (for the gate display), the payment URL, and the hold expiration time so the client can show a countdown.
complete_reservation(user_id, reservation_id, payment_token): Verifies the payment token with the third-party provider and updates the reservation status to CONFIRMED. If the token is invalid, the reservation stays TENTATIVE until the hold expires. This endpoint does not touch the bitmap, the bitmap was already updated during reserve_spot. It only changes the reservation status.
vehicle_arrived(reservation_id, timestamp): Records check-in time in the Transaction table. Called by the gate hardware when a vehicle enters. The gate validates the reservation exists and has status=CONFIRMED before opening the barrier.
vehicle_left(reservation_id, timestamp): Records check-out time. If the vehicle stayed past the reservation end time, flags the overstay for additional charges. The charge is calculated based on the lot's hourly rate for the overstay duration, rounded up to the nearest 15-minute slot.
API Gateway: Rate limiting, TLS termination, authentication, and request routing. Routes reservation-related requests to the Reservation Service and gate-related requests to the Transaction Service. Also protects against DDoS attacks that could prevent legitimate users from booking during high-demand events. Rate limiting is per-user for check_capacity (prevent scraping) and per-lot for reserve_spot (prevent inventory hoarding). The gateway also validates request parameters before forwarding, rejecting requests with end_time before start_time or reservation windows exceeding the maximum allowed duration (7 days).
Reservation Service: Handles check_capacity, reserve_spot, and complete_reservation. This is the only service that writes to the Reservation table and updates bitmaps. All writes happen inside PostgreSQL transactions. This single-writer design is intentional, having one service own all bitmap mutations eliminates cross-service coordination and makes concurrency reasoning straightforward.
Transaction Service: Handles vehicle_arrived and vehicle_left. Writes to the Transaction table only. It never modifies reservations or bitmaps. This boundary is enforced at the code level, the Transaction Service has a database connection with write access only to the Transaction table, not the Reservation or Spot tables.
Redis Cache: Stores lot capacity snapshots for fast check_capacity reads. This cache is advisory only, it tells users "roughly how many spots are available" but never authorizes a booking. The database always has the final say.
PostgreSQL: Source of truth for all reservation and transaction data. Sharded by lot_id for data locality. Each shard has a primary and at least one read replica for fault tolerance. The bitmap columns and reservation records for a lot always reside on the same shard, enabling single-shard transactions. Indexes on (lot_id, status, start_time) accelerate the common queries: finding active reservations for a lot, expired tentative holds, and no-show candidates.
Payment Provider: Third-party service (Stripe/PayPal) that handles payment processing. The Reservation Service verifies payment tokens with the provider during complete_reservation. The system stores only the transaction reference, not credit card details, PCI compliance is delegated entirely to the payment provider.
Background Sweepers: Two scheduled jobs. The hold expiration sweeper runs every minute, finding TENTATIVE reservations past their expires_at and releasing their bitmap bits. The no-show monitor runs every 15 minutes, finding CONFIRMED reservations with no check-in after 8 hours. Both clear bitmap bits and update reservation status inside transactions.
Deep dive into 2-3 key components. Explain how they work, how they scale, discuss tradeoffs, capacity, and any relevant algorithms or data structures.
Notice that steps 4-6 are the critical section, they happen inside a single database transaction with row-level locks. Steps 7-9 happen outside the transaction, so the lock is released quickly. This separation is why the bitmap check is fast even under contention: the lock is held only for the microseconds needed to check bitmap, insert, and commit. The payment (which takes seconds) happens after the lock is released.
The gate flow is deliberately simple, the complexity lives in the reservation flow where money and spot allocation happen. The gate is just recording physical events. This means gate hardware can be cheap, stateless devices running minimal firmware. If the Transaction Service is temporarily unreachable, the gate can cache events locally and sync later. A delayed check-in record does not cause a double-booking because the bitmap was already set during reservation.