My Solution for Design an Efficient Parking Lot System with Score: 9/10
by quantum_vortex687
System requirements
MVP:
- User should be see a selection of car parking spaces in an area of their choosing.
- User should be able to reserve a parking space from a list of locations that the company supports.
Functional Requirements:
- Manage Vehicle Entry/Exit:
- Upon arrival, scan or enter reservation details to grant access and allocate the correct parking space.
- On exit, ensure the user has completed the payment process, then allow the vehicle to leave the parking lot.
- List Available Parking:
- Provide real-time information about available parking spaces in a chosen area.
- Display details such as pricing, vehicle type compatibility, and availability duration.
- User Reservations:
- Allow users to choose and reserve a parking space for a specific duration.
- Provide confirmation of the reservation with a unique reservation ID.
- Payment for Reservations:
- Facilitate secure and convenient payment options (credit card, online payment gateways) for users to pay for their reservations. It is assumed a 3rd party provider like Stripe will handle Payments.
- Early Exit Handling:
- Allow users the flexibility to exit before the reservation deadline.
- Assess and apply any adjusted charges based on early exit policies.
- Analytic Data Support
- Track user behaviour for booking behaviour and parking lots used.
Non-Functional:
- Scalability:
- System should be able to scale horizontally to accommodate increasing user requests and parking lots across various locations. If there's an event in a parking area the system supports like a sporting event, the system will need to be flexible to scale to meet these requests.
- Availability:
- Ensure system is highly available.
- Double-Booking Prevention:
- Ensure parking space cannot be double booked.
Capacity estimation
Questions: Capacity Estimation how many users are we accommodating?
- Geographical Footprint:
- Assume operations across 10 countries.
- Approximately 100 parking lots in each country.
- 10 * 100 = 1000 Total Parking Lots Worldwide.
- Parking Lot Capacity:
- Each parking lot can, on average, handle 200 vehicles.
- 1000 Total Parking Lots * 200 Vehicles = 200,000 Total Spaces
- Daily Reservation Requests:
- Each parking lot receives around 200 reservation requests daily matching 200 vehicles.
- Max of 200,000 reservations a day and Daily Active Users.
- User Types:
- 80% short-term users staying for about 4 hours.
- 20% long-term users staying for about 5 days.
- Storage
- Assume each reservation has the following dataset: UserID, ParkingLotID, CountryID, ReservationStart, ReservationEnd, Vehicle Info, Payment Data, Metadata.
- Approx size = 1KB per reservation record (estimate)
- Daily Ingest Data
- 200,000 reservations / day x 1 KB = 200 MB / Day
- Annual Storage:
- 200 MB x 365 = 73 GB year
- Add any overhead for logs, backups, redundancy (x3)
- Total = 220 GB / year
A Booking System requires strong consistency and ACID (Atomicity, Consistency, Isolation, Durability) guarantees to prevent double-booking or race conditions. Built-in constraints help enforce data integrity, ideal for conflict resolution (double bookings), mature support for joins, transactions, complex queries.
Therefore for the primary booking system a Relation DB will be used like SQL Server or PostgresSQL.
API design
Public User-Facing APIs:
- BookingAPI
- GET /parkingLots/{parkingLotID}/availability – Check capacity and availability for a given parking lot.
- POST /bookings – Create a new booking - WRITE Booking to Database. Posts booking-created to Kafka.
Private APIs:
PaymentAPI
- Listens to Kafka booking-created topic for payments. Responsible for calling Payment Service to process the booking.
- A Web Hook can be used to await for payment confirmation - assumption is system using a 3rd Party Payment Provider like Stripe. Post payment-success, payment-failed to Kafka Queue and WRITEs Payment to the DB using the BookingID as the relationship.
- Messages should be idempotent and use Exactly-Once Message Queue to prevent duplicate payments.
NotificationsAPI:
- Listens to Kafka topics like payment-success, payment-failed. Responds with notification like email user to confirm booking.
AnalyticsAPI
- Listens for booking-created topic. WRITEs to DB
Car Park API - Gate Checking Service:
- ParkingAPI
- GET /parkingAccess?plate=XYZ123 – Validates if a vehicle has a valid reservation.
- PUT /parkingAccess – Updates vehicle arrival and exit timestamps.
System Notes:
Used a Message Queue Kafka is used to decouple services for durable event queues.
Events are persisted for replay to support failure recovery and system observability.
Database design
Core Entities
User
- user_id (PK), name, email, phone, vehicle_plate
ParkingLot
- parking_lot_id (PK), name, location, capacity, country_id
ParkingSpace
- space_id (PK), parking_lot_id (FK), type, is_available
Booking
- booking_id (PK), user_id (FK), space_id (FK), start_time, end_time, status, created_at
- Enforce unique constraint on space_id + overlapping start_time/end_time to prevent double-booking.
Payment
- payment_id (PK), booking_id (FK), amount, status, provider_ref_id, created_at
EventLog (for analytics/debugging)
- event_id, type, related_id, timestamp, payload
```
Table users {
user_id int [pk, increment]
name varchar
email varchar
phone varchar
vehicle_plate varchar
created_at timestamp
}
Table parking_lots {
parking_lot_id int [pk, increment]
name varchar
location varchar
capacity int
country_id varchar
created_at timestamp
}
Table parking_spaces {
space_id int [pk, increment]
parking_lot_id int [ref: > parking_lots.parking_lot_id]
type varchar
is_available boolean
created_at timestamp
}
Table bookings {
booking_id int [pk, increment]
user_id int [ref: > users.user_id]
space_id int [ref: > parking_spaces.space_id]
start_time timestamp
end_time timestamp
status varchar
created_at timestamp
// Prevent overlapping double bookings (handled via logic in code or DB constraint)
Note: 'Enforce uniqueness of space_id + non-overlapping time window via application logic or exclusion constraints.'
}
Table payments {
payment_id int [pk, increment]
booking_id int [ref: > bookings.booking_id]
amount decimal
status varchar
provider_ref_id varchar
created_at timestamp
}
Table event_logs {
event_id int [pk, increment]
type varchar
related_id int
payload text
timestamp timestamp
}
```
High-level design
The system uses a read-optimised, write-consistent architecture with event-driven architecture for processing asynchronous operations.
Client sends a request, which is communicate through the Load Balancer(s)/API Gateway, which is sent to BookingAPI.
BookingAPI optimises READs via a WRITE-THROUGH Cache this to keep the cache updated with the Database for strong consistency and durability at a trade-off of latency on WRITEs. BookingAPI WRITEs new bookings to the DB layer and produces "booking_created" topic to Kafka.
Kafka Message Queue
Data Layer
- Cache - Write-Through
- Database - Primary/Secondary to for strong consistency on WRITES, and scalability on READS.
Message Queue (Kafka):
- PaymentAPI - Consumes "booking_created". Calls Stripe, recieves Web Hook - produces "payment_success", "payment_fail"
- NotificationsAPI - Consumes "payment_success", "payment_fail" - sends notification of booking confirmation to user.
- AnalyticsAPI - Consumes "booking_created" - WRITES to Database.
High-Level System Design Diagram:
Request flows
The system follows as read-optimised, write-consistent and even-driven architecture.
1. Client to BookingAPI
- A user initiates a parking reservation from the client (mobile/web).
- The request is routed through the Load Balancer/API Gateway to BookingAPI.
2. BookingAPI Processing
- Read Operations: BookingAPI fetches availability data using a write-through cache (e.g., Redis). Cache is updated on writes, ensuring near real-time consistency.
- Write Operations:
- Inserts the booking into the Primary Database (e.g., PostgreSQL) for ACID guarantees.
- After a successful DB commit, it publishes a "booking-created" event to Kafka.
3. Kafka Event Propagation to ensure loose-coupling between services.
- PaymentAPI consumes "booking_created" topic, which initiates a payment with Stripe, waiting on a webhook.
- On receiving webhook, it emits "payment_success" or "payment_fail".
- NotificationsAPI listens for payment result events and sends corresponding user notifications (e.g., email or SMS).
- AnalyticsAPI also consumes "booking_created" and logs the event to the analytics DB for reporting.
Data Layer
- Cache supports low-latency reads.
- Database supports strong-consistency writes and read-scalability via primary/replica architecture.
Detailed component design
BookingAPI
- Handles booking creation and availability checks.
- Horizontally scalable; uses write-then-publish: writes to DB, then emits booking_created to Kafka.
- Reads from write-through Redis cache for low-latency availability checks.
- Prevents double-booking using DB constraints or row-level locks (SELECT FOR UPDATE).
Kafka
- Acts as a durable, distributed message queue to propagate events like booking_created, payment_success, and payment_fail.
- Enables loose coupling between services—BookingAPI doesn’t need to know about downstream consumers (PaymentsAPI, AnalyticsAPI, NotificationsAPI).
- Supports scalability via partitioned topics and replayability for fault recovery or backfilling.
- Ensures message durability and ordering within partitions, critical for processing payment and booking workflows in sequence.
Cache Layer (Redis)
- Stores availability data with fast read access.
- Write-through model: updates cache and DB on writes.
Each component is designed for horizontal scaling, consistency, and resilience. Let me know if you’d like a component diagram or call flow.
Trade offs/Tech choices
Kafka
Chosen for asynchronous, decoupled communication between services.
Trade-off: Adds operational complexity (managing brokers, partitions, message retention), but enables scalability, failure isolation, and event replay.
Preferred over direct API calls or synchronous flows to prevent cascading failures, allowing for a separation of concerns and circuit-breaking pattern. Supports extensibility (e.g., plugging in new consumers like AnalyticsAPI).
Relational DB (e.g., Postgres)
Selected for strong consistency, ACID guarantees, and mature support for constraints and transactions—essential for preventing double bookings.
Trade-off: May require sharding or replication strategies at scale versus simpler horizontal scaling in NoSQL.
Chosen over NoSQL for its ability to handle complex queries, enforce data integrity, and support joins (e.g., booking + payment history).
Failure scenarios/bottlenecks
Try to discuss as many failure scenarios/bottlenecks as possible.
1. BookingAPI Failure
If BookingAPI crashes after writing to DB but before publishing to Kafka, downstream services won’t be triggered.
Mitigation: Kafka’s exactly-once semantics to ensure atomic DB write and event publish.
Kafka Broker Outage
If Kafka is unavailable, services can’t produce or consume events.
Mitigation: Kafka replication and quorum-based writes ensure durability. Events can be replayed once brokers recover.
PaymentsAPI or Stripe Downtime
If PaymentsAPI fails or Stripe is unresponsive, booking remains unpaid.
Mitigation: Retry with exponential backoff, use webhook retry mechanisms, and persist payment state for replay.
Notification Failures
If NotificationsAPI fails, users may not receive confirmations.
Mitigation: Use dead-letter queues to retain troublesome erroring notification for later retries, retries, and monitor failed delivery metrics.
Database Bottleneck
Write-heavy traffic (e.g., during events) may overwhelm the primary DB.
Mitigation: Add read replicas, optimize indices, and consider partitioning or sharding if needed.
Kafka Replay
Kafka retains messages, allowing replay by consumers to recover from service failures or data loss—critical for PaymentsAPI, AnalyticsAPI, and NotificationsAPI reliability.
Future improvements
The system above provides a solid, scalable and event-driven implementation for a Parking System.
Future improvements:
Add CDN - Introduce a Content Delivery Network (e.g., Cloudflare) to cache static assets like images, scripts, and style sheets. This reduces load on backend servers and improves latency for users globally, it adds an additional logging layer to aid debugging issues.
Add a Logging layer to catch errors - Use Splunk or Sentry to capture info, debug, error logs.
Additional enhancements could include:
- Rate Limiting/Gateway Throttling to prevent abuse.
- Circuit Breakers/Timeouts for external calls like Stripe.
- Feature Flags for controlled rollout of new functionality.
- Multi-region deployment for high availability and lower latency.