Designing concurrently accessed data for a chat system

Hi,

I have implemented a chat system using NodeJS (cluster-master multiple processes)/MongoDB which works just fine. I have an issue though: when implementing Web Push, I need to send the current badge count/number to the client. Sadly there is no way of sending a silent push and the client deciding what the current badge count is.

I am storing the read information attached to every message. Additionally, I am storing the accumulated badge (unread) count in an object which has conversation ids as keys:

{
  "conversationId1": 10,
  "conversationId2": 5
  ...
}

This object is attached to the user and sent with the push message to the client. The client sends “mark as read” request to the server which marks the message as read and decrements the badge count. This breaks if multiple concurrent requests try to mark messages as read. As the server needs to fetch the current badge count in order to decrement it (no way of simply making an $inc findAndModify because of initially not existing keys/null values and constraining the badge count to 0 on decrement) and reads are not blocked during a transaction this leads to cases where two (node) processes read the same current value and the resulted decrement is not correct.

My question is: how do I model this? I thought of several options:

  • store the accumulated information in a separate collection with (userId, conversationId) identifying the conversation. I will still need to fetch the data on every push, which even with a compound key seems like an overkill
  • re-generate the badge count from the unread messages (which I do via aggregation, but only when the users first opens the page). This is seems like quite an overkill to be done on every push send. It’s why I store the aggregated/accumulated object in the first place.
  • quick workaround: implement a redis lock to lock the resource which is storing the aggregated value

Any idea how to accomplish this without sacrificing performance for consistency?

Cheers

Hi @Chris_Bernil and welcome to MongoDB community forums!!

Based on my understanding on the above information mentioned in the posts, you might first want to make the application to handle concurrent requests.
Since with every push operation, a read lock and with every “mark as read” a write lock would be required. To get started, the recommendation would to first visit the FAQ-Concurrency Control In MongoDB to understand how can you leverage the existing data model design to perform efficiently.

One another approach that you could follow would be using the Optimistic locking mechanism where the version control mechanism would be would help in dealing with the concurrent request by checking the version of the data updates and stored in the database. You can make use _ _version in Mongoose to save the document version and check before the read and write operation.

Having a separate collection could be a potential solution if this is not resulting into building one to many relationships which would involve multiple collection in one operation. Ideally, the flexibility that data model provides to make use of embedded documents would be enough to make the connections operations and would result into consistency of document as well.

If this seems like an option which works for you efficiently and helps in preventing the race conditions, this could be used.

Generally, best approach depends on the specific characteristics of your application and workload. It’s often a trade-off between consistency and performance.

Please reach out in case of further questions.

Regards
Aasawari

1 Like