0% found this document useful (0 votes)
8 views

System Design Examples

The document discusses potential designs for systems like ride-sharing apps, messaging apps, web crawlers, and auctions. It considers user and data estimates to determine requirements, breaks down components, and evaluates different design options and tradeoffs to address scalability, performance, and data consistency issues.

Uploaded by

Mohit Agarwal
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
8 views

System Design Examples

The document discusses potential designs for systems like ride-sharing apps, messaging apps, web crawlers, and auctions. It considers user and data estimates to determine requirements, breaks down components, and evaluates different design options and tradeoffs to address scalability, performance, and data consistency issues.

Uploaded by

Mohit Agarwal
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 10

***********************************************************************************

***********************************************************************************
*********************

note : uber system design


-> a rider will request multiple driver near by

-> riders estimate (demand)


i) how many total rider: 100M total rider => storage req ?
i) how many active rider : 10M active rider => how many req per seconds ?
i) how often they consume our service : 10 riders per month on avg

-> driver estimate (supply)


i) 1M drivers :
i) 500K active drivers
i) avg shift : 6 hours

note : storage location


-> db estimate : if we want to support 50k insert operation per second then we need
10 shards
-> each shards roughly should have equal number of users
-> shard key : country : tenant based sharding => drawback : one country may have
very high number of users called as hotspot
=> divide world in polygons that can be large or small ; find driver location with
help of driver location service and put it in shard locator to map location with
shard
=> hexagons are good shaped to cover a sphere like globe and each in area

note : api : show drivers around user


-> refresh 5 seconds
-> display 10 drivers at most
-> rest based

note : throughput calculation : number of request per second calculation


-> 10M active riders
-> 5 opens of app a day per user = 5*10M = 50M open a day total
-> refresh every 5 seconds
-> avg session time is 1 min = 12 refreshes
-> 50M * 12 = 600M requests per day
-> 600M / 24*60*60 = ~7K requests per seconds (read)

-> user location will go to location service and then mapped to correct shared
where a filtering logic will show 10 nearest driver to rider
-> each shard has 1M/10 = 100K users (filter 10 pointes nearest to (x,y))
-> indexing at position in db will increase req timeouts for drivers so we will use
CQRS
-> driver location will be stored in db in normal fashion ; there will be one more
db that will query everything in every 5 seconds from that db and put it in a db
with indexing
-> that will be done with help of indexing worker process

=> drawback : delay between driver location updation and user reading ; number of
indexing as it is expensive

note : matching service


-> it will select a driver for a user out of 10 with any strategy
i) sequential : req driver and until a driver accept req one by one (not good for
user)
i) concurrent : req all drivers at same time and rider will be assigned on fcfs
(good for user ; bad for driver)

=> how to send req to driver ?


-> driver gateway: introducing a gateway layer with matching service which will use
websockets that will send req to all the drivers in sequntial or concurrent manner
-> rider gatway : after driver assignment ; rider will get the details of user
=> driver gateway socketes will be connected only with drivers
=> rider gateway sockets will be connected only with riders

note : polling
-> rider connect with driver gateway socket and ask matching service to fetch and
book one out of 10 nearest cab
-> matching service ack the rider
-> then after an interval rider keep on asking if cab is booked and matching
service either responds no / driver details

note : uber system design


-> driver -> websockets -> driver gateway -> (locations) => driver location system
-> sharded storage
-> every 5 seconds : driver location system fetches location out of sharded storage
and apply geo indexing
-> every 10 seconds : rider -> rider gateway (websockets / rest) -> (fetch 10 taxi
around me) -> driver location service (fast query)
-> rider -> (confirm booking) -> matchin service -> (send rider details to 10
nearest driver) -> driver gateway web scokets
-> if rest then driver keep checking every 10 seconds if cab is booked or not a =>
matching service ack no / cab details as response to the rest request
-> if web sockets then once drive books then details are sent from matching service
to connected driver

***********************************************************************************
***********************************************************************************
*********************

note : whatsapp system design


-> active users: 100M
-> number of chats we need to support at a time: 1M chats
-> number of messages per day : 1B
-> platforms we need to support : mobile and web
-> do we need to support group chats : no

