assuming all slots hold same amount of items, average 10 users a day, pilot project w/1000 machines in over 10 countries. Machine has 7 rows and 5 columns, 10 year lifespan. Assume machines only allow for denominations of 100,20,5, 50cents,10cents,5cents,1cents with a limit on how much a machine can hold of each denomination. Each machine has a camera that holds 24 hours worth of footage at 720p which is deleted at the end of the week
~ 24 gb of space needed per machine per week ( with standard compression we can achieve around 12 gb)
Machine{
id: 10 bytes,
password: 10 bytes,
rows : 3290 bytes (7 rows w/5 cols),
change: 120 bytes,
camera_id: 24 gb,
longitude:10 bytes,
latitude: 10 bytes,
notification_frequency: 10 bytes
}
Camera{
id: 10 bytes,
machine_id: 10 bytes,
footage: 12 gb
}
Change{
id: 10 bytes,
machine_id: 10 bytes,
hundred: 10 bytes,
fifty: 10 bytes,
twenty: 10 bytes,
ten:10bytes,
five:10bytes,
one:10bytes,
fifty_cent:10bytes,
ten_cent: 10 bytes,
five_cent: 10 bytes,
one_cent: 10 bytes
}
Row{
id : 10 bytes,
row_number: 10 bytes,
machine_id: 10 bytes,
Slot: 450 bytes
}
Slot{
id: 10 bytes,
slot_number: 10 bytes,
machine_id: 10 bytes,
item: 70 bytes
}
Item {
id: 10 bytes,
item_name: 10 bytes,
item_description: 30 bytes,
item_category: 10 bytes,
price: 10 bytes,
quantity: 10 bytes,
row_id: 10 bytes,
slot_id: 10 bytes
}
Cart{
id: 10 bytes
items: 70 * 10(max items in cart) = 700 bytes
}
Activity{
id:10 bytes,
machine_id: 10 bytes,
items_sold: 1000 bytes,
date: 10 bytes
}
Notification{
id: 10 bytes,
machine_id: 10 bytes,
reason: 100 bytes
}
payment{
id: 10 bytes,
type: 10 bytes,
date: 10 bytes,
amount: 10 bytes,
}
Error{
id: 10 bytes,
machine_id: 10 bytes,
date: 10 bytes,
message: 100 bytes
}
With data retention policy, we will only keep camera data from the last 30 days, ie around 4 weeks
12gb/week * 4 * 1000 machines = 48000 GB per month
48 TB * 12 * 10 = 5760 TB for 10 years
SendNotification(machine_id,reason,date) -> sends notification to owner for whatever reason
changeMachineId(machine_id,password) -> allows a machine owner to change machine_id if it is not already in use
ViewMachineInfo(machine_id,password) -> Returns the user the machine info/inventory/analytic graphs
DispenseItem(machine_id,item_id) -> dispenses item and updates quantity. If item is below 30% capacity then send notification
RecordActivity(machine_id,Cart,date) -> After each sale this function is called, records the sale in the vending machine's local memory by its date
SendReport(machine_id,Activity[]) -> Sends a report to the owner of the sales based on preset notification schedule (daily, weekly, monthly)
DisplayError(machine_id,message) -> Displays error to machine user (low change, insufficient funds etc)
DispenseChange(machine_id, change_amount) -> Utilizes machine id to see what change this particular machine has and dispenses change accordingly, if there is an issue dispensing change, error message will be displayed
AddToCart(cartId, Item) -> Adds item to the cart
UploadVideoFootage(machine_id, date, footage) -> Compress daily video footage at the end of the day and upload footage to online storage
DeleteFootage(date) -> Delete all footage recorded from past month as per data retention policy
PurchaseItemCash(date,machine_id,Item,method,card_details,cash) -> If physical cash is used, check if it is enough and dispense item or error accordingly. Play audible sound and display visual message upon success. The machine updates visual outstanding balance upon insertion of each coin/bill.
ReturnMoney() -> Returns all money a user inserted into the machine
PurchaseItemCard(date,machine_id,Item,card_details,apple_pay_details) -> send transaction through stripe to validate card details or apple pay information and dispense/display error accordingly. Play audible sound and display visual message upon success. If payment failed, view stripe api error response message to see what happened and rewrite the error message in readable and clear terms. Display a red "x" symbol and clearly display payment error
Machine{
id: string,
user_id: string,
password: string,
rows : Row[],
change: Change,
camera_id: string,
longitude: string,
latitude: string,
notification_frequency: string
}
Camera{
id: 10 bytes,
machine_id: 10 bytes,
footage: mp4
date: datetime
}
Change{
id: string,
machine_id: string,
hundred: int,
fifty: int,
twenty: int,
ten: int,
five: int,
one: int,
fifty_cent: int,
ten_cent: int,
five_cent: int ,
one_cent: int
}
Row{
id : string,
machine_id: string,
row_number: int,
Slot: Slot[]
}
Slot{
id: string,
machine_id: string,
slot_number: int,
item: Item
}
Item {
id: string,
item_name: string,
item_description: string,
item_category: string,
quantity: int,
price: float,
row_id: string
slot_id: string
}
Cart{
id: string,
items: Item[],
cart_total: float
}
Activity{
id: string,
machine_id: string,
items_sold: Item[],
date: datetime
}
Notification{
id: string,
machine_id: string,
reason: string,
date, datetime
}
payment{
id: string,
type: string (card or cash or apple pay),
date: datetime,
amount: float,
}
Explain how the request flows from end to end in your high level design. Also you could draw a sequence diagram using the diagramming tool to enhance your explanation...
RecordActivity(machine_id,Cart,date) -> This function takes the machine id and the user's cart data to record a transaction made on the machine. The data is stored by date on the database. This key points to the machine id and the items purchased. The point of storing this data is for future data visualization in order to generate metrics which help machine owners determine which products sell better than others in order to maximize profits and develop effective restocking schedules from taking note of time of transactions.
UploadVideoFootage(machine_id, date, footage) -> This function records daily footage, at the end of the day, a script compresses the video footage using some kind of video compression software (VLC, etc). This is then is stored in some blob storage like amazon s3. The id of the video is also stored in a table by date, once the video deletion policy date arrives, videos stored by a certain date are deleted
I chose to use a relational database over non relational since we have data models that won't change much, this also seems to be a very read heavy system which can benefit from a relational database. We also need a very consistent database, however since each machine is unique and we will have at most one user using a machine at a time, we can ignore any potential race condition issues
We can store the video id's in a separate time series database to optimize searching for all video ids to delete by upload date
During peak user times, we can reduce system load by utilizing a load balancer that routes users to their appropriate geolocation db shard.
We can employ a hot standby database that replicates data onto another database which can be swapped with the original database in case the original db goes down
What are some future improvements you would make? How would you mitigate the failure scenario(s) you described above?