How to handle eventual consistency?
What is CAP theorem?
CAP theorem states that it is impossible for a distributed data store to simultaneously provide more than two out of the following three guarantees:
- Consistency: Every read receives the most recent write or an error
- Availability: Every request receives a (non-error) response – without the guarantee that it contains the most recent write
- Partition tolerance: The system continues to operate despite an arbitrary number of messages being dropped (or delayed) by the network between nodes
Source: Wikipedia
In practice for distributed systems, we cannot compromise for Partition Tolerance, data records must be replicated across different nodes and networks to keep the system up through intermittent outages. Therefore we have to trade between consistency and availability.
What is eventual consistency?
Eventual consistency is a consistency model used in distributed computing to achieve high availability that informally guarantees that, if no new updates are made to a given data item, eventually all accesses to that item will return the last updated value.
Source: Wikipedia
We can achieve high availability of our database by increasing the replication of our database. But in any replicated database system, it is impossible to achieve strong consistency as the “writes” take time for the changes to be replicated on the other databases.
Different ways of handling eventual consistency
We can achieve high availability by creating many replicas of the database. We can have one replica ( Master ) where all the writes happen and the other replicas ( Slaves ) which are used for reading values. There is going to be a time frame between when data is updated in the write model and the changes are updated in the read models during which we will get stale data. Let’s figure out how to handle this eventual consistency.
Read your own writes
We can return the resultant read model from the write database once the command has executed successfully. The problem with this approach is that you will have redundant code to read values from the database in the “Read Service” and the “Write Service”.
Poll the read model
Once we have written the data, we can poll the read models to make sure that the changes have been reflected and then return success.
The problems with this approach:
- Additional latency is caused due to the polling read models
- If we poll too frequently we could add load to the database
- If we poll less frequently we will add wait time even after the read model has been updated
Pub/Sub for read model changes
We can use pub/sub model on the read model which notifies it’s subscribers after it has processed the events. The advantage is that we don’t need to poll the read model thereby not adding additional load and we can complete the request immediately after the updates have been applied to the read models. A disadvantage of this approach is that we have to have additional code to support publishing notifications
Strongly consistent command dispatch
We can classify requests as strongly consistent and eventually consistent. The less important data can be eventually consistent and important/significant data can be ensured to be strongly consistent. The strong consistency can be achieved by ensuring that the data has been replicated in the read models before completing the request.
Using version number
Every command should return a version number that could be a function value based on the timestamp. The client then queries the read model for data with version equal to or greater than this version. If the read model’s version is less than the required version, we can set “Retry-After” header to ensure that the browser retries the request after the set time.