SlideShare a Scribd company logo
LIVING IN EVENTUALLY
CONSISTENT REALITY
INTRODUCTION
Bartosz Sypytkowski
▪ @Horusiath
▪ b.sypytkowski@gmail.com
▪ bartoszsypytkowski.com
 Eventual consistency with CRDTs
 Existing solutions
 CRDT – basics and optimizations
 Different notions of time
AGENDA
CASE STUDY
YouTube v2.0
VIDEO
STREAMING
Alice
VIDEO
STREAMING
Alice
VIDEO
STREAMING
Alice
VIDEO
STREAMING
Alice
SINGLE WRITER
REPLICATION
LET’S ADD A VIDEO VIEW COUNTER
PAGE VIEWS
MULTI-MASTER
REPLICATION
CAN WE SYNCHRONIZE DATA SAFELY WITHOUT A NEED
FOR CONSENSUS?
CONFLICT-FREE REPLICATED DATA TYPES
State
based
State
delta
based
Operation
based
Pure
operation
based
Simple
protocol
Complex
structures
Complex
protocol
Simple
structures
USE CASES
1. Sync data over the network with large
latencies
2. Sync data between periodically
disconnected devices
3. Navigation
4. Chat applications
5. Collaborative text editing
6. Mobile advertising
7. Edge computing
CRDT IN
THE WILD
Riak database operation based
AntidoteDB database operation based
Amazon DynamoDB database
Azure CosmosDB database (multi-master) custom (state based)
Redis CRDB database state based
Lasp Erlang library delta/state based
Akka.DistributedData JVM/.NET library delta/state based
Eventuate JVM library operation based
Roshi Go library state based
Automerge Javascript library operation based
STATE BASED CRDT
101
LEADER
FOLLOWER
REPLICATION
READS
Leader
Follower Follower
LEADER
FOLLOWER
REPLICATION
WRITES
Leader
Follower Follower
LEADER
FOLLOWER
REPLICATION
WRITES
Leader
Follower Follower
LEADER
FOLLOWER
REPLICATION
WRITES
Leader
Follower Follower
ROUND TRIP
TIMES
Source: https://speakerdeck.com/ept/local-first-software-returning-data-ownership-to-users
COMPAR
ING
VECTOR
CLOCKS
MASTERLESS
REPLICATION
COMPAR
ING
VECTOR
CLOCKS
MASTERLESS
REPLICATION
COMPAR
ING
VECTOR
CLOCKS
MASTERLESS
REPLICATION
WE DON’T NEED CONSENSUS, IF INDIVIDUALLY WE
ALWAYS REACH THE SAME CONCLUSION
CONVERGENCE
1. Commutative: x • y = y • x
2. Associative: (x • y) • z = x • (y • z)
3. Idempotent: x • x = x
HOW TO KEEP
THINGS IN SYNC
CASE #1
DISTRIBUTED VIEW COUNTER
G-COUNTER
GROWING ONLY
COUNTER
const GCounter = {
empty() {
return {};
},
increment(counter, id) {
counter[id] = (counter[id] || 0) + 1;
},
value(counter) {
return Object.values(counter).reduce((sum, x) => sum + x, 0);
},
merge(existing, incoming) {
Object.keys(incoming).forEach(id => {
const incomingVal = incoming[id];
const existingVal = existing[id] || 0;
existing[id] = Math.max(incomingVal, existingVal);
});
}
};
G-COUNTER
A 2
B 1
C 1
VALUE
G-COUNTER
A 2
B 1
C 1
VALUE
(2 + 1 + 1) => 4
G-COUNTER
GROWING ONLY
COUNTER
const GCounter = {
empty() {
return {};
},
increment(counter, id) {
counter[id] = (counter[id] || 0) + 1;
},
value(counter) {
return Object.values(counter).reduce((sum, x) => sum + x, 0);
},
merge(existing, incoming) {
Object.keys(incoming).forEach(id => {
const incomingVal = incoming[id];
const existingVal = existing[id] || 0;
existing[id] = Math.max(incomingVal, existingVal);
});
}
};
G-COUNTER
A 1
B 3
A 2
B 1
C 1
MAX
MERGE
G-COUNTER
A 1
B 3
A 2
B 1
C 1
A 2
B 3
C 1
MAX(1, 2)
MAX
MAX(3, 1)
MAX(0, 1)
MERGE
G-COUNTER
GROWING ONLY
COUNTER
const GCounter = {
empty() {
return {};
},
increment(counter, id) {
counter[id] = (counter[id] || 0) + 1;
},
value(counter) {
return Object.values(counter).reduce((sum, x) => sum + x, 0);
},
merge(existing, incoming) {
Object.keys(incoming).forEach(id => {
const incomingVal = incoming[id];
const existingVal = existing[id] || 0;
existing[id] = Math.max(incomingVal, existingVal);
});
}
};
CASE #2
DISTRIBUTED “LIKE” COUNTER
PN-COUNTER
POSITIVE
NEGATIVE
COUNTER
const PNCounter = {
empty() {
return { inc: GCounter.empty(), dec: GCounter.empty() };
},
increment(counter, id) {
GCounter.increment(counter.inc, id);
},
decrement(counter, id) {
GCounter.increment(counter.dec, id);
},
value(counter) {
return GCounter.value(counter.inc) - GCounter.value(counter.dec);
},
merge(existing, incoming) {
GCounter.merge(existing.inc, incoming.inc);
GCounter.merge(existing.dec, incoming.dec);
}
};
PN-COUNTER
POSITIVE
NEGATIVE
COUNTER
const PNCounter = {
empty() {
return { inc: GCounter.empty(), dec: GCounter.empty() };
},
increment(counter, id) {
GCounter.increment(counter.inc, id);
},
decrement(counter, id) {
GCounter.increment(counter.dec, id);
},
value(counter) {
return GCounter.value(counter.inc) - GCounter.value(counter.dec);
},
merge(existing, incoming) {
GCounter.merge(existing.inc, incoming.inc);
GCounter.merge(existing.dec, incoming.dec);
}
};
CASE #3
VOTING SYSTEM – SURVEY PARTICIPATION TRACKING
G-SET
GROWING ONLY
SET
const GSet = {
empty() {
return {};
},
add(set, item) {
set[item] = 1;
},
value(set) {
return Object.keys(set);
},
merge(existing, incoming) {
Object.keys(incoming).forEach(item => existing[item] = 1);
}
};
G-SET
GROWING ONLY
SET
const GSet = {
empty() {
return {};
},
add(set, item) {
set[item] = 1;
},
value(set) {
return Object.keys(set);
},
merge(existing, incoming) {
Object.keys(incoming).forEach(item => existing[item] = 1);
}
};
SET UNION
CASE #4
DISTRIBUTED SHOPPING CART
G-MAP
+
PN-COUNTER
const ORCart = {
empty() {
return {};
},
add(cart, item, id) {
var counter = cart[item] || PNCounter.empty();
PNCounter.increment(counter, id);
cart[item] = counter;
},
remove(cart, item, id) {
var counter = cart[item] || PNCounter.empty();
PNCounter.decrement(counter, id);
cart[item] = counter;
},
value(cart) {
return Object.keys(cart).reduce((result, item) => {
result[item] = PNCounter.value(cart[item]);
return result;
}, {});
},
merge(existing, incoming) {
Object.keys(incoming).forEach(item => {
const x = existing[item] || PNCounter.empty();
const y = incoming[item];
PNCounter.merge(x, y);
existing[item] = x;
});
}
};
G-MAP
+
PN-COUNTER
const ORCart = {
empty() {
return {};
},
add(cart, item, id) {
var counter = cart[item] || PNCounter.empty();
PNCounter.increment(counter, id);
cart[item] = counter;
},
remove(cart, item, id) {
var counter = cart[item] || PNCounter.empty();
PNCounter.decrement(counter, id);
cart[item] = counter;
},
value(cart) {
return Object.keys(cart).reduce((result, item) => {
result[item] = PNCounter.value(cart[item]);
return result;
}, {});
},
merge(existing, incoming) {
Object.keys(incoming).forEach(item => {
const x = existing[item] || PNCounter.empty();
const y = incoming[item];
PNCounter.merge(x, y);
existing[item] = x;
});
}
};
G-MAP
+
PN-COUNTER
const ORCart = {
empty() {
return {};
},
add(cart, item, id) {
var counter = cart[item] || PNCounter.empty();
PNCounter.increment(counter, id);
cart[item] = counter;
},
remove(cart, item, id) {
var counter = cart[item] || PNCounter.empty();
PNCounter.decrement(counter, id);
cart[item] = counter;
},
value(cart) {
return Object.keys(cart).reduce((result, item) => {
result[item] = PNCounter.value(cart[item]);
return result;
}, {});
},
merge(existing, incoming) {
Object.keys(incoming).forEach(item => {
const x = existing[item] || PNCounter.empty();
const y = incoming[item];
PNCounter.merge(x, y);
existing[item] = x;
});
}
};
G-MAP
+
PN-COUNTER
const ORCart = {
empty() {
return {};
},
add(cart, item, id) {
var counter = cart[item] || PNCounter.empty();
PNCounter.increment(counter, id);
cart[item] = counter;
},
remove(cart, item, id) {
var counter = cart[item] || PNCounter.empty();
PNCounter.decrement(counter, id);
cart[item] = counter;
},
value(cart) {
return Object.keys(cart).reduce((result, item) => {
result[item] = PNCounter.value(cart[item]);
return result;
}, {});
},
merge(existing, incoming) {
Object.keys(incoming).forEach(item => {
const x = existing[item] || PNCounter.empty();
const y = incoming[item];
PNCounter.merge(x, y);
existing[item] = x;
});
}
};
CASE #5
TWITTER - LIST OF FOLLOWERS
G-SET
GROWING ONLY
SET
1. Can only grow
2. No (safe) semantics for removing values
CAN WE COMPOSE G-SETS INTO REMOVE-AWARE SET?
2P-SET
TWO PHASE SET
const TwoPhaseSet = {
empty() {
return { add: GSet.empty(), rem: GSet.empty() };
},
add(set, item) {
GSet.add(set.add, item);
},
remove(set, item) {
GSet.add(set.rem, item);
},
value(set) {
return Object.keys(set.add).reduce((result, item) => {
if (!set.rem[item]) {
result.push(item);
}
return result;
}, []);
},
merge(existing, incoming) {
GSet.merge(existing.add, incoming.add);
GSet.merge(existing.rem, incoming.rem);
}
};
2P-SET
TWO PHASE SET
const TwoPhaseSet = {
empty() {
return { add: GSet.empty(), rem: GSet.empty() };
},
add(set, item) {
GSet.add(set.add, item);
},
remove(set, item) {
GSet.add(set.rem, item);
},
value(set) {
return Object.keys(set.add).reduce((result, item) => {
if (!set.rem[item]) {
result.push(item);
}
return result;
}, []);
},
merge(existing, incoming) {
GSet.merge(existing.add, incoming.add);
GSet.merge(existing.rem, incoming.rem);
}
};
2P-SET
ISSUES
1. Once removed, element cannot be
reinserted.
2. Requires tombstones to be always
present.
COMPAR
ING
VECTOR
CLOCKS
OBSERVED
REMOVE SET
A:T1
B:T2
ADDREM
timeline
VALUE
[ A, B ]
COMPAR
ING
VECTOR
CLOCKS
OBSERVED
REMOVE SET
A:T1
B:T2
ADDREM
Insert
C:T3
timeline
VALUE
[ A, B ]
COMPAR
ING
VECTOR
CLOCKS
OBSERVED
REMOVE SET
A:T1
B:T2
ADDREM
Insert
C:T3
A:T1
B:T2
C:T3
ADDREM
timeline
VALUE
[ A, B, C ]
COMPAR
ING
VECTOR
CLOCKS
OBSERVED
REMOVE SET
A:T1
B:T2
ADDREM
Remove
C:T4
A:T1
B:T2
C:T3
ADDREM
timeline
VALUE
[ A, B, C ]
COMPAR
ING
VECTOR
CLOCKS
OBSERVED
REMOVE SET
A:T1
B:T2
ADDREM
Remove
C:T4
A:T1
B:T2
C:T3
ADDREM
timeline
A:T1
B:T2
ADD
C:T4
REM
VALUE
[ A, B ]
COMPAR
ING
VECTOR
CLOCKS
OBSERVED
REMOVE SET
A:T1
B:T2
ADDREM
A:T1
B:T2
C:T3
ADDREM
timeline
A:T1
B:T2
ADD
C:T4
REM
Insert
C:T5
VALUE
[ A, B ]
COMPAR
ING
VECTOR
CLOCKS
OBSERVED
REMOVE SET
A:T1
B:T2
ADDREM
A:T1
B:T2
C:T3
ADDREM
timeline
A:T1
B:T2
ADD
C:T4
REM
Insert
C:T5
A:T1
B:T2
C:T5
ADDREM
VALUE
[ A, B, C ]
LAST WRITE
WINS SET
const LWWSet = {
empty() {
return { add: {}, rem: {} };
},
add(set, item) {
set.add[item] = (new Date()).getTime();
set.rem[item] = undefined;
},
remove(set, item) {
set.add[item] = undefined;
set.rem[item] = (new Date()).getTime();
},
value(set) {
return Object.keys(set.add).reduce((result, item) => {
const addedAt = set.add[item];
const removedAt = set.rem[item] || 0;
if (addedAt >= removedAt) {
result.push(item);
}
return result;
}, []);
},
merge(existing, incoming) {
function partialMerge(a, b) {
Object.keys(b).forEach(item => {
a[item] = Math.max(b[item], a[item] || 0);
});
}
partialMerge(existing.add, incoming.add);
partialMerge(existing.rem, incoming.rem);
}
};
LAST WRITE
WINS SET
const LWWSet = {
empty() {
return { add: {}, rem: {} };
},
add(set, item) {
set.add[item] = (new Date()).getTime();
set.rem[item] = undefined;
},
remove(set, item) {
set.add[item] = undefined;
set.rem[item] = (new Date()).getTime();
},
value(set) {
return Object.keys(set.add).reduce((result, item) => {
const addedAt = set.add[item];
const removedAt = set.rem[item] || 0;
if (addedAt >= removedAt) {
result.push(item);
}
return result;
}, []);
},
merge(existing, incoming) {
function partialMerge(a, b) {
Object.keys(b).forEach(item => {
a[item] = Math.max(b[item], a[item] || 0);
});
}
partialMerge(existing.add, incoming.add);
partialMerge(existing.rem, incoming.rem);
}
};
LAST WRITE
WINS SET
const LWWSet = {
empty() {
return { add: {}, rem: {} };
},
add(set, item) {
set.add[item] = (new Date()).getTime();
set.rem[item] = undefined;
},
remove(set, item) {
set.add[item] = undefined;
set.rem[item] = (new Date()).getTime();
},
value(set) {
return Object.keys(set.add).reduce((result, item) => {
const addedAt = set.add[item];
const removedAt = set.rem[item] || 0;
if (addedAt >= removedAt) {
result.push(item);
}
return result;
}, []);
},
merge(existing, incoming) {
function partialMerge(a, b) {
Object.keys(b).forEach(item => {
a[item] = Math.max(b[item], a[item] || 0);
});
}
partialMerge(existing.add, incoming.add);
partialMerge(existing.rem, incoming.rem);
}
};
SYSTEM
CLOCK
TIMESTAMP
1. Requires clocks to be in sync.
2. Doesn’t say anything about concurrent
updates.
VECTOR CLOCKS
TO THE RESCUE
COMPAR
ING
VECTOR
CLOCKS
PARTIAL
ORDERING
Equals
A 2
B 3
C 1
A 2
B 3
C 1
=
=
=
COMPAR
ING
VECTOR
CLOCKS
PARTIAL
ORDERING
Equals Greater than
A 2
B 3
C 1
A 2
B 3
C 1
A 2
B 3
C 2
A 2
B 3
C 1
=
=
=
=
=
>
COMPAR
ING
VECTOR
CLOCKS
PARTIAL
ORDERING
Equals Greater than
Less than
A 2
B 3
C 1
A 2
B 3
C 1
A 2
B 3
C 2
A 2
B 3
C 1
A 2
B 2
C 1
A 2
B 3
C 1
=
=
=
=
=
>
=
<
=
COMPAR
ING
VECTOR
CLOCKS
PARTIAL
ORDERING
Equals Greater than
Less than Concurrent
A 2
B 3
C 1
A 2
B 3
C 1
A 2
B 3
C 2
A 2
B 3
C 1
A 2
B 2
C 1
A 2
B 3
C 1
A 1
B 3
C 1
A 2
B 2
C 1
=
=
=
=
=
>
=
<
=
<
>
=
VECTOR
CLOCK
const VectorClock = {
merge: GCounter.merge,
increment: GCounter.increment,
compare(a, b) {
function partialCompare(result, id) {
if (result === null) return result;
const aval = a[id] || 0;
const bval = b[id] || 0;
if (aval > bval) {
if (result === -1) return null;
else return 1;
} else if (aval < bval) {
if (result === 1) return null;
else return -1;
} else return result;
}
var result = Object.keys(a).reduce(partialCompare, 0);
return Object.keys(b).reduce(partialCompare, result);
}
};
ADD-WINS
OBSERVED
REMOVE SET
const AWORSet = {
empty() {
return { add: {}, rem: {} };
},
add(set, item, id) {
var clock = set.add[item] || set.rem[item] || {};
VectorClock.increment(clock, id);
set.rem[item] = undefined;
set.add[item] = clock;
},
remove(set, item, id) {
var clock = set.add[item] || set.rem[item] || {};
VectorClock.increment(clock, id);
set.add[item] = undefined;
set.rem[item] = clock;
},
value(set) {
return Object.keys(set.add).reduce((result, item) => {
var addClock = set.add[item] || {};
var remClock = set.rem[item] || {};
if (VectorClock.compare(addClock, remClock) !== -1) {
result.push(item);
}
return result;
}, []);
},
merge(existing, incoming) {
function partialMerge(a, b) {
Object.keys(b).forEach(item => {
var aclock = a[item] || {};
var bclock = b[item] || {};
VectorClock.merge(aclock, bclock);
a[item] = aclock;
});
}
partialMerge(existing.add, incoming.add);
partialMerge(existing.rem, incoming.rem);
}
};
ADD-WINS
OBSERVED
REMOVE SET
const AWORSet = {
empty() {
return { add: {}, rem: {} };
},
add(set, item, id) {
var clock = set.add[item] || set.rem[item] || {};
VectorClock.increment(clock, id);
set.rem[item] = undefined;
set.add[item] = clock;
},
remove(set, item, id) {
var clock = set.add[item] || set.rem[item] || {};
VectorClock.increment(clock, id);
set.add[item] = undefined;
set.rem[item] = clock;
},
value(set) {
return Object.keys(set.add).reduce((result, item) => {
var addClock = set.add[item] || {};
var remClock = set.rem[item] || {};
if (VectorClock.compare(addClock, remClock) !== -1) {
result.push(item);
}
return result;
}, []);
},
merge(existing, incoming) {
function partialMerge(a, b) {
Object.keys(b).forEach(item => {
var aclock = a[item] || {};
var bclock = b[item] || {};
VectorClock.merge(aclock, bclock);
a[item] = aclock;
});
}
partialMerge(existing.add, incoming.add);
partialMerge(existing.rem, incoming.rem);
}
};
ADD-WINS
OBSERVED
REMOVE SET
const AWORSet = {
empty() {
return { add: {}, rem: {} };
},
add(set, item, id) {
var clock = set.add[item] || set.rem[item] || {};
VectorClock.increment(clock, id);
set.rem[item] = undefined;
set.add[item] = clock;
},
remove(set, item, id) {
var clock = set.add[item] || set.rem[item] || {};
VectorClock.increment(clock, id);
set.add[item] = undefined;
set.rem[item] = clock;
},
value(set) {
return Object.keys(set.add).reduce((result, item) => {
var addClock = set.add[item] || {};
var remClock = set.rem[item] || {};
if (VectorClock.compare(addClock, remClock) !== -1) {
result.push(item);
}
return result;
}, []);
},
merge(existing, incoming) {
function partialMerge(a, b) {
Object.keys(b).forEach(item => {
var aclock = a[item] || {};
var bclock = b[item] || {};
VectorClock.merge(aclock, bclock);
a[item] = aclock;
});
}
partialMerge(existing.add, incoming.add);
partialMerge(existing.rem, incoming.rem);
}
};
ADD-WINS
OBSERVED
REMOVE SET
const AWORSet = {
empty() {
return { add: {}, rem: {} };
},
add(set, item, id) {
var clock = set.add[item] || set.rem[item] || {};
VectorClock.increment(clock, id);
set.rem[item] = undefined;
set.add[item] = clock;
},
remove(set, item, id) {
var clock = set.add[item] || set.rem[item] || {};
VectorClock.increment(clock, id);
set.add[item] = undefined;
set.rem[item] = clock;
},
value(set) {
return Object.keys(set.add).reduce((result, item) => {
var addClock = set.add[item] || {};
var remClock = set.rem[item] || {};
if (VectorClock.compare(addClock, remClock) !== -1) {
result.push(item);
}
return result;
}, []);
},
merge(existing, incoming) {
function partialMerge(a, b) {
Object.keys(b).forEach(item => {
var aclock = a[item] || {};
var bclock = b[item] || {};
VectorClock.merge(aclock, bclock);
a[item] = aclock;
});
}
partialMerge(existing.add, incoming.add);
partialMerge(existing.rem, incoming.rem);
}
};
Overhead:
Nr. of nodes * (Key size + seq. nr.)
Examples:
3 × (4 + 8) = 36 bytes
10 × (16 + 8) = 240 bytes
WALL CLOCK
TIMESTAMP
Overhead:
8 bytes
VECTOR CLOCKS
DELTAS
COMPRESSING THE PAYLOAD
COMPAR
ING
VECTOR
CLOCKS
DELTAS
A 2
B 3
C 1
State
(G-Counter)
COMPAR
ING
VECTOR
CLOCKS
DELTAS
A 2
B 3
C 1
State
(G-Counter)
INC(B)
COMPAR
ING
VECTOR
CLOCKS
DELTAS
A 2
B 3
C 1
State
(G-Counter)
INC(B)
A 2
B 4
C 1
New State
(G-Counter)
COMPAR
ING
VECTOR
CLOCKS
DELTAS
A 2
B 3
C 1
State
(G-Counter)
INC(B)
A 2
B 4
C 1
New State
(G-Counter)
B 4
Delta
(G-Counter)
DELTA
STATE CRDT
1. Propagate only change set of the state
after update.
2. Make sure that all changes have been
propagated and received.
APPENDIX #1
ENFORCING CONSISTENCY
ENFORCING
CONSISTENCY
WRITES
Alice
X1
X1
X1
X1 X1
X2
ENFORCING
CONSISTENCY
WRITES
Alice
Replicate write
to majority of
replicas
X2
X1
X1
X1 X1
ENFORCING
CONSISTENCY
WRITES
Alice
ACK
ACK
X2
X2
X1
X2 X1
ENFORCING
CONSISTENCY
WRITES
Alice
ACK
X2
X2
X1
X2 X1
ENFORCING
CONSISTENCY
READS
Bob
X2
X2
X1
X2 X1
ENFORCING
CONSISTENCY
READS
Bob
Request reads
from majority of
replicas
X2
X2
X1
X2 X1
ENFORCING
CONSISTENCY
READS
Bob
Request reads
from majority of
replicas
X2 X1
X2 X1
X1
X2
X2
ENFORCING
CONSISTENCY
READS
Bob
X2
X2
X2 X1
X2
X2
APPENDIX #2
DOTTED VERSION VECTORS
DOT
A:1REPLICA ID SEQUENCE NR.
DOTTED
VERSION
VECTORS
DOTTED
VERSION
VECTORS
REPRESENTATION
D 4
C 5
B 7
A 5
D 8
C 10
C 11
A 9
vector clock dot cloud
+
OR-SET
UNOPTIMIZED
VERSION “banana”
A
1
“apple”
A B
2 1
“carrot”
A B C
1 2 1
“pear”
A B C D
2 2 1 1
ADD
“pear”
A B C D
1 2 3 1
“strawberry”
A B C D
2 2 4 1
REM
ORSet
OR-SET
WITHOUT
TOMBSTONES
D 1
C 1
B 2
A 2
C 3
C 4
vector clock dot cloud
+
“banana”
A
1
“apple”
A
2
“carrot”
B
2
“pear”
D
1
+
active set
OR-SET
TRACKING
REMOVED VALUES
D 1
C 1
B 2
A 2
C 3
C 4
vector clock dot cloud
+
“banana”
A
1
“apple”
A
2
“carrot”
B
2
“pear”
D
1
+
active set
(C:3) DOT FROM
REMOVED OBJECT
dot context
1. JSON-like CRDTs
2. Distributed transactions
a. RAMP
b. CURE
WHAT’S NEXT?
SUMMARY
 Consistency without consensus: https://www.infoq.com/presentations/crdt-soundcloud
 CRDTs and the Quest for Distributed Consistency: https://www.youtube.com/watch?v=B5NULPSiOGw
 CRDT blog posts: https://bartoszsypytkowski.com/tag/crdt/
 CRDT examples in F#: https://github.com/Horusiath/crdt-examples/
 Azure CosmosDB custom conflict resolution: https://docs.microsoft.com/en-us/azure/cosmos-db/how-