=> when the user will open the app and click on one of his friend, then all the
messages sent by user and recieved by user should be fetched with pagination

note : rest based apis


-> lets suppose we have multiple instance of chat service connecting with same
database
-> each user will send message as put request and the load balancer will pass it to
a service instance which will put it into db
-> each user will send a get request /user_id : to fetch all messages of this
particular user every 5 seconds

note : db design
-> from_uid ; to_uid ; message ; sent_at_time
=> each query will be run on uuids of sender and reciever => indexing at uuids will
increase the query speed

note : identification of bottlenecks


1) storage location
-> 1B messages per day => 12k messages per second
-> 12K inserts per seconds in db ; but db estimate limit is 5000k inserts per
seconds ; still it can be done with incresing db storage power with little bit of
buffering

2) number of request
-> number of fetch per minute : (total open chats * number of times fetch gets
called in a minute) => 1000000 * (60/5)
-> number of fetch per second : 200k select per second
=> it is very bad performance

*) scalable reads
i)
-> it happens because we are pulling/fetching the messages every 5 seconds
-> instead of that we can use web sockets to directly push the message to the user

i) we can scale reads with help of db replicas


-> 1 node process 10k request then we will make 20 nodes / 20 instances of db and
put it behind db load balancer
-> cons : it will increase cost as we have to store 20 copies of 1B chats

i) sharding chat messages


-> if we shard on the basis of user_id then to recieve messages between 2 users we
need to query two shards
-> sharding strategy : *** all messages sent from neo to trinity and trinity to neo
will be present in same shard
i) sort([neo, trinity]) = [neo, trinity]
i) sort([trinity, neo]) = [neo, trinity]
i) hash(sort([neo, trinity]))

note : whatsapp system design


-> user / client -> websockets -> multiple instances of chat system service
-> chat system -> 12k writes -> shard locator (based on sender and reciever user
id) -> correct shard
-> chat system -> 200k reads -> shard locator (based on user reading the message of
which friend) -> correct shard

note : queues + web sockets to improve system performance


-> if we only use websockets then it make a synchronous system making it a bit
slower
-> if a sender send message to receiver then until reciever recieves the message
sender can't send new message
-> if we use queue then sender messages will keep on appending in queue async and
will get processed and recieved by reciever ; meanwhile sender can send more
messages
-> websockets and db writer will be subscribed to that queue to send message to
user and put message in db shard by load balancer and shard locator respectively
=> it removes the reads on db : read only on user connect

***********************************************************************************
***********************************************************************************
*********************

note : web crawler system design


-> number of sites to crawl : complete internet : 1.5 B sites
-> start crawl point and endless crawl
-> all the pages of all websites needs to be sent to indexer

note : basic design


-> get url -> fetch content -> store
-> get url -> fetch content -> extract new urls -> get url

note : problem
-> extraction of new url and fetching new url is synchronous
-> fetching of content and storing is synchronous
=> using queues at both the places will make it async and efficient
=> url extractor and indexing service will be subscribed to content queue

-> get url -> fetch content -> push queue -> indexer
-> get url -> fetch content -> extract new urls -> push queue -> get url

note : content fetcher service


-> it needs headless browsing because most of the websites are in javascript
-> browser architecture : user interface ; render engine ; js interpreter ;
networking
-> if head browser then it will just fetch user interface that can miss lot of
details
-> if head less then it will fetch all the js content along with html

note : url structure


-> protocol://host/path/param#anchor
-> content of urls can be changes overtime

note : extract new url


=> solution 1
-> it can have a uniqueness checker : it will check whether url we are extracting
is new or already processed == bloom filter (map)
-> but we need to accomodate all sites all pages => (1.5 B sites * 10 pages each
site avg) = ~50GB ram
-> it is easy to have a server with 64 GB ram ; but we need to have multiple server
behind load balancer to provide better availablilty : sync problem
=> solution 2
-> we can have redis database instead bloom filter which will have hashed key of
processed sites : just need to store urls
-> 1 url : 50 bytes => 1.5B = ~700GB data ; redis data max limit estimate : 10 GB
ram
-> we can shard redis db into 70 redis dbs
=> solution 3
-> relational db instead redis : relational db size estimate : 1 TB data

