System requirements


Functional:

  1. Register with the system, providing details about your vehicle, vehicle type (standard, oversize, motorcycle, ADA, EV), etc.
  2. Place a reservation with a specified parking lot, including expected arrival and departure times
  3. Modify or cancel a reservation
  4. Check in upon arrival with a reservation to get your assigned spot
  5. Check in upon arrival without a reservation
  6. View parking availability for a parking lot for the current time, or some specified time
  7. Check out upon departure. Pay for the parking and release the parking spot
  8. Add a parking lot, including # of parking spots and # of accessible parking spots


Non-Functional:

  1. Store 1M car registrations, with 20% expected annual growth, and 10% expected customer expiration
  2. Allow parking lots with 1K parking spots
  3. Support 10K reservations per day, with 20% annual growth
  4. All operations take < 500ms
  5. Service must be always available (99.99% up-time)



Capacity estimation

  1. Customer registrations will require about 1K (including car info, customer name and address, preferences, history) for 1GB of storage, initially
  2. Reservations will be small (128 bytes for customer, parking spot, date, and status) but persist indefinitely to provide customer history. Storage will grow by 300MB per year, increasing 20% each year
  3. Support 10K reservations per day (< 1 transaction per second), growing 20% per year. Expect seasonal variation in reservations up to 500%



API design

POST /api/v1.0/registration

{"name": string, "license_plate": string, "email": string, "vehicle_type": enum}


returns:


If all fields are valid and we haven't already registered:

200 {"vehicle_id": int, "creation_date": timestamp, "modified_date": timestamp "name": string, "license_plate": string, "email": email_addr, "vehicle_type": enum}


Else: 400 Bad Request


POST /api/v1.0/reservation

{"vehicle_id": int, "parking_lot_id": int, "entry_time": timestamp, "exit_time": timestamp}


returns:


If the vehicle is valid and there is space in the parking lot:

200 {'reservation_id": int, "vehicle_id": int, "parking_lot_id": int, "entry_time": timestamp, "exit_time": timestamp}


Else: 404 Not Found (if vehicle_id is bad) or 400: Bad request (if no spots are available)


PUT /api/v1.0/reservation

{"reservation_id": int, "vehicle_id": int, "entry_time": timestamp, "exit_time": timestamp}


If the reservation_id is valid and there is space in the parking lot:

200 {"reservation_id": int, "vehicle_id": int, "parking_lot_id": int, "entry_time": timestamp, "exit_time": timestamp}


Else: 404 Not Found (if reservation_id is bad) or 400: Bad request (if no spots are available)


PUT /api/v1.0/reservation

{"reservation_id": int, "checked_in": TRUE}


If the reservation_id is valid, the customer will be assigned a parking spot:

200 {"reservation_id": int, "checked_in": TRUE, "parking_spot": string, ...}


Else: 404


POST /api/v1.0/reservation

{"vehicle_id": int, "parking_lot_id": int, "entry_time": timestamp, "exit_time": timestamp, "checked_in": TRUE}


If there is a spot available that meets the vehicle requirements:

200 {"reservation_id": int, "checked_in": TRUE, "parking_spot": string, ...}


GET /api/v1.0/parking_lot/{parking_lot_id}/?page=int


If parking_lot_id is valid return capacity info for the lot:

{"parking_lot_id": int, "available_spots": int, "available_oversize": int, "available_motorcylces": int, "available_ada": int, "available_ev": int}


PUT /api/v1.0/reservation

{"reservation_id": int, "checked_in": FALSE}


If the reservation_id is valid, the customer's parking spot will be released:

200 {"reservation_id": int, "checked_in": FALSE, "parking_spot": string, ...}


Else: 404


POST /api/v1.0/parking_lot/

{"parking_lot_name": string, "available_spots": int, "available_oversize": int, "available_motorcylces": int, "available_ada": int, "available_ev": int}


Return

{"parking_lot_id": int, "parking_lot_name": string, "available_spots": int, "available_oversize": int, "available_motorcycles": int, "available_ada": int, "available_ev": int}


Database design

Table registration

vehicle_id: int -- Primary Key

name: string

license_plate: string

email: string

vehicle_type: enum

reservation_count: int


Table reservation

reservation_id: int -- Primary Key

vehicle_id: int -- External Id

parking_lot_id: int -- External Id

scheduled_entry_time: timestamp

actual_entry_time: timestamp

scheduled_exit_time: timestamp

actual_exit_time: timestamp

checked_in: bool


Table parking_lot

parking_lot_id: int -- Primary Key

parking_lot_name: string


Table physical_capacity

parking_lot_id: int -- External Id

vehicle_slot: int

capacity: int


Table current_capacity

parking_lot_id: int: Index

hour_slot: timestamp

vehicle_type: enum

capacity: int


We will have a stored procedure that is run on the reservations to update the current_capacity table


High-level design

We have a Load Balancer to distribute traffic among the Services


Registration Service allows a customer to register their vehicle with our System--and make changes to their registration


Reservation Service allows a customer to request a reserved spot in the parking lot, to modify their reservation, to check in when they arrive for their reservation, and to check out when they leave


Parking Lot Service allows a parking lot owner to specify the capacity of their parking lot


A Redis cache sits between the Database and the services, allowing quicker access to DB entries


Database is a MySQL DB. We need a SQL database because we need to guarantee transactional consistency




Request flows

Requests flow through the Load Balancer to the appropriate service (based on the API path). Each service will attempt to find the DB objects referenced in the RESTful payload (first in Cache and then in Database) and, if the request is valid, it is executed



Detailed component design

Reservation Service is used for several actions:

  1. Make a reservation: First we validate that the referenced objects are valid and if so, we check capacity, looking for Reservations records that overlap entry_time -> exit_time. We can make this efficient by periodically computing capacity for each hour.
  2. Revise a reservation: As above, we validate the inputs, then check capacity. If the time is available, we release the initial time interval and book the new one
  3. Check in for reservation: As above, we first validate the inputs. If they are good, we set the actual_entry_time and mark the reservation as checked_in
  4. Check in without a reservation: This is effectively a combination of 1 and 3
  5. Check out: We first validate the inputs. Then set the actual_exit_time and mark the reservation as not checked_int


We will have run a stored procedure that updates the current_capacity table. It will use an Interval Tree approach to do this efficiently



Trade offs/Tech choices

We choose a Load Balancer to provide redundancy across servers and enable us to scale. Also, if our service gets big enough we can shard the DB to improve performance

We use a SQL DB (MySQL) because our actions are transactional

We use a Cache to improve DB performance





Failure scenarios/bottlenecks

Maintaining the available capacity of each parking lot is potentially expensive, computationally. We need to devise a policy for updating the capacity--and an algorithm for doing this quickly

If customers don't follow their reservations we can potentially underbook or overbook the parking lot.





Future improvements

  1. Deal with charging customers
  2. Deal with customers missing their reservations and maybe being charged
  3. Deal with customers missing their check_out time and maybe being charged
  4. Offer discounts for frequent customers, especially during quiet times
  5. Offer waitlist for customers, if their preferred lot is full at their desired time
  6. Suggest alternative lots for customers, if their preferred lot is full