to-manage-conflicts#create-a-custom-conflict-resolution-policy-using-a-stored-procedure
 Redis Enterprise CRDB: https://docs.redislabs.com/latest/rs/administering/database-operations/create-
crdb/
REFERENCES
THANK YOU

More Related Content

Similar to Living in eventually consistent reality

Kamil Chmielewski, Jacek Juraszek - "Hadoop. W poszukiwaniu złotego młotka."
Kamil Chmielewski, Jacek Juraszek - "Hadoop. W poszukiwaniu złotego młotka."Kamil Chmielewski, Jacek Juraszek - "Hadoop. W poszukiwaniu złotego młotka."
Kamil Chmielewski, Jacek Juraszek - "Hadoop. W poszukiwaniu złotego młotka."
sjabs
 
Powering Heap With PostgreSQL And CitusDB (PGConf Silicon Valley 2015)
Powering Heap With PostgreSQL And CitusDB (PGConf Silicon Valley 2015)Powering Heap With PostgreSQL And CitusDB (PGConf Silicon Valley 2015)
Powering Heap With PostgreSQL And CitusDB (PGConf Silicon Valley 2015)
Dan Robinson
 
Receipt processing with Google Cloud Platform and the Google Assistant
Receipt processing with Google Cloud Platform and the Google AssistantReceipt processing with Google Cloud Platform and the Google Assistant
Receipt processing with Google Cloud Platform and the Google Assistant
Orestes Carracedo
 
