APIs used by the user would be the following -
APIs used by the Parking Lot staff would be the following -
Based on the estimates done above, we should go for a database that offers high consistency, for that purpose, going with a relational database makes sense. A database like MySQL or PostgreSQL which used Single Leader replication would handle write conflicts well. Among the two, we go with Oracle MySQL since it is more equipped to handle cases where might run into write conflicts, since it uses pessimistic concurrency control in the form of Two Phase Locking.
Our database can be sharded based on the region, we just need to make sure to replicate data across data centers to avoid data loss in case of a failure.
Our tables would look like given below.
flowchart TD CL[client] --> CDN[CDN] CDN-->RL[Rate Limiter] RL --> LB[Load Balancer] LB --> GW[API Gateway] GW --> RES[Reservation Server] RES --> CC[Cache] RES --> DB[Database] RES --> MQ[Message Queue] GW --> TX[Transaction Server] TX --> DB TX --> CC MQ-->PAY[Payment System]
check_capacity() is handled by Reservation Server consulting the data in the database.
reserve_spot() is handled by Reservation Server, which creates a new entry in Reservations table.
It would create a request for payment, which is queued by Message Queue and submitted to the external Payment System.
reserve_spot() may fail if multiple users are trying to book the same spot. Return an error and ask the client to try calling reserve_spot() again.
It would also fail if the lot is full. In that case, the client should not retry. Reservation Server may return helpful information such as estimated time spots may open up in the future.
vehicle_arrived() and vehicle_left() are handled by Transaction Server, which modifies the Transaction table to keep track of vehicle check-ins and checkouts.
If vehicle does not arrive after some number of hours (e.g. 24 hours), reservation would be canceled. The user would be charged for 1 day of payment.
If inconsistent state happens, e.g., vehicle_left() is called before corresponding vehicle_arrived(), administrator at the lot should be notified. The service would assume the vehicle arrived around the corresponding reservation start time.
sequenceDiagram Client->>+Reservation Server: reserve_spot() Reservation Server->>+Client: reservation_ID, price Client->>+Reservation Server: pay() Reservation Server->>+Payment System: Payment Request Payment System ->>+ Reservation Server: Payment Result Parking Lot ->>+ Transaction Server: vehicle_arrived() Parking Lot ->>+ Transaction Server: vehicle_left()
See Sequence Diagram. It depicts how messages flow for client to make a reservation and pay for it, and for the parking lot system to notify the Transaction System when the user's vehicle arrives and leaves.
One important algorithm is how to find an open spot when reserve_spot() call is made.
To support this functionality, we would chop 24 hours into 15 minutes. One day would be represented by 96 slots. Since we just need to store occupied / unoccupied, we can use one bit to represent the 15 minute slot. 1 spot requires 96 bits per day. 35040 bits (~4KB) per year.
When reserve_spot(start_time, end_time) is called, the algorithm would be:
This algorithm would find the closest parking spot that is available for the desired time slot.
Because this search is bound by the number of parking slots (100s) and the granularity of reservation (15 minutes), it would be performant enough.
It would probably be possible to use Interval Tree data structure to optimize this algorithm further, if further optimization is required.
The payment for the service will be redirected to an Payment Service Provider like Stripe or VISA since payments need a high degree a security which will be expensive to implement from scratch. We can create a ledger to record all payment transactions and then send the daily report to our provider for cross checking.
During our payments, we can make use of idempotency to make sure payments for a transaction happen only once. We will need a unique key generator for this purpose, for which we can use a Base 62 unique ID generator. The unique transaction ID can serve as our idempotency key.
A problem could in cases where a large number of people try to make a reservation in case of an event, like a concert for a popular music band or artist, classic thundering herd problem. This could overwhelm the system. Although, it should be pointed that our transaction system would only receive requests proportional to the number of parking slots we have, which is not much.
To combat this, we horizontally scale all components, rate limit requests to maintain smooth operations. We also replicate aggressively to handle data loss in case of a disaster. Also, a monitor system could be put in to check the health of all our services.