When a server receives changes from different clients, how does the server know which change to accept and which to reject? If there is a conflict between two changes, who wins?
A single authoritative server
If you run a single server, you can easily just override data when it is updated, like this:
If you scale up to multiple servers you will need to keep track of the last update time so that you can determine which value to use when merging changes between them.
const shouldSet = (value, newValue) => newValue.timestamp > value.timestamp
This will work fine in most situations but will lead to inconsistencies if two servers write a change with the same timestamp. Depending on the order in which the servers received the change, they will end up with different values.
There are many methods of tie-breaking. Which you choose really depends on the application. Some of the most common tie-breaking methods are:
- Highest value
- Highest server id
- Create is preferenced over delete
- Delete is preferenced over create
In many cases, what you use for tie-breaking is arbitrary. What is most important is that there is consistency across all servers.
The addition of a tie-breaking method looks like this:
const shouldSet = (value, newValue) => newValue.timestamp > value.timestamp || (newValue.timestamp === value.timestamp && newValue.serverId > value.serverId)
If servers are ran by many different people, or changes are being applied while users are offline, you can't necessarily trust the timestamps. System clocks are inconsistent across devices and users can modify the system clock for malicious purposes.
In these situations we can use sequence numbers to determine which value to use. Each server will have a sequence number starting with 0.
When a local change is made, the sequence number will be incremented.
When a remote change is received, the server will check if version is higher than the one it has. If it is, it will use the new value.
const shouldSet = (value, newValue) => newValue.sequence > value.sequence || (newValue.sequence === value.sequence && newValue.serverId > value.serverId)
If you're more visually inclined, you can use the following diagram to visualize the algorithm.. On the left is an example of highest sequence number winning. The right is using the server id as a tie-breaker.
Learn how to create a map of last-writer-wins registers: lww-map .