Lo Mejor Del Pdc2008 El Futrode C#
Lo Mejor Del Pdc2008 El Futrode C#Lo Mejor Del Pdc2008 El Futrode C#
Lo Mejor Del Pdc2008 El Futrode C#
Juan Pablo
 
Domain Driven Design
Domain Driven DesignDomain Driven Design
Domain Driven Design
Pascal Larocque
 
Compose Async with RxJS
Compose Async with RxJSCompose Async with RxJS
Compose Async with RxJS
Kyung Yeol Kim
 
Community-driven Language Design at TC39 on the JavaScript Pipeline Operator ...
Community-driven Language Design at TC39 on the JavaScript Pipeline Operator ...Community-driven Language Design at TC39 on the JavaScript Pipeline Operator ...
Community-driven Language Design at TC39 on the JavaScript Pipeline Operator ...
Igalia
 
A head start on cloud native event driven applications - bigdatadays
A head start on cloud native event driven applications - bigdatadaysA head start on cloud native event driven applications - bigdatadays
A head start on cloud native event driven applications - bigdatadays
Sriskandarajah Suhothayan
 
MongoDB World 2018: Keynote
MongoDB World 2018: KeynoteMongoDB World 2018: Keynote
MongoDB World 2018: Keynote
MongoDB
 
