JustinDB - data versioning: Vector Clocks
Preface
JustinDB provides eventual consistency, which allows for updates to be propagated to all replicas asynchronously.
JustinDB has a tunable availability characteristic. It is highly available to serve requests, with the ability to tune its level of availability–nearing, but never quite reaching, strong consistency. As you may have read previous post about replication you know that every data is replicated to N distinct physical nodes (servers). Besides that it allows you to decide how many nodes must be written to or read from per request. These values are settings labeled N (the number of nodes to replicate to), R (the number of nodes read from before returning), and W (the number of nodes written to before considered successful).
Under certain failure scenarios (e.g. network partitions), updates may not arrive at all replicas for an extended period of time. Its worth to notice that updates in the presence of e.g. node failures can potentially result in an object having distinct version sub-histories, which the system will need to reconcile in the future.
JustinDB uses Vector Clock in order to capture causality between different versions of the same object.
Vector Clock
A vector clock is an algorithm for generating a partial ordering of events in a distributed system and detecting causality violations.
Sounds a bit confusing.
At it basis, Vector Clock just measures a sequence of events and keep a running history of updates. Its effectively a list of (node, counter) pairs. One vector clock is associated with every version of every objects.
This is how Vector Clock is modeled in JustinDB:
Lets illustrate vector clocks in action
Quick reminder: JustinDB at it basis can thought be as a typical key -> value
data structure.
We have 3 actors in our system: Luke, Han Solo and Leia. They try to order the food.
- Luke has decided to order sushi. Vector Clock now contains his name and the number of updates he’s performed.
key: food
vclock: {Luke: 1}
value: sushi
- Han Solo has got a message about an order but he doesn’t like Luke decision. He decided to update the order to take spaghetti instead.
key: food
vclock: {Luke: 1, Han Solo: 1}
value: spaghetti
- At the sime time as Han Solo, Leia comes along. She decides that sushi is a good idea (author: c’mon, it always is!) but she prefers to eat ramen.
key: food
vclock: {Luke: 1, Leia: 1}
value: ramen
-
We got a problem. Now we have two distinct vector clocks in play that diverge from
{Luke: 1}
. JustinDB store both values. -
Later in the day Han Solo checks again, but this time he gets two conflicts values, with two vector clocks.
key: food
vclock: {Luke: 1, Leia: 1}
value: ramen
--
vclock: {Luke: 1, Han Solo: 1}
value: spaghetti
Han Solo tries to solve this problem. He actually knows that Leia is a big fun of ramen so he decided to resolves the conflict choosing her option and providing new vector clock (sorry Luke…).
key: food
vclock: {Luke: 1, Leia: 1, Han Solo: 2}
value: ramen
- Now every subsequent request for food key will just return the agreed upon ramen.
Summary
In this part about Vector Clock we learned:
- what is it actually
- why do we need such technique in distributed system with enabled replication
- how to use it in real world
In next part I’m going to show how actually we use Vector Clock with JustinDB API:
- how Vector Clocks are generated for every stored objects
- how to update particular object
- how to update conflicted versions
- how system reason about causality of conflicted versions
Cheers! ✌️