-> sol 1: high throughput ; weak consistency


-> sol 2: medium throughput : medium consistency
-> sol 3: low throughput ; high consistency

***********************************************************************************
***********************************************************************************
*********************

note : design an auction


-> there is a current price of an item
-> you place a higher price and bid
-> if your bid is maximum of all then you win
-> if someone else bids higher than you then you outbid: current price change
note : per day values
-> how many items expected to be sold at same time ? : number of auction : 100k
-> how many bidder on an avg per bid ? : 10-1k
-> how many bids are there on an avg : 5 bids
-> how long an avg auction ? : 24 hours

note : basic design : rest


-> user -> post/item/:id/bid -> bid service -> db (item_id, price)

=> new bid -> bid service -> it sends get request to db to get current top price
=> bid service checks if price right now is higher : if yes update price in db and
send congratulate ; if no then send sorry

note : major issue : data inconsistency due to lack of serialization


-> if current price is 2$
-> one bidder bids 3$ and one bidder bids $5
-> both will read value at same time from db => $2
-> both will win the bid which is wrong ; $5 should win

note : solution
-> bidder bids a price ; bidding service fetch current high
-> if current high > bidder price then return sorry
-> else execute this query : update bids set price = bidder_price where / if price
= current high
-> if 1 row affected : still price is same then send congrats
-> if 0 row affected : someone else changed the price in error concurrency window
then send sorry

=> this will also cause issue if 3$ writes first in db then it will win : puttin a
price < bidder_price will resolve
=> alternate solution : one bid at a time but that will also make our system slow

note : serialization : queue (async approach)


-> all the bidder will bid and send their price to bidding service
-> bidding service will put all the prices into a bid queue
=> method 1
-> a serializer will be subscribed to that queue and get all price from it ;
process them (find maximum of them)
-> then perform the update in db
=> method 2
-> a serializer will fetch bids one by one ; check with db and update
=> how to tell client if there bid was accepted or rejected as bid service do not
know if a user bid accepted or rejected or which user won as it do not communicate
with db anymore

note : event driven approach


-> we will have another response queue from serializer to bids service ; but client
response can be slow because of that
=> rest approach: polling : every x seconds it wil check if the user's bid won or
not : bid service => check status => db
=> web sockets : all users connected with websockets ; once serialzer decides
winner and update db it will send response to resp queue for each user
=> once msg arrives at bid service : it will send to web socket

note : scalablility check

-> writes
-> 100k items * 1000 users * 5 bids per user => 500,000,000 writes per day / 24
hours
=> 8k writes per second = ~ a single node / instance of db can handle

-> reads
-> every bid require read from db : 1000 users * 5 bids = 5k reads (db reads)
-> we need to read a new resp msg in res queue : 8k messages read (res queue reads)

note : auction system design


-> user -> websockets -> bid service -> bid queue -> bid processor (read from bid
queue + fetch from db) <-> db
-> bid processor -> rep queue -> bid service (read from resp queue and send to user
via sockets)

***********************************************************************************
***********************************************************************************
*********************

note : url shortner


-> user give long url to servive
-> service give shorter url
-> vice versa

note :
-> new urls per month: 500 M
-> how long to keep urls in db : 5 years
-> total urls : 500 * 5 * 12 => 30 B
-> read write ratio : 100:1

note : write per second


-> 500M url per month => 300 urls per second
=> we can handle even 10x more writes easily

note : storage requirements


-> 1 url => 100 bytes
=> 30 B * 100 bytes => 3 TB
=> a machine can handle 3 TB data with a large hard disk

note : basic design (rest http)


-> user -> put / long url -> url shortner (generate identifier) -> (identifier +
long url) -> storage

note : read per second


-> 300 write per second => 30K reads per second => at peak 300K reads per second
=> single instance of db can only read 10K per second

note : scaling reads

=> relational database


-> read replica can enable it ; but each of our db replicate should have 3 TB data
along with load balancer
-> get url/xyz -> url shortner -> storage (number of relica = 3-30)