JavaScript Objects and OOP Programming with JavaScript
JavaScript Objects and OOP Programming with JavaScriptJavaScript Objects and OOP Programming with JavaScript
JavaScript Objects and OOP Programming with JavaScript
Laurence Svekis ✔
 
Presto in Treasure Data (presented at db tech showcase Sapporo 2015)
Presto in Treasure Data (presented at db tech showcase Sapporo 2015)Presto in Treasure Data (presented at db tech showcase Sapporo 2015)
Presto in Treasure Data (presented at db tech showcase Sapporo 2015)
Mitsunori Komatsu
 
Map/reduce, geospatial indexing, and other cool features (Kristina Chodorow)
Map/reduce, geospatial indexing, and other cool features (Kristina Chodorow)Map/reduce, geospatial indexing, and other cool features (Kristina Chodorow)
Map/reduce, geospatial indexing, and other cool features (Kristina Chodorow)MongoSF
 
Constance et qualité du code dans une équipe - Rémi Prévost
Constance et qualité du code dans une équipe - Rémi PrévostConstance et qualité du code dans une équipe - Rémi Prévost
Constance et qualité du code dans une équipe - Rémi Prévost
Web à Québec
 
JS Fest 2019. Anjana Vakil. Serverless Bebop
JS Fest 2019. Anjana Vakil. Serverless BebopJS Fest 2019. Anjana Vakil. Serverless Bebop
JS Fest 2019. Anjana Vakil. Serverless Bebop
JSFestUA
 
