Design a notification system
Functional Requirements
- Users receive notifications as the events happen within the platform
- Users can subscribe to a channel to receive all relevant notifications
- Users can receive notifications in their respective client app (Mobile, web, integrations)
- Notification can be rate-limited
Non-Functional Requirements
- Always available
- Easy to add new clients
- Scalable: Linearly scalable
- What is the scale of the system?
- Average of 50k notifications a day
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
Could there be Some Questions — Answers?
What kind of notifications?
- Push notification, SMS, EMail
What have supported devices?
- IOS, Android, Laptop / Device
Design Constraints
- Average of 50K notifications a day.
Per notification would be 100 chars. Every char 2 Byte.
100 chars X 2 Byte = 200 Byte
And we need to store some metadata(Id, user_id, date, device_type etc. ) for notification , and we assume 100 bytes :
200 * 1000 = 20000 bayt
50.000 * 20000 Bayt = 10 ^ 8 = 1GB
Per day we need to store almost 1 GB of data in DB or Cache. Not too much data. We can store it in an RDBMS Database. And we can make a replica for DB. In the future, we can do “DB Sharding” for performance and scalability.
High-Level System Design
User Service
A user can register and log into our system.
We can store it in an RDBMS DB as a User table. Because we need some relation for users and other services.
User service can connect to Subscribe Service to subscribe to some channels.
API
POST /users/login
POST /users/register
POST /subscriptions/ {user_id: user_id}
GET /subscriptions/:user_id
Subscribe Service
Subscribe Service stores all channel information and subscription information.
When a user tries to reach this subscription service, we can hit it for the first time in a DB and we can store it in a cache.
Next time we can serve directly from Cache.
A user has many subscriptions.
A subscribe service has many notifications and we can define what kind of notification it gets from Notification Service.
API
GET /subscriptions/:user_id
GET /subscriptions/:id/notifications/
Notification Service
In this service, we can define some notification templates and types. Like some subscriptions might have just “TEXT MESSAGE” type, sometimes just “EMAIL” type. And all notification types should be a template. We can decide how our message will be seen in the client email box or client mobile phone using Template in this service.
API
POST /notifications/ {some relative data}
GET /notifications/:notification_id
Notification Generate Service — Workers
After some relationship between User — Subscription — Notification we can generate Notification using this service or workers.
Why workers? Because generating a notification template would take time. Specifically Email.
This service connects to the Tracking Service to update Notification Status.
Maybe we need to connect the Subscription Service to get some info and template from this service.
Workers can be Sidekiq Or any background job. We have to use a background job, because any client won’t be waiting to send an email or text for 1 minute, sometimes 4 minutes.
Tracking Service
We can store all notification statuses in this service. Like
Status :
- Pending
- Sent
- Delivered
- Receive
- Error
This service can get data from Generating and Sending services.
A client can get a notification status.
Notification Sending Service
This service includes some 3 parts providing company relationships.
We need to connect to the 3rd API service for sending this notification.
Like we can use for SMS sending a Twilio or for Push notification we can use OneSignal API.
These services provide the right way of sending our notifications.
Retry Mechanism
If a notification wasn’t delivered or got any error from 3rd vendors, we can add a queue again to the Notification generation workers. And we can hold a limit, retryable limit in our config. Like max retry is 3 times.
Rate Limiter
We can put a notification rate limiter to prevent or control notification send count.
Like a system can send notifications a total of 30 times in a minute per user.
Or we can send a maximum of 10 times on an android device or an email notification per minute.
High-Level System Design
- We can put a Rate Limiter Service between user service and Notification Generate Service.
- When we start generating a notification, we can check in a Rate Limiter algorithm, if this notification allowing to send we can send Notification Generate Service / Workers otherwise we can return an error HTTP status “429 — Too many requests”
- We can store it in Cache as key-value data.
- Key: user_id
- Value: {count, timestamp}
- If we need to store user process and rate limit params we can start a background job and this job will be written in NoSQL DB. We don’t need a direct connection between the Rate Limiter service and the DB. Why is NoSQL DB, because we have key, value data like bulky data. We don’t need to store a lot of relationships in an RDBMS database. NoSQL DB will be a better option to store data like this data.
Some Rate Limiter Algorithm
Alg 1
We can use Cache (key, value store) like Redis. Basically, we can calculate current time — last record time >= specific time.
If this case passes , we can check the second item , how many times we can send notification in this specific time.
Redis
key : user_id
value : {count: 1, start_time: 123123123}
If current_time — start_time <= 1 min and
- If ‘Count < 30’, increment the Count and allow the request.
- If ‘Count >= 30’, reject the request.
Alg 2
We can use the Redis Sorted Set key, value store. We can store the timestamp of each request in a Redis.
Like :
Key : user_id > 1
Value : Sortedtime <UnixTime> > {1231231233,123123456,234345634}
Assume 3 requests per minute per user:
- Remove all timestamps from the Sorted Set that are older than “Current_time — 1 minute”
- Count the total number of elements in the sorted set. Reject the request if this count greater than our limit.
- Insert the current time in the sorted set and accept the request.