Suppose I am designing a mobile application that requires both real-time collaboration and offline support. Instead of using a hybrid approach (directly interacting with the server when online and using a local database when offline), I am considering using a local database as the Single Source of Truth (SSOT).
Proposed Approach:
Local Database as SSOT:
The UI always reads data from the local database (e.g., using Flow from Room DAO).
Online Mode:
User actions update the local database first and asynchronously make an API call to update the server.
During a conflict, the server applies a resolution strategy (e.g., Last Write Wins, Operational Transformation) and sends the updated data.
WebSockets push real-time updates, ensuring the local database (and therefore the UI) stays in sync.
Offline Mode:
Changes are stored in the local database as well as in a another Sync DB table (to track pending actions).
A background worker (e.g., WorkManager) syncs pending changes from Sync DB table once the network is available.
Advantages I See:
✔ Consistent UI state since data always comes from the local database.
✔ Seamless offline support without handling different flows for online/offline.
✔ Reduced API load, as only necessary sync operations can be performed in batch.
✔ Backend handles conflict resolution, avoiding UI inconsistencies.
Potential Concerns:
❌ Latency in reflecting real-time updates since WebSocket changes must go through the local database first.
Questions:
Is this a scalable and maintainable approach for real-time applications?
Are there any downsides compared to a hybrid approach (directly interacting with the server when online)?
Would love to hear your insights!
This approach is generally the cleanest and easiest way to implement an offline-first app.
The main concen you seem to have is this:
Latency in reflecting real-time updates since WebSocket changes must go through the local database first.
Let's first look at the performance of WebSockets. WebSockets allow for an efficient bidirectional data exchange that reduces the overhead necessary to react to the previous message because it keeps the underlying TCP connection alive. This is the reason it is much faster than HTTP where you need to establish a new connection on every request.1
But the latency is still high. If the server is 20ms away, you still have a round trip time of over 40ms. For mobile devices you will often have a high variance in latency due to suboptimal signal reception, and a generally worse network quality so the average will pobably be much higher.
Your concern now is that adding the additional cost of writing the network data to a local database will further increase the latency, negatively impacting your app's performance. Well, it does add an additional overhead. If that is acceptable for your use case is something only you can decide. But keep in mind that we are taking about writing and reading data to and from the internal flash memory of the device. There is no slow bottleneck like the network involved and the RTT will usually be way below 1ms2, but that depends on a lot of other factors.
The performance of the hardware where your app will eventually run on and the load on the file system due to other apps or the Android system itself is out of your control. Some of the factors you can control are:
To answer your question, it all boils down to the specific performance requirements on the one hand and the performance optimizations you are willing/able to implement on the other. That's something only you can decide. My guess, though, is that the impact of writing to the database first is negligible. The additional cost it incurs is most likely just noise that is swallowed by the variance in network conditions.
Depending on how important the performance is for your use case you should actually run a benchmark. Measure the additional time it takes to write and read to the database on top of the time it takes for the WebSocket to deliver the data in various network conditions. Only then you can get a solid answer to what the actual impact it is. And then you can decide if that is acceptable for you.
1 As a tangent, that's also the reason why WebSockets don't work well in the background of an Android app because keeping the connection alive imposes a significant drain on the resources and the Android system may kill the app at any time to preserve battery life. A more efficient way is to use Firebase Cloud Messaging instead to push messages that wake your app only when needed. This is usually only helpful for tasks that would otherwise run in the background.
2 Maybe even faster than writing and reading the data by conventional means.