0

I am currently trying to architecture an in-app chat feature with firebase realtime-db.
It should contain a UI view where the user can see its latest chats with an active connection to show latest updates.

My current data structure looks sth like this.

"users": [
   "UID1": {
      "name": "Rocky Balboa",
      "lastSeenAt": "2023-01-06T00:00Z",
      "chatRooms": ["CR1", "CR2"]
   }
]

"chatRooms": [
    "CR1": {
       "participants": [ "UID1", "UID2", ... ], 
       "lastMessage": {
          "id": "M2",
          "message": ":)",
          "createdBy": "UID2",
          "createdAt": "2023-01-06T00:00Z",
       }
    }
]

"messages": [
   "CR1": [
     "M1": {
       "message": "Adrian, I did it!",
       "createdBy": "UID1",
       "createdAt": "2023-01-05T00:00Z"
     }
     "M2": { ... }
   ]
]

The steps how I would process to do it in-app looks like this:

  1. Single fetch on /users/{UID1}/chatRooms to get participating chat room keys
  2. Multiple ValueEventListeners on chatRooms with fetched data from step 1.
    chatRooms/{CR1}, chatRooms/{CR2} ... and merge them into a Kotlin Flow

My concern now is that these multiple ValueEventListeners are inefficient or even costly.

My other option would be to delete the chatRooms base path and fan-out all recent changes to users chatRooms property with all required info like message etc.
Something like this

"users": [
   "UID1": {
      "name": "Rocky Balboa",
      "lastSeenAt": "2023-01-06T00:00Z",
      "chatRooms": [
         "CR1": {
           "participants": [ "UID1", "UID2", ... ], 
           "lastMessage": {
              "id": "M2",
              "message": ":)",
              "createdBy": "UID2",
              "createdAt": "2023-01-06T00:00Z",
           }
         }
      ]
   }
]

"messages": [
   "CR1": [
     "M1": {
       "message": "Adrian, I did it!",
       "createdBy": "UID1",
       "createdAt": "2023-01-05T00:00Z"
     }
     "M2": { ... }
   ]
]

But in this case I would have duplicated the data (which is not that bad per se since nosql) on every users item and have to update them everywhere. This effort also increases when there are more than 2 participants + will be ugly when db sharding is required. But it would make it possible to listen on multiple chat rooms in one event listener.

What would you prefer? Are my fears justified?

Ahmet K
  • 713
  • 18
  • 42

1 Answers1

1

Reading multiple pieces of data from Firebase Realtime Database is much faster than you may think, as the SDK pipelines the requests over a single web socket connection. To learn more about this, see Speed up fetching posts for my social network app by using query instead of observing a single event repeatedly

This means that you don't really pay extra for the first structure, neither in overhead nor in (bandwidth) cost. You do pay a bit in the cost of your code though, as loading this type of normalized data typically takes a bit more code than reading the denormalized equivalent.

On the other hand, writing the normalized data is typically simpler and faster than writing the denormalized equivalent.

So what you end up doing is trading write complexity and performance, for read complexity. This is a very common trade-off when dealing with NoSQL databases, and only you can make the call for your app and its use-cases.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807