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:


  • Needs to be consistent (need to handle multiple attempts of reserving the same parking spot at the same time by locking the row during a request and checking if a row is locked before fully committing)
  • Needs to be reliable (Needs to always provide correct information)
  • Needs to be scalable (we should be able to add more servers and databases)
  • Needs to have high availability (99.9% uptime)


Capacity:


  • Assume we need to handle about 500 million parking lots around the world
  • Assume every parking lot can hold about 100 cars
  • Assume users park and depart around 5 times a second
  • Assume users reserve around 10 times a second


(5 cars parking and departing/second) * 60 seconds * 60 minutes * 24 hours * 365 days * 5 years = 788 million

Assume every database entry is about 500 bytes = 394 GB


(10 users reserving/second) * 60 seconds * 60 minutes * 24 hours * 365 days * 5 years = 1.6 billion

Assume every database entry is about 500 bytes = 788 GB


API Design


  1. reserveParking API
    1. Request: { userId: string, parkingLotId: string, parkingSpaceId: string, reservationTimeStart: DateTime, reservationTimeEnd: DateTime }
    2. Response: None
  2. getReservations API
    1. Request: { userId: string }
    2. Response: { parkingLotId: string, parkingSpaceId: string, reservationTimeStart: DateTime, reservationTimeEnd: DateTime }
  3. parkCar API
    1. Request: { userId: string, parkingLotId: string, parkingSpaceId: string }
    2. Response: None
  4. departCar API
    1. Request: { userId: string, parkingLotId: string, parkingSpaceId: string }
    2. Response: None
  5. checkInGate API
    1. Request: { userId: string, parkingLotId: string, time: DateTime }
    2. Response: None
  6. checkOutGate API
    1. Request: { userId: string, parkingLotId: string, time: DateTime }
    2. Response: None


High-Level Design


The client will connect to a load balancer to balance the requests made to the server. Client will also connect to CDN to access static content for better performance.


The load balancer can direct to the server which points to different services. We will have the reservation service which will handle all reservations and payment related actions. We handle both in the same service so that if payment fails, reservations should also fail. Payments can be sent to a message queue which will be sent to an external payment system. We will write all items to a write through cache which will directly store that into the database. All the services will check the cache first and if we get a cache miss, we will check the database.


We will have multiple database instances, with a lookup table that maps a parkingLotId to a different shard. For example, we can have a lookup table that looks like:

{

lot1: database1

lot2: database1

lot3: database2

lot4: database3

}

The database will only store information on the lots that are mapped to it. As we get more and more users, we can continue adding more databases.


If user arrives at the lot, the lot will access the CheckIn Service to check the user into the lot. If the user leaves the lot, the lot will access the CheckIn Service to check the user out of the lot.


Once user parks, the lot will access the Parking Service to mark in the database that the user has parked.


User Flow

  1. User wants to reserve a parking spot. They will access the service and select a parking spot that is available at a certain lot.
  2. The user will make a payment through the payment provider and the reservation will attempt to be created.
  3. If the reservation is successful, it will be saved into the cache and the database.
  4. On the day of the reservation, the user will enter the parking lot and the parking lot will check the user in while recording the current time.
  5. Once the user parks the car, the lot will mark the user as parked during their reservation time until the time limit is up.
  6. Once the user leaves, the lot will mark the user as checked out.


Detailed Component Design


Reservation Service

  • Functionality
    • Handles both reserveParking and getReservations APIs to reserve a parking spot for a user and can also fetch the reservation information so the user can view it at any time. Payment is handled in reserveParking so that payment fails, reservation will also fail. Information will be written to the cache which will write to the database. Failed payments can be routed to the DLQ for inspection and manual redrives. If the initial reservation is failed, we can have an automatic retry (3 times) with an exponential backoff strategy.
    • If multiple users attempt to reserve the same spot, we will first check if the row in the DB is locked. If it's not locked, we will prioritize whoever attempted to reserve first. Once the request has been made, we will lock the row. Right before committing, we will check if the row is locked and if it is, the reservation will fail.
    • We can ensure data is never stale by having a TTL for the cached entries. When we have a cache miss, we will check availability in the database before making the reservation.
    • Tech Choice: Redis, Amazon RDS, Amazon SQS

Parking Service

  • Functionality
    • Handles parkCar and departCar APIs to track when a user has parked their car during their reserved time and when they choose to leave. Information will be written to the cache which will write to the database. If parking service fails, we can have an automatic retry (3 times) with an exponential backoff strategy.
  • Tech Choice: Redis, Amazon RDS

CheckIn Service

  • Functionality
    • Handles when a user has arrived at a parking lot and when they have left the parking lot. Information will be written to the cache which will write to the database. If checkin service fails, we can have an automatic retry (3 times) with an exponential backoff strategy.
  • Tech Choice: Redis, Amazon RDS


TradeOffs/Tech Choice:

  • We will be using a write through cache so we do not have to sacrifice performance. If we just use a normal cache, we will potentially be using stale data and users may attempt to reserve or park in the same spot. By using a write through cache, we can have high performance while not sacrificing consistency or having stale data.
  • I have went with a relational database over a nonrelational one because I have chosen to prioritize consistency over speed and scalability. We want our parking lot to always have the correct information, otherwise users may park in the same spot as another user.