Scale estimates: Various parking lots, 1M users, Daily bookings ~ 10K. 100 Requests per second to system including booking, payments, check in and check out.
Data Entities -
lotId
name
capacity
location
spotId
lotId
type
status
status:
AVAILABLE
RESERVED
OCCUPIED
bookingId
userId
carId
spotId
startTime
endTime
status
paymentId
status:
PENDING
RESERVED
CHECKED_IN
COMPLETED
NO_SHOW
CANCELLED
paymentId
bookingId
amount
status
POST /book - Book a parking spot
request - {
userId,
carId,
parkingSpotId,
fromTime,
toTime
}
response - HTTP 200
{
parkingSpotId
}
POST /check-in - Checks in the car for the booked spot
request - {
carId,
parkingId
}
POST /check-out - Check out the car for the booked spot
request - {
carId,
parkingId
}
Background job /handle-no-show
The system consists of three main components: Booking Service, Check-In/Check-Out Service, and a No-Show Worker.
All requests first pass through an API Gateway, which handles authentication and rate limiting, and are then routed through a Load Balancer to the appropriate service instance.
The Booking Service handles parking spot reservations. It validates spot availability, creates reservations, and processes payments through the external payment gateway. To prevent double booking, the service uses PostgreSQL transactions with row-level locking. If payment succeeds but booking confirmation fails, a compensating refund is issued.
The Check-In/Check-Out Service handles vehicle entry and exit. Parking lot devices such as cameras send the detected car and parking spot information to the service. The service validates that an active reservation exists for the vehicle and updates the booking and parking spot status accordingly.
The No-Show Worker runs periodically and releases reservations when users fail to check in within a configurable grace period, making the spot available for other users.
PostgreSQL serves as the source of truth and stores users, vehicles, parking spots, bookings, and payment references. It is chosen because reservation systems require strong consistency and ACID transactions.
Redis is used to cache parking lot metadata and spot availability information to reduce database load and improve response times. We use make sure the cache is short lived so the parking lot availabilty data is refershed. Here, even if user sees stale data for parking lot available spot or info for some time, its not a big issue compared to the benefits caching provides us for improving performace. Reservation ownership and payment information are always read from PostgreSQL to maintain consistency.
The system is horizontally scalable by running multiple service instances behind a load balancer, while caching and rate limiting help the system handle peak traffic efficiently.
Two topics we will deep dive into now are:
1) Ensuring no double booking or overlap booking
Select * From ParkingSpot
Where Id = 56
AND Status = 'AVAILABLE'
For UPDATE;
hence, using row-lock locks the record when the transaction begins for booking and only releases once the entire transaciton is committed and completed. An optional optimzation could be made on UI side where once any user starts booking process from frontend that particualr spot is marked as unavailable from the UI side for X minutes, so no other user can request another booking while first user is still completing their process.
2) Payment consistency and failure handling
3) Hot lot Contention- What happens when very popular lot gets sudden spike - for eg. 10k users tryting to reserve airport parking for the same lot at 9AM