C Programming :- An Example
C Programming :- An Example C Programming :- An Example
C Programming :- An Example
Atit Gaonkar
 
bbyopenApp_Code.DS_StorebbyopenApp_CodeVBCodeGoogleMaps.docx
bbyopenApp_Code.DS_StorebbyopenApp_CodeVBCodeGoogleMaps.docxbbyopenApp_Code.DS_StorebbyopenApp_CodeVBCodeGoogleMaps.docx
bbyopenApp_Code.DS_StorebbyopenApp_CodeVBCodeGoogleMaps.docx
ikirkton
 
java experiments and programs
java experiments and programsjava experiments and programs
java experiments and programs
Karuppaiyaa123
 
BLoC - Be Reactive in flutter
BLoC - Be Reactive in flutterBLoC - Be Reactive in flutter
BLoC - Be Reactive in flutter
Giacomo Ranieri
 
Cnam azure 2014 mobile services
Cnam azure 2014   mobile servicesCnam azure 2014   mobile services
Cnam azure 2014 mobile services
Aymeric Weinbach
 

Similar to Living in eventually consistent reality (20)

Kamil Chmielewski, Jacek Juraszek - "Hadoop. W poszukiwaniu złotego młotka."
Kamil Chmielewski, Jacek Juraszek - "Hadoop. W poszukiwaniu złotego młotka."Kamil Chmielewski, Jacek Juraszek - "Hadoop. W poszukiwaniu złotego młotka."
Kamil Chmielewski, Jacek Juraszek - "Hadoop. W poszukiwaniu złotego młotka."
 
Powering Heap With PostgreSQL And CitusDB (PGConf Silicon Valley 2015)
Powering Heap With PostgreSQL And CitusDB (PGConf Silicon Valley 2015)Powering Heap With PostgreSQL And CitusDB (PGConf Silicon Valley 2015)
Powering Heap With PostgreSQL And CitusDB (PGConf Silicon Valley 2015)
 
