Muratatak
5 min readJul 3, 2021

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.
Muratatak
Muratatak

Written by Muratatak

Software Engineer, Rubyist, Follow Rails Way

No responses yet