System requirements
Functional:
- Register with the system, providing details about your vehicle, vehicle type (standard, oversize, motorcycle, ADA, EV), etc.
- Place a reservation with a specified parking lot, including expected arrival and departure times
- Modify or cancel a reservation
- Check in upon arrival with a reservation to get your assigned spot
- Check in upon arrival without a reservation
- View parking availability for a parking lot for the current time, or some specified time
- Check out upon departure. Pay for the parking and release the parking spot
- Add a parking lot, including # of parking spots and # of accessible parking spots
Non-Functional:
- Store 1M car registrations, with 20% expected annual growth, and 10% expected customer expiration
- Allow parking lots with 1K parking spots
- Support 10K reservations per day, with 20% annual growth
- All operations take < 500ms
- Service must be always available (99.99% up-time)
Capacity estimation
- Customer registrations will require about 1K (including car info, customer name and address, preferences, history) for 1GB of storage, initially
- 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
- 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:
- 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.
- 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
- 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
- Check in without a reservation: This is effectively a combination of 1 and 3
- 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
- Deal with charging customers
- Deal with customers missing their reservations and maybe being charged
- Deal with customers missing their check_out time and maybe being charged
- Offer discounts for frequent customers, especially during quiet times
- Offer waitlist for customers, if their preferred lot is full at their desired time
- Suggest alternative lots for customers, if their preferred lot is full