Scalable and best way to get notifications not read count and mentions of a chat

Viewed 809

Imagine and application like Whatsapp that for each chat has a count of mentions and messages not read:

Whatsapp notifications

I want to implement a scalable system to handle notification count of an app. Here what I've think about possible solutions and their problems:

1) Create a counter for each user in each group collection and increase by 1 for each new message:

Problem: if I have chats with 500, 1000, 10000 users I will have to do 500, 1000, 10000 field updates.

Test: I've created a new collection with 50M of documents. Update time for 6000 users = 0.15 seconds. Update time for 100000 users = 14.2 seconds. It's not scalable.

Notifications Model: (compound index: roomId: 1, channelId: 1, userId: 1)

{
  roomId: string,
  channelId: string,
  userId: string,
  unread_messages: int,
  unread_mentions: int,
  last_read: date
}

2) Save the last message read from each user and when doing the initial data GET, count for each chat, from the last message read to the last, and limit it.

Problem: if you have 200 chats and you limit the number of notifications to 100 and it has been a while without logging into the application, you will have to count 100 * 200 rooms. When the "Count" operation is quite expensive for databases.

Test: I've counted 100 messages per chat and 200 chats = 8.4 seconds. Messages indexed by id and timestamp. A lot of time for client login.

3) Set up a PUB / SUB using for example ActiveMQ, RabbitMQ or Kafka, and for each chat create a queue.

Problem: You duplicate messages in the database and in queue/topics, in addition to being shared queues you would have to make queries if I am user X up to where I have read the last time and when you connect as a subscriber those messages are consumed and they are no longer available to other consumers. In kafka, if each topic it's a chat, I can't do a count of pending notifications without getting all pending messages and consuming them. So, if I consume this messages and I dont enter in a chat, there will be no notifications the next time I log in.

Can you think of any more options or are any of the ones I mentioned previously are scalable?

Thank you very much in advance.

2 Answers

In order to solve this, you can keep the count of written messages in every chat and the count of read messages for every user in every chat. Essentially, the difference between these numbers is the number of unread messages of a user for a specific chat.

Let' say there are 1000 online users, all in 100 chat rooms, 10 users active in each room and 990 inactive in each room. Each active user, all of a sudden writes one message in the chat. This will produce 1000 messages and only 1000 counts (10 per chat). Users which are inactive will only receive the new count for each chat, but their own count for read messages stays the same. For those active in a chat there is no count, since the number of their read messages will equal the count of the chat.

If one user is offline and enters online in one chat, he will get 10 messages and one update for the number of read messages. If he is enrolled on all 100 chats, he will get 1000 messages and 100 updates if he reads all.

If one user is online, but not active in any chat (app in the background), he will get the new count for every chat that is written into. Since there is a read message count for every chat in his profile, the client will have to do the math and display the difference.

This can be further optimised by letting the client do some work and update the backend with the number of read messages. This basically offloads the backend for half of the operations in the example above, so the effective number of operations done in the backend will be 1000.

Of course, there can be further optimisations done, like bidirectional asynchronous updates that are being sent at controlled time intervals or number of messages. This allows for both the client and the backend to send bulk notifications and control use of resources.

Given context you provided, I think solution 1) is perfectly viable, but decouple counter update from visualization and keep these info in memory.

Now imagine the following process:

  • application start
    • during start a separate thread is running, doing first counter (in 14.2 seconds, acceptable on start )
    • these information are loaded in some kind of in memory database ( for example redis ), for quick access -> this is your "user in memory notification counter cache" with a simple map (uid,[c]) where uid is userId and [c] is array of counter.
      • you can limit this map for each user, for example at max 255 chat/groups, otherwise your application need to compute and update/extend the map (like the limit you mentioned)
        • periodically you can "compact" this map and purge from memory unused counters ( each night, as example, or each 2 hours, depends on your requirements ) to keep memory on check and don't explode
  • user1 access to the application first time
    • application fire a request and get unread messages notifications from the cache ( in memory, so really quick)
  • user2 send a message to user1, now some scenarios:
    • user1 is not online ( app closed ), so a "slow" refresh for user1 (and only this) unread notification counter can be triggered to update the in memory cache ( and some seconds are acceptable )
    • user1 is online, chat is openend and messages is delivered. In this case counter cache doesn't require a refresh
    • user1 is online, but not in the chat specific chat, but for example in the chat list. I suppose some kind of trigger can be fired and ask for updated/refresh list of notification messages for the user, BUT for only the chat with user2, not for all --> I think this is the key, so you can update/refresh is on the app and in the in memory central cache

I think this will solve your problem, get more speed and quickness, but require:

  • application know the status for each user (online/offline) and store it for quick access ( another map in the in memory database maybe ? )
  • local user app know when a new message in a specific chat is available

I suppose this two requirements are already part of your system, for a "chat messaging platform kind-of"

Related