Receipt processing with Google Cloud Platform and the Google Assistant
Receipt processing with Google Cloud Platform and the Google AssistantReceipt processing with Google Cloud Platform and the Google Assistant
Receipt processing with Google Cloud Platform and the Google Assistant
 
Lo Mejor Del Pdc2008 El Futrode C#
Lo Mejor Del Pdc2008 El Futrode C#Lo Mejor Del Pdc2008 El Futrode C#
Lo Mejor Del Pdc2008 El Futrode C#
 
Domain Driven Design
Domain Driven DesignDomain Driven Design
Domain Driven Design
 
Compose Async with RxJS
Compose Async with RxJSCompose Async with RxJS
Compose Async with RxJS
 
Community-driven Language Design at TC39 on the JavaScript Pipeline Operator ...
Community-driven Language Design at TC39 on the JavaScript Pipeline Operator ...Community-driven Language Design at TC39 on the JavaScript Pipeline Operator ...
Community-driven Language Design at TC39 on the JavaScript Pipeline Operator ...
 
project
projectproject
project
 
A head start on cloud native event driven applications - bigdatadays
A head start on cloud native event driven applications - bigdatadaysA head start on cloud native event driven applications - bigdatadays
A head start on cloud native event driven applications - bigdatadays
 
MongoDB World 2018: Keynote
MongoDB World 2018: KeynoteMongoDB World 2018: Keynote
MongoDB World 2018: Keynote
 
JavaScript Objects and OOP Programming with JavaScript
JavaScript Objects and OOP Programming with JavaScriptJavaScript Objects and OOP Programming with JavaScript
JavaScript Objects and OOP Programming with JavaScript
 
Presto in Treasure Data (presented at db tech showcase Sapporo 2015)
Presto in Treasure Data (presented at db tech showcase Sapporo 2015)Presto in Treasure Data (presented at db tech showcase Sapporo 2015)
Presto in Treasure Data (presented at db tech showcase Sapporo 2015)
 
Map/reduce, geospatial indexing, and other cool features (Kristina Chodorow)
Map/reduce, geospatial indexing, and other cool features (Kristina Chodorow)Map/reduce, geospatial indexing, and other cool features (Kristina Chodorow)
Map/reduce, geospatial indexing, and other cool features (Kristina Chodorow)
 
Constance et qualité du code dans une équipe - Rémi Prévost
Constance et qualité du code dans une équipe - Rémi PrévostConstance et qualité du code dans une équipe - Rémi Prévost
Constance et qualité du code dans une équipe - Rémi Prévost
 
JS Fest 2019. Anjana Vakil. Serverless Bebop
JS Fest 2019. Anjana Vakil. Serverless BebopJS Fest 2019. Anjana Vakil. Serverless Bebop
JS Fest 2019. Anjana Vakil. Serverless Bebop
 
C Programming :- An Example
C Programming :- An Example C Programming :- An Example
C Programming :- An Example
 
bbyopenApp_Code.DS_StorebbyopenApp_CodeVBCodeGoogleMaps.docx
bbyopenApp_Code.DS_StorebbyopenApp_CodeVBCodeGoogleMaps.docxbbyopenApp_Code.DS_StorebbyopenApp_CodeVBCodeGoogleMaps.docx
bbyopenApp_Code.DS_StorebbyopenApp_CodeVBCodeGoogleMaps.docx
 
java experiments and programs
java experiments and programsjava experiments and programs
java experiments and programs
 
BLoC - Be Reactive in flutter
BLoC - Be Reactive in flutterBLoC - Be Reactive in flutter
BLoC - Be Reactive in flutter
 
Cnam azure 2014 mobile services
Cnam azure 2014   mobile servicesCnam azure 2014   mobile services
Cnam azure 2014 mobile services
 

More from Bartosz Sypytkowski

Postgres indexes: how to make them work for your application
Postgres indexes: how to make them work for your applicationPostgres indexes: how to make them work for your application
Postgres indexes: how to make them work for your application
Bartosz Sypytkowski
 
How do databases perform live backups and point-in-time recovery
How do databases perform live backups and point-in-time recoveryHow do databases perform live backups and point-in-time recovery
How do databases perform live backups and point-in-time recovery
Bartosz Sypytkowski
 
Scaling connections in peer-to-peer applications
Scaling connections in peer-to-peer applicationsScaling connections in peer-to-peer applications
Scaling connections in peer-to-peer applications
Bartosz Sypytkowski
 
Rich collaborative data structures for everyone
Rich collaborative data structures for everyoneRich collaborative data structures for everyone
Rich collaborative data structures for everyone
Bartosz Sypytkowski
 
Postgres indexes
Postgres indexesPostgres indexes
Postgres indexes
Bartosz Sypytkowski
 
Behind modern concurrency primitives
Behind modern concurrency primitivesBehind modern concurrency primitives
Behind modern concurrency primitives
Bartosz Sypytkowski
 
Collaborative eventsourcing
Collaborative eventsourcingCollaborative eventsourcing
Collaborative eventsourcing
Bartosz Sypytkowski
 
Behind modern concurrency primitives
Behind modern concurrency primitivesBehind modern concurrency primitives
Behind modern concurrency primitives
Bartosz Sypytkowski
 
Virtual machines - how they work
Virtual machines - how they workVirtual machines - how they work
Virtual machines - how they work
Bartosz Sypytkowski
 
Short story of time
Short story of timeShort story of time
Short story of time
Bartosz Sypytkowski
 
Akka.NET streams and reactive streams
Akka.NET streams and reactive streamsAkka.NET streams and reactive streams
Akka.NET streams and reactive streams
Bartosz Sypytkowski
 
Collaborative text editing
Collaborative text editingCollaborative text editing
Collaborative text editing
Bartosz Sypytkowski
 
The last mile from db to disk
The last mile from db to diskThe last mile from db to disk
The last mile from db to disk
Bartosz Sypytkowski
 
GraphQL - an elegant weapon... for more civilized age
GraphQL - an elegant weapon... for more civilized ageGraphQL - an elegant weapon... for more civilized age
GraphQL - an elegant weapon... for more civilized age
Bartosz Sypytkowski
 

More from Bartosz Sypytkowski (14)

Postgres indexes: how to make them work for your application
Postgres indexes: how to make them work for your applicationPostgres indexes: how to make them work for your application
Postgres indexes: how to make them work for your application
 