=> redis db replicas (key value store) => 3 redis repicas can easily handle it (1
replica can do 100K reads per second)
-> but redis is limited by ram ; if ram is 64 GB then also we will need 50
instances of redis even to store data

=> dynamo db (300K read per second) (but price scales with cost : very expensive
with large scale system)
=> cache
-> get url/xyz => check if shortened url identifier is already in the cache =>
return
-> caching distribution and strategy : read aside cacheing
-> if 1% of total db storage (30TB) => 30 GB (managable)
-> but 300 K peak reads can not be handled by single machine ; if we distribute
cache along 4 machines then it will come under 100 k rps (manageable) (cache
sharding)

=> in memory cache is more faster than cache

note : final url shortner system design


-> put long url 300 rps -> url shortner -> rdbms (6 * 3 TB data each instance)
-> get url/xyz 300k rps -> url shortner => check in-memory cachce -> check cache
(30 GB * 4 sharded cache in 4 machines) -> rdbms

***********************************************************************************
***********************************************************************************
*********************

note : coupon system


-> user comes to claim coupon : if not claimed already => give coupon code
-> if claimed already then check if more coupons are left => if yes send congrats ;
else send sorry

note : estimates
-> number of users : 100M active users
-> number of coupons to give away : 10M coupons
-> how long will it take us : 10 minutes

note : basic design (rest http)


-> user -> uid => coupon service -> (select * from coupons where user_id = uid) ->
storage (user_uuid, coupon_uuid)
-> if row exist then return coupon id to the user
-> if row does not exist then count number of rows in coupon table => number of
rows = total coupons claimed till now ; if count > 10M then send sorry
-> else insert a row into coupon table : uid, cid and send congrats to user

note : issues
-> concurrency : if two user check for coupon and only one coupon left but both
users will get a coupon because for both row_count = 10M - 1
-> scalability

note : concurrency
-> serialization of events using a queue
-> all the uuid req will go to queue and then serializer will pick one by one ;
check rows in db and return the resp in resp queue => coupon service
-> if multiple consumer (serializer) => then it will fall into same problem of
concurrency

note : read estimate


-> 100 M active users : 10 minutes to distribute coupon
-> 10M users per min => ~20k active users per second
-> total reads per second : 20k (rdbms can handle 10k but manageable if we increase
db power or use redis)
=> but what if 100M users arrive at first minute => 1M read per second

note : write estimate


-> 10M coupon : 10 minutes
-> 1M write per min
=> 2k writes (avg)
=> worst case : 10M write per second

note : pre population


-> we will make a db with 10M rows with coupon ids random (userids will be null)
-> when a user req then if uuid already exist then return coupon code
-> if does not exist then update coupon set user_uuid=? where user_uuid=null limit
1
=> if failed query then return sorry ; else return congrats

note : to imporve performace as number of req can be very high


=> we can run different microservices instance of coupon service
=> we can use l7 load balancer that will map user_ids with a particular coupon
service that can easily handle 1000 req per seconds
-> each server can have its own db for particular set of users => 1M rows each db
=> if one server db is full ; then all user req to that server can not get coupon
even if other dbs have coupons

-> each service can have


=> in memory cache of issued coupons
=> in memory queue of coupons to issue (if it crashes then those user req will not
get coupon)

***********************************************************************************
***********************************************************************************
*********************

note : twitter system design


-> user a tweets => news feed system
-> user b can view tweets of user a & c
-> user c can view tweets of user a & b

note :
-> new tweets per second : 10K
-> read write ratio : 30:1
-> number of avg and max follower a user can have : 100 followers / 1M followers

