I'm trying to understand the way the conflict resolution works in firebase for which I need some help.
Assuming that I've json object saved in a node in firebase realtime:
{
"shape": "rectangle",
"stroke": 10,
"color": "black"
}
I've defined a test page which reads this data and displays and also listens to the changes happening on node with key in realtime. I've added a provision to update the data which eventually updates the specific key value alone.
Sample used case
client 1 - loads the page
data - {"shape": "rectangle", "stroke": 10, "color": "black"}
client 2 - loads the page
data - {"shape": "rectangle", "stroke": 10, "color": "black"}
client 2 goes offline
client 2 updates stroke value to 20
data - {"shape": "rectangle", "stroke": 20, "color": "black"}
* data is yet to sync to the server
client 1 makes a change after client 2 has already done with its changes and changes stroke to 5
data - {"shape": "rectangle", "stroke": 5, "color": "black"}
* data gets synced to the server immediately
client 2 comes online and pushes its changes and overrides the changes made by client 1
data - {"shape": "rectangle", "stroke": 20, "color": "black"}
Ideally since the client 1 made the change at a later point of time than client 2 the client 1 changes should be retained when client 2 data gets synced.
I would be very glad if somebody can suggest me a way to such type of conflict resolution in firebase(may be by defining some rules and some extra logic).
With your current code, the expected behavior is indeed that the last write wins.
There are two other options:
Let's have a look at each in turn.
Using transactions is the most common way around this problem. When you use a transaction in Firebase, the client sends a "compare and set" operation to the server. This is an instruction of the type: "if the current value is A, set it to B". In your scenario that means that the second write detects that the stroke has already changed, so it gets a retry.
To learn more about transactions have a look at the Firebase documentation, and at my answer here about how they work.
This may sound like a great solution, but it does unfortunately affect the scalability of your code. The more users are trying to modify the same data, the more likely the transaction has to retry. That's why it's always good to consider if the conflict can be avoided altogether.
Preventing conflicts is the best conflict resolution strategy out there. By preventing conflicts, you never have to resolve them, which means you never have to write code to resolve conflicts, which means that your application will scale a lot better/farther.
To prevent conflicts, you'll want to look for a data structure where your users are always writing to unique locations. In your use case, instead of having each client update the stroke, you could instead have the client write their "update action" to a queue of updates. For example:
shapes
shapeid1
pushid1: {"shape": "rectangle", "stroke": 10, "color": "black"} /* initial data */
pushid2: { "stroke": 5 } /* first update */
pushid3: { "stroke": 20 } /* second update */
In this data structure, nobody is overwriting anyone else's data (something that is easy to enforce in security rules). Everyone is just appending new updates to the shape (using ref.push()
, which generates unique locations in chronological order).
To get the current data for a shape, each client will need to read all updates for that shape and recalculate them on the client. For most use-cases I've seen that's a simple operation, but if not: it is quite easy to have a Cloud Function calculate a periodic snapshot of the state.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With