How do databases perform live backups and point-in-time recovery
How do databases perform live backups and point-in-time recoveryHow do databases perform live backups and point-in-time recovery
How do databases perform live backups and point-in-time recovery
 
Scaling connections in peer-to-peer applications
Scaling connections in peer-to-peer applicationsScaling connections in peer-to-peer applications
Scaling connections in peer-to-peer applications
 
Rich collaborative data structures for everyone
Rich collaborative data structures for everyoneRich collaborative data structures for everyone
Rich collaborative data structures for everyone
 
Postgres indexes
Postgres indexesPostgres indexes
Postgres indexes
 
Behind modern concurrency primitives
Behind modern concurrency primitivesBehind modern concurrency primitives
Behind modern concurrency primitives
 
Collaborative eventsourcing
Collaborative eventsourcingCollaborative eventsourcing
Collaborative eventsourcing
 
Behind modern concurrency primitives
Behind modern concurrency primitivesBehind modern concurrency primitives
Behind modern concurrency primitives
 
Virtual machines - how they work
Virtual machines - how they workVirtual machines - how they work
Virtual machines - how they work
 
Short story of time
Short story of timeShort story of time
Short story of time
 
Akka.NET streams and reactive streams
Akka.NET streams and reactive streamsAkka.NET streams and reactive streams
Akka.NET streams and reactive streams
 
Collaborative text editing
Collaborative text editingCollaborative text editing
Collaborative text editing
 
The last mile from db to disk
The last mile from db to diskThe last mile from db to disk
The last mile from db to disk
 
GraphQL - an elegant weapon... for more civilized age
GraphQL - an elegant weapon... for more civilized ageGraphQL - an elegant weapon... for more civilized age
GraphQL - an elegant weapon... for more civilized age
 

Recently uploaded

May Marketo Masterclass, London MUG May 22 2024.pdf
May Marketo Masterclass, London MUG May 22 2024.pdfMay Marketo Masterclass, London MUG May 22 2024.pdf
May Marketo Masterclass, London MUG May 22 2024.pdf
Adele Miller
 
Globus Compute Introduction - GlobusWorld 2024
Globus Compute Introduction - GlobusWorld 2024Globus Compute Introduction - GlobusWorld 2024
Globus Compute Introduction - GlobusWorld 2024
Globus
 
In 2015, I used to write extensions for Joomla, WordPress, phpBB3, etc and I ...
In 2015, I used to write extensions for Joomla, WordPress, phpBB3, etc and I ...In 2015, I used to write extensions for Joomla, WordPress, phpBB3, etc and I ...
In 2015, I used to write extensions for Joomla, WordPress, phpBB3, etc and I ...
Juraj Vysvader
 
First Steps with Globus Compute Multi-User Endpoints
First Steps with Globus Compute Multi-User EndpointsFirst Steps with Globus Compute Multi-User Endpoints
First Steps with Globus Compute Multi-User Endpoints
Globus
 
Launch Your Streaming Platforms in Minutes
Launch Your Streaming Platforms in MinutesLaunch Your Streaming Platforms in Minutes
Launch Your Streaming Platforms in Minutes
Roshan Dwivedi
 
BoxLang: Review our Visionary Licenses of 2024
BoxLang: Review our Visionary Licenses of 2024BoxLang: Review our Visionary Licenses of 2024
BoxLang: Review our Visionary Licenses of 2024
Ortus Solutions, Corp
 
Providing Globus Services to Users of JASMIN for Environmental Data Analysis
Providing Globus Services to Users of JASMIN for Environmental Data AnalysisProviding Globus Services to Users of JASMIN for Environmental Data Analysis
Providing Globus Services to Users of JASMIN for Environmental Data Analysis
Globus
 
Field Employee Tracking System| MiTrack App| Best Employee Tracking Solution|...
Field Employee Tracking System| MiTrack App| Best Employee Tracking Solution|...Field Employee Tracking System| MiTrack App| Best Employee Tracking Solution|...
Field Employee Tracking System| MiTrack App| Best Employee Tracking Solution|...
informapgpstrackings
 
Beyond Event Sourcing - Embracing CRUD for Wix Platform - Java.IL
Beyond Event Sourcing - Embracing CRUD for Wix Platform - Java.ILBeyond Event Sourcing - Embracing CRUD for Wix Platform - Java.IL
Beyond Event Sourcing - Embracing CRUD for Wix Platform - Java.IL
Natan Silnitsky
 
Orion Context Broker introduction 20240604
Orion Context Broker introduction 20240604Orion Context Broker introduction 20240604
Orion Context Broker introduction 20240604
Fermin Galan
 
How Recreation Management Software Can Streamline Your Operations.pptx
How Recreation Management Software Can Streamline Your Operations.pptxHow Recreation Management Software Can Streamline Your Operations.pptx
How Recreation Management Software Can Streamline Your Operations.pptx
wottaspaceseo
 
Prosigns: Transforming Business with Tailored Technology Solutions
Prosigns: Transforming Business with Tailored Technology SolutionsProsigns: Transforming Business with Tailored Technology Solutions
Prosigns: Transforming Business with Tailored Technology Solutions
Prosigns
 
Innovating Inference - Remote Triggering of Large Language Models on HPC Clus...
Innovating Inference - Remote Triggering of Large Language Models on HPC Clus...Innovating Inference - Remote Triggering of Large Language Models on HPC Clus...
Innovating Inference - Remote Triggering of Large Language Models on HPC Clus...
Globus
 
Globus Connect Server Deep Dive - GlobusWorld 2024
Globus Connect Server Deep Dive - GlobusWorld 2024Globus Connect Server Deep Dive - GlobusWorld 2024
Globus Connect Server Deep Dive - GlobusWorld 2024
Globus
 
Webinar: Salesforce Document Management 2.0 - Smarter, Faster, Better
Webinar: Salesforce Document Management 2.0 - Smarter, Faster, BetterWebinar: Salesforce Document Management 2.0 - Smarter, Faster, Better
Webinar: Salesforce Document Management 2.0 - Smarter, Faster, Better
XfilesPro
 
Utilocate provides Smarter, Better, Faster, Safer Locate Ticket Management
Utilocate provides Smarter, Better, Faster, Safer Locate Ticket ManagementUtilocate provides Smarter, Better, Faster, Safer Locate Ticket Management
Utilocate provides Smarter, Better, Faster, Safer Locate Ticket Management
Utilocate
 