note : tweeting
-> user -> post /tweet -> news feed service -> db (tweet table (uid, tweet, ts) +
follower table (s_id, t_id) (s follows t means s can see tweets of t but vice
versa is not true)
-> followers / users -> get /tweet?limit=10 (each minute) -> news feed

note : write per second


-> 10K writes per second : a large db can handle (generally it handle 5K writes) =>
write buffering + CQRS design

note : read per second


-> user tweets and all followers reads the tweet in same seconds => 100 reads per
second / 1M reads per second
-> if total number of users x => x*100 read per second / x*1M reads per second
=> but generally it is not the case, so by read-write ratio => 300k reads per
second (a db can handle 10k reads per second)

note : storage requirements


-> 10K tweets per second => 864M tweets per day
-> 100 byte per tweet => 86 GB
-> total tweets per year => ~31 TB (it can not fit in single rdbms => ~hdd = 1-2-4
TB)

note : scaling reads


-> 300K reads per second
-> a db can handle 10K reads per second => 30 replica
-> but with increased power we can stretch it to 20k - 50k => 6 - 15 replicas
=> pros : simplicity
=> cons : cost and space requirements
=> problem : if we make 5 replica then also in each replica we need to store 30 TB
of data

note : solution
-> instead of replicas we can shard database : each shard will have a piece of 30TB
of data
=> 30 shards => 10K reads per shard + 1 TB data each shard
=> 12 shards => 25K reads per shard + 3 TB for each shard
=> pros : less costly ; cons : complexity

note : scaling reads


-> caching (redis db) :
=> cache aside : if a cache miss then service will get value from db and put in
cache and send to frontend
=> read through : if a cache miss then cache will get value from db and put in
cache itself and send to service
-> a cache can handle 100k rps then we need 3 instance of caches
-> but the percentage of data in cache
i) 86 GB per day => if 3 instance of cache then each instance : 32 GB then we can
store entire data into cache for the day

=> do we need rdbms at all ?


-> yes we need for long term storage

note : feed optimization


-> if a user wants to fetch messages then they need to fetch messages of all users
they follow
-> a user has 100 followers => 100 req
=> but if we store data in cache in different way : all the tweets by followers of
user wil be stores together then it will we can fetch all tweets for user in 1 req
=> 100x optimization
=> problem : but now we hava cache with a structure sort of (s_id, u_id, tweet) :
if a user tweets then it will written number of follower times instead 1

note :
-> if a user has lot of followers
=> 10K tweets per second * 100 followers => 1000k writes to timelines per second
=> if 1M followers then 1M*10K writes to timelines (db writes)

note : so for vip, we can not use the feed optimization that's why we can make a
simple cache db to optimize the writes from that
-> a user fetches feeds of followers it follows and then the followers who are vips
from differenct cache

note : twitter system design


-> user -> post/tweets -> news feed (instance of service : > 1000 req per sec) ->
cache (normal user (replicate tweet for each follower) + vip storage) -> db (tweet
table + follower table)
-> followe -> get /tweets?limit=10 (each minute) -> fetch data from normal cache +
vip sotrage cache -> news feed
***********************************************************************************
***********************************************************************************
**********************

note : ticketing system design


-> number of venue : 1000
-> max number of tickets for a venue : 100 K
-> fetcival tickets (one time ; non frequent) / airplane, cinema tickets (frequent
and regular)
-> how to handle payments ?

note :
-> user select event (venue) to book ticket x number of tickets -> go to payment
portal -> fetch the ticket

note : db structure
-> events table : venue ; name ; date ; max_tickets
-> tickets : event_id, user_id, num_tickets

note : how to count number of current tickets sold


-> either we can store it as a col in events table or we can sum(num_tickets) in
tickets table (looks slower)
-> but for first solution if payment fails then we need to decrement the counter =>
we can add a status col in tickets table (reserved if booked but not paid, paid if
booked and paid)

note : estimates
-> store tickets for 5 years => 1 year forward and 4 year backward
=> max venue size * total venue * 365 * 5 = 200 B rows (uid: string, vid: string)
~ 1 record/row = 50 bytes => 10 TB data
=> we can do db sharding on the basis of veneues : each db : 1 TB data

note : scaling factors


-> number of people competing for a ticket : 100 people per ticket
-> total users : 10M
-> how quickly we expect to sell tickets : 30 minutes (number of req per seconds)
=> 10M user / 30 mins => ~6k req per second (6 instances of ticket microservice)
=> each one has to register the status as reserved => 6K writes that a db can
handle

***********************************************************************************
***********************************************************************************
**********************

You might also like