Exploring Innovations in Data Repository Solutions - Insights from the U.S. G...
Exploring Innovations in Data Repository Solutions - Insights from the U.S. G...Exploring Innovations in Data Repository Solutions - Insights from the U.S. G...
Exploring Innovations in Data Repository Solutions - Insights from the U.S. G...
Globus
 
GraphSummit Paris - The art of the possible with Graph Technology
GraphSummit Paris - The art of the possible with Graph TechnologyGraphSummit Paris - The art of the possible with Graph Technology
GraphSummit Paris - The art of the possible with Graph Technology
Neo4j
 
Quarkus Hidden and Forbidden Extensions
Quarkus Hidden and Forbidden ExtensionsQuarkus Hidden and Forbidden Extensions
Quarkus Hidden and Forbidden Extensions
Max Andersen
 
Vitthal Shirke Java Microservices Resume.pdf
Vitthal Shirke Java Microservices Resume.pdfVitthal Shirke Java Microservices Resume.pdf
Vitthal Shirke Java Microservices Resume.pdf
Vitthal Shirke
 

Recently uploaded (20)

May Marketo Masterclass, London MUG May 22 2024.pdf
May Marketo Masterclass, London MUG May 22 2024.pdfMay Marketo Masterclass, London MUG May 22 2024.pdf
May Marketo Masterclass, London MUG May 22 2024.pdf
 
Globus Compute Introduction - GlobusWorld 2024
Globus Compute Introduction - GlobusWorld 2024Globus Compute Introduction - GlobusWorld 2024
Globus Compute Introduction - GlobusWorld 2024
 
In 2015, I used to write extensions for Joomla, WordPress, phpBB3, etc and I ...
In 2015, I used to write extensions for Joomla, WordPress, phpBB3, etc and I ...In 2015, I used to write extensions for Joomla, WordPress, phpBB3, etc and I ...
In 2015, I used to write extensions for Joomla, WordPress, phpBB3, etc and I ...
 
First Steps with Globus Compute Multi-User Endpoints
First Steps with Globus Compute Multi-User EndpointsFirst Steps with Globus Compute Multi-User Endpoints
First Steps with Globus Compute Multi-User Endpoints
 
Launch Your Streaming Platforms in Minutes
Launch Your Streaming Platforms in MinutesLaunch Your Streaming Platforms in Minutes
Launch Your Streaming Platforms in Minutes
 
BoxLang: Review our Visionary Licenses of 2024
BoxLang: Review our Visionary Licenses of 2024BoxLang: Review our Visionary Licenses of 2024
BoxLang: Review our Visionary Licenses of 2024
 
Providing Globus Services to Users of JASMIN for Environmental Data Analysis
Providing Globus Services to Users of JASMIN for Environmental Data AnalysisProviding Globus Services to Users of JASMIN for Environmental Data Analysis
Providing Globus Services to Users of JASMIN for Environmental Data Analysis
 
Field Employee Tracking System| MiTrack App| Best Employee Tracking Solution|...
Field Employee Tracking System| MiTrack App| Best Employee Tracking Solution|...Field Employee Tracking System| MiTrack App| Best Employee Tracking Solution|...
Field Employee Tracking System| MiTrack App| Best Employee Tracking Solution|...
 
Beyond Event Sourcing - Embracing CRUD for Wix Platform - Java.IL
Beyond Event Sourcing - Embracing CRUD for Wix Platform - Java.ILBeyond Event Sourcing - Embracing CRUD for Wix Platform - Java.IL
Beyond Event Sourcing - Embracing CRUD for Wix Platform - Java.IL
 
Orion Context Broker introduction 20240604
Orion Context Broker introduction 20240604Orion Context Broker introduction 20240604
Orion Context Broker introduction 20240604
 
How Recreation Management Software Can Streamline Your Operations.pptx
How Recreation Management Software Can Streamline Your Operations.pptxHow Recreation Management Software Can Streamline Your Operations.pptx
How Recreation Management Software Can Streamline Your Operations.pptx
 
Prosigns: Transforming Business with Tailored Technology Solutions
Prosigns: Transforming Business with Tailored Technology SolutionsProsigns: Transforming Business with Tailored Technology Solutions
Prosigns: Transforming Business with Tailored Technology Solutions
 
Innovating Inference - Remote Triggering of Large Language Models on HPC Clus...
Innovating Inference - Remote Triggering of Large Language Models on HPC Clus...Innovating Inference - Remote Triggering of Large Language Models on HPC Clus...
Innovating Inference - Remote Triggering of Large Language Models on HPC Clus...
 
Globus Connect Server Deep Dive - GlobusWorld 2024
Globus Connect Server Deep Dive - GlobusWorld 2024Globus Connect Server Deep Dive - GlobusWorld 2024
Globus Connect Server Deep Dive - GlobusWorld 2024
 
Webinar: Salesforce Document Management 2.0 - Smarter, Faster, Better
Webinar: Salesforce Document Management 2.0 - Smarter, Faster, BetterWebinar: Salesforce Document Management 2.0 - Smarter, Faster, Better
Webinar: Salesforce Document Management 2.0 - Smarter, Faster, Better
 
Utilocate provides Smarter, Better, Faster, Safer Locate Ticket Management
Utilocate provides Smarter, Better, Faster, Safer Locate Ticket ManagementUtilocate provides Smarter, Better, Faster, Safer Locate Ticket Management
Utilocate provides Smarter, Better, Faster, Safer Locate Ticket Management
 
Exploring Innovations in Data Repository Solutions - Insights from the U.S. G...
Exploring Innovations in Data Repository Solutions - Insights from the U.S. G...Exploring Innovations in Data Repository Solutions - Insights from the U.S. G...
Exploring Innovations in Data Repository Solutions - Insights from the U.S. G...
 
GraphSummit Paris - The art of the possible with Graph Technology
GraphSummit Paris - The art of the possible with Graph TechnologyGraphSummit Paris - The art of the possible with Graph Technology
GraphSummit Paris - The art of the possible with Graph Technology
 
Quarkus Hidden and Forbidden Extensions
Quarkus Hidden and Forbidden ExtensionsQuarkus Hidden and Forbidden Extensions
Quarkus Hidden and Forbidden Extensions
 
Vitthal Shirke Java Microservices Resume.pdf
Vitthal Shirke Java Microservices Resume.pdfVitthal Shirke Java Microservices Resume.pdf
Vitthal Shirke Java Microservices Resume.pdf
 

Living in eventually consistent reality