Gossip Protocol ও CRDT — ছড়িয়ে পড়া আর conflict-free গণিত
Gossip Protocols and CRDTs
Leader ছাড়া cluster-এ খবর rumor-এর মতো ছড়ায় — মাত্র O(log N) round-এ; আর CRDT-র merge-algebra ওই এলোমেলো arrival-order-কেও অগ্রাহ্য করে সব replica-কে এক state-এ মিলিয়ে দেয়।
আগে এটা বুঝি
quorum-replication-এ consistency-র দাম (R + W > N), Raft-এ leader আর
majority vote — সবখানেই এক assumption: coordination চাই। কিন্তু ৯০০-
node Cassandra ring ভাবুন — তিন মহাদেশে ছড়ানো, প্রতি ঘণ্টায় node ওঠানামা।
এখানে leader নেই, সবাই peer। দুইটা প্রশ্ন উত্তরহীন: node-১ জানল node-৭ মরেছে —
বাকি ৮৯৮জন জানবে কীভাবে? আর দুই replica-তে concurrent write —
leader ছাড়া কে মীমাংসা করবে? আজকের লেসন এই দুই প্রশ্নের। প্রথমটার উত্তর
gossip protocol — খবর rumor-এর মতো: প্রতিটা node মাঝে মাঝে
যাদৃচ্ছিক peer-কে ‘শুনেছিস?’ জিজ্ঞেস করে, আর অবিশ্বাস্যভাবে দ্রুত পুরো
cluster জেনে যায়। দ্বিতীয়টার উত্তর CRDT —
data-structure এমনভাবে গড়া যায় যে concurrent update মেলানোটাই merge-rule;
coordination লাগে না, তবু সব replica এক state-এ পৌঁছায়।
প্রতিশ্রুতি সৎ: gossip-এ খবর অবশেষে পৌঁছায় — staleness-window লুকাব না, মাপব। CRDT-কে magic ভাবতে দেব না — merge-এর algebra খুলে দেখব, কোন structure কোন intent রক্ষা করে, কোনটা চুপচাপ data ফেলে দেয়।
মূল ধারণা
Rumor কীভাবে ছড়ায় — push, pull, push-pull
নিয়ম সরল: প্রতিটা node periodic tick-এ যাদৃচ্ছিক peer বেছে state
বিনিময় করে; fanout k মানে প্রতি tick-এ kজন। বিনিময়ের তিন স্বাদ:
| Style | কে পাঠায় | ভালো কখন | দুর্বল কখন |
|---|---|---|---|
| Push | যার খবর আছে সে ঠেলে দেয় | শুরুতে — informed কম | শেষে — receiver ইতিমধ্যেই জানে |
| Pull | যার খবর নেই সে চেয়ে নেয় | শেষে — uninformed কম | শুরুতে — কার কাছে চাইব অজানা |
| Push-pull | দুজনে যার-যার নেই নেয় | সব ধাপে | message দ্বিগুণ |
Probability-sketch: cluster-এর u ভগ্নাংশ এখনো অজ্ঞ হলে push-round-এ
কোনো অজ্ঞ node কারো নজরে না পড়ার সম্ভাবনা —
শুরুতে প্রতি round-এ অজ্ঞ-সংখ্যা প্রায় e^-k গুণ কমে — exponential
collapse, খবর পৌঁছায় log N-মাপের round-এ; k=1-এও হাজার-node cluster
ডজনখানেক round-এ জানে। শেষ ধাপে বদনাম: মাত্র কয়েকজন অজ্ঞ থাকলে random-
peer hit দুষ্কর — ওই long tail কাটে pull বা push-pull।
Variant-গুলোর নামকরণ
| Variant | চালনা | কখন থামে | ব্যবহার |
|---|---|---|---|
| Anti-entropy | নিয়মিত digest-compare | কখনো না — background | Replication repair |
| Rumor-mongering | উত্তেজনায় ছড়ায় | k blind-push-পরে mute | Hot news spread |
| SWIM-style | Detection + dissemination একসাথে | Continuous | Membership |
SWIM-এর naming: random peer-কে PING; reply না এলে তৃতীয় কারো মাধ্যমে Ping-Req (indirect probe); fail → SUSPECT ঘোষণা — সন্দেহ-খবরটাই gossip-এ ছড়ায়; refutation না এলে timeout-এ DEAD। Detection-decision আর dissemination-vehicle এক protocol-এ জোড়া।
Staleness window — সৎ স্বীকারোক্তি
Eventual convergence read-guarantee না — write-এর পরে অন্য replica-তে
পুরনো মান দেখার window থাকে, মোটামুটি log(N)/log(k) × tick × delay।
Bailis-দের PBS paper এটাকে মাপযোগ্য promise বানায় — ‘p% সময় t-সেকেন্ডের
বেশি stale না’ জাতীয় claim। মূল কথা: eventual consistency quantitative
হতে পারে, ‘কোনোদিন এক হবে’-র দুর্বল শব্দ না।
Strong eventual consistency — আর merge-এর তিন শর্ত
Gossip delivery দেয়, order দেয় না — replica-রা concurrent update ভিন্ন ক্রমে দেখতেই পারে; ad-hoc LWW-ধাঁচের মীমাংসা তখন replica-ভেদে ভিন্ন ফল — divergence। CRDT-র চুক্তি strong eventual consistency (SEC): (১) eventual delivery — যে update এক replica পেয়েছে সবাই অবশেষে পাবে; (২) convergence — যারা একই update-set পেয়েছে তাদের state অবশ্যই এক, order/timing/retry কিছুই ফল বদলাতে পারবে না। State-based CRDT-তে replicas state বিনিময় করে merge করে; convergence-নিশ্চিত করতে merge-এর তিন গুণ লাগে — প্রতিটা শর্ত gossip- বাস্তবতার প্রতিধ্বনি:
| শর্ত | মানে | কোন বাস্তবতা থেকে |
|---|---|---|
| Commutative | a ⊔ b = b ⊔ a | খবর যেকোনো order-এ পৌঁছায় |
| Associative | (a ⊔ b) ⊔ c = a ⊔ (b ⊔ c) | Relay-তে grouping এলোমেলো |
| Idempotent | a ⊔ a = a | Duplicate delivery অনিবার্য |
তিনটা মিলে join semilattice: partial order-এ জোড়ার least upper bound-ই
⊔; merge মানে lattice-এ ঊর্ধ্বমুখী হাঁটা — পথ যা-ই হোক, destination এক।
চার concrete CRDT
G-Counter। সরাসরি integer-sum merge idempotence ভাঙে (duplicate-এ দ্বিগুণ); slot-wise max-এ আলাদা node-এর increment overwrite হয়। সমাধান per-node slot — node শুধু নিজের ঘর লেখে, max-ই সঠিক union:
node-A: {A:3, B:2, C:5} node-B: {A:2, B:4, C:5}
merge = slot-max {A:3, B:4, C:5} value = sum = 12PN-Counter: decrement-এর জন্য positive-negative
দুই G-Counter-এর জুটি; value = P − N।
LWW-Register। Write বহন করে timestamp; merge রাখে বড়টা, tie-তে node-id — deterministic-ই দরকার, fairness না। সৎ স্বীকারোক্তি: clock-skew জগতে চুপচাপ একটা write ফেলে দিতে পারে — wall-clock-এ আমরা অবিশ্বস্তই দেখেছি।
OR-Set। Add নেয় unique tag; remove শুধু দেখা tag tombstone করে — concurrent add+remove-এ add wins:
R1: add(x,t1) R2: remove(x) — চেনে শুধু পুরনো tag-set
merge: add-set ∋ t1, removed ∌ t1 → x বেঁচে যায়ভেতরে কী ঘটছে
Semilattice-প্রমাণ — convergence কেন অমোঘ
Local update = state এক ধাপ ঊর্ধ্বে (monotonic); merge-ও upper bound-এ নেয়। একই update-set পেলে সবার final state = সেই update-গুলোর join — এক point; order, duplication, delay ফল বদলাতে পারে না। G-Counter- এর lattice slot-vector-দের coordinate-wise order; OR-set-এরটা subset order।
Push-pull crossover
Push শেষে মরে: m informed হলে hit-probability u=(N−m)/N। Pull শুরুতে
মরে: খবর-পাওয়ার সম্ভাবনা m/N। Cross roughly m ≈ N/ln N — আগে push,
পরে pull সেরা; push-pull দুই দিক একসাথে বলে propagation দ্বিগুণ দ্রুত,
tail-ও কাটে। Rumor-mongering-এ deaf-node-gap থেকে যেতে পারে — তাই rumor-
এর সাথে anti-entropy চলেই: rumor speed, anti-entropy completeness।
Payload-এর অর্থনীতি — op-based আর delta
Op-based শুধু operation পাঠায় — payload O(op), দাম causal-delivery channel: happens-before order-এ delivery না হলে add-wins-ও উল্টে যেতে পারে। Delta-state middle-path — শেষ sync-এর diff, gossip-sahishnu আর ছোট payload একসাথে। Production-এ মিশ্রণ: hot-path delta, periodic full-state safety-net।
Tombstone GC — চিরকালের ঋণ
Remove মনে রাখতে হয় নইলে resurrection: পুরনো delete-খবর দেরিতে পৌঁছালে পুরনো add আবার বেঁচে যায়। কিন্তু tombstone কখন মুছব — global knowledge ছাড়া কেউ নিশ্চিত না সবাই দেখেছে। শিল্প-উত্তর সব compromise: grace-period (Cassandra gc_grace_seconds), version-watermark, checkpointed compaction। ভুল tune মানে ghost-data — এই লেসনের সবচেয়ে নীরব production-bug।
উদাহরণ
Offline shopping-cart — দুই phone, এক cart
Offline-first app: phone আর tablet, sync মাঝে মাঝে।
t1 phone: add('coffee') {c1} t2 tablet: add('tea') {t7}
t3 phone: add('milk') {m3} t4 tablet: remove('coffee') rem{c1}
SYNC merge: adds {c1,t7,m3}, rems {c1}
tea — concurrent add, remove তার tag চেনে না → বাঁচে ✓
milk — remove-এর সমসাময়িক add → বাঁচে ✓
coffee— remove tag সরাসরি দেখে মেরেছে → মরে ✓তিনটা ফলই intuition-মেলা: remove কেবল যা দেখেছিল তা-ই মেরে। এবার একই list LWW-Register-এ? Phone ts=103, tablet ts=104 (clock ২s এগিয়ে) — tablet জেতে, milk হাওয়া; crash নেই, bug নেই, শুধু clock-ফারাক। Data-type choice- ই intent-policy — এই লেসনের দামি এক লাইন। View-count G-Counter-এ? ৩+২ বার দেখা — slot-max ৫ ঠিক; naive sum ১০ ভুল।
ছবিতে
EPIDEMIC (k=3) MERGE FUNNEL
R0 ●○○○○○○○ informed=1 writes: inc(A,+2) inc(B,+1)
R1 ●●●●○○○○ =4 add(x,t9) remove(y,t2)
R2 ●●●●●●●●●●● =12
R3 ●●●●●●●●●●●●●●●● =27 R1 ─┐ ┌─ R1'
R4 ●●●●●●●●●●●●●●●●●●●● =28 ├─ gossip-merge ──┤ সবাই:
message-cost ≈ k×Σ(informed) R2 ─┤ slot-max, ├── cnt{A:2,B:1}
boom-phase-এই বেশিরভাগ │ set-union │ set{adds:{t9},
R3 ─┘ └─ rems:{t2}}
arrival-order যা-ই হোক — convergedনিজে চালিয়ে দেখুন
Gossip speedometer + OR-set truth-table — দুই theory-claim চোখে হাতে
"""gossip_lab.py — epidemic rounds + OR-set concurrency matrix"""
import random
def simulate_gossip(n=200, k=2, trials=30):
"""full-propagation round-count-র distribution"""
out = []
for _ in range(trials):
informed, rounds = {0}, 0
while len(informed) != n:
new = set()
for node in informed: # push-phase
new.update(random.sample(range(n), k))
for u in range(n): # pull-phase
if (u not in informed and random.random() < 0.5
and random.choice(range(n)) in informed):
new.add(u)
informed |= new
rounds += 1
out.append(rounds)
return out
print('=== gossip: rounds-to-full-propagation (30 trials) ===')
for n in (5, 200):
print(f'-- N={n}')
for k in (1, 2, 3):
rs = sorted(simulate_gossip(n=n, k=k))
print(f' k={k}: median={rs[len(rs)//2]} '
f'min={rs[0]} max={rs[-1]}')
class ORSet:
"""add-wins set — tagged adds; remove tombstones seen tags"""
def __init__(self):
self.adds = {} # tag -> element
self.rems = set() # tombstoned tags
def add(self, elem, tag):
self.adds[tag] = elem
def remove(self, elem):
self.rems |= {t for t, e in self.adds.items() if e == elem}
def elements(self):
return {e for t, e in self.adds.items() if t not in self.rems}
def merge(a, b):
m = ORSet()
m.adds = {**a.adds, **b.adds}
m.rems = a.rems | b.rems
return m
print('\n=== OR-set: concurrent add/remove final-state matrix ===')
# case 1: R2 কিছু দেখেনি → add-wins
r1, r2 = ORSet(), ORSet()
r1.add('x', 't1'); r2.remove('x') # R2-র চেনা tag শূন্য
ok1 = 'x' in merge(r1, r2).elements()
# case 2: R2 তার tag দেখেছিল → observed-remove works
r1b, r2b = ORSet(), ORSet()
r1b.add('x', 't1'); r2b.adds = dict(r1b.adds) # R2-এর copy
r2b.remove('x') # tombstones t1
ok2 = 'x' not in merge(r1b, r2b).elements()
# case 3: y-এর দুই concurrent add — element once
r1c, r2c = ORSet(), ORSet()
r1c.add('y', 'ta'); r2c.add('y', 'tb')
w3 = merge(r1c, r2c)
ok3 = w3.elements() == {'y'} and len(w3.adds) == 2
for name, expect, ok in [
('add(x) || remove(x-unseen)', 'add-wins -> x survives', ok1),
('remove after seeing tag', 'observed-remove -> x dies', ok2),
('two concurrent adds of y', 'union -> y once (2 tags)', ok3),
]:
print(f'{name:<28} expect: {expect:<26} got-ok={ok}')
print(f'\nALL-THEORY-MATCHED = {all([ok1, ok2, ok3])}')নমুনা output (round-সংখ্যা trial-ভেদে ওঠানামা করবে):
=== gossip: rounds-to-full-propagation (30 trials) ===
-- N=5
k=1: median=3 min=2 max=6
k=2: median=3 min=2 max=4
k=3: median=2 min=1 max=3
-- N=200
k=1: median=10 min=8 max=12
k=2: median=7 min=6 max=8
k=3: median=6 min=5 max=7
=== OR-set: concurrent add/remove final-state matrix ===
add(x) || remove(x-unseen) expect: add-wins -> x survives got-ok=True
remove after seeing tag expect: observed-remove -> x dies got-ok=True
two concurrent adds of y expect: union -> y once (2 tags) got-ok=True
ALL-THEORY-MATCHED = Trueতিনটা পাঠ:
- N=200-তে log-scaling স্পষ্ট: k=1 ~১০ round, k=3 ৫–৬; min-max spread-ই
probabilistic nature-এর সাক্ষ্য, আর
log₂200 ≈ 7.6-এর সাথে k=2-এর ৭–৮ হাতে-হাতে। - OR-set-এর তিন case-ই intent-অনুযায়ী — merge বিচার করে না, tag-bookkeeping-ই বিচার করে।
Fanout k=1,2,3-এ full-propagation round-সংখ্যা log-scale-এ কমে, k বাড়লে tail কাটে — O(log N)-sketch-এর live প্রমাণ; আর OR-set-এর প্রতিটা concurrent add/remove interleaving যে final-state দেয় সেটা add-wins theory-র matrix-এর সাথে হুবহু মেলে।
নিজে বানান
CRDT Suite — চার data-type, merge-algebra, property-fuzz
- GCounter — per-node slot dict, slot-wise max merge, value=sum
- PNCounter — positive-negative দুই GCounter-এর জুটি
- LWWRegister — timestamp write, merge বড় ts, tie-এ node-id
- ORSet — tagged adds + tombstone rems, add-wins merge
- Fuzz harness — random schedules, random sync, convergence assert
"""crdts.py — four CRDTs + property-based fuzz (~120 line)"""
import random
from dataclasses import dataclass, field
@dataclass
class GCounter:
slots: dict = field(default_factory=dict) # node -> count
def inc(self, node, by=1):
self.slots[node] = self.slots.get(node, 0) + by # নিজের slot-ই
def value(self):
return sum(self.slots.values())
def merge(self, other):
for node, c in other.slots.items():
self.slots[node] = max(self.slots.get(node, 0), c)
@dataclass
class PNCounter:
pos: GCounter = field(default_factory=GCounter)
neg: GCounter = field(default_factory=GCounter)
def inc(self, node, by=1):
(self.pos if by >= 0 else self.neg).inc(node, abs(by))
def value(self):
return self.pos.value() - self.neg.value()
def merge(self, other):
self.pos.merge(other.pos)
self.neg.merge(other.neg)
@dataclass
class LWWRegister:
value=None; ts=0; writer=''
def set(self, value, ts, writer): # strict > — equal-ts-এ পুরনো জেতে
if (ts, writer) > (self.ts, self.writer):
self.value, self.ts, self.writer = value, ts, writer
def merge(self, other):
self.set(other.value, other.ts, other.writer)
@dataclass
class ORSet:
adds: dict = field(default_factory=dict) # tag -> elem
rems: set = field(default_factory=set) # tombstoned tags
def add(self, elem, tag):
self.adds[tag] = elem
def remove(self, elem):
self.rems |= {t for t, e in self.adds.items() if e == elem}
def elements(self):
return {e for t, e in self.adds.items() if t not in self.rems}
def merge(self, other):
merged = dict(self.adds); merged.update(other.adds)
self.adds = merged
self.rems |= other.rems
def random_schedule(rng, ops_per_replica=12):
nodes = ['n1', 'n2', 'n3']
g = {n: GCounter() for n in nodes}
pn = {n: PNCounter() for n in nodes}
lw = {n: LWWRegister() for n in nodes}
os_ = {n: ORSet() for n in nodes}
for i in range(ops_per_replica):
n = rng.choice(nodes)
g[n].inc(n)
pn[n].inc(n, rng.choice([1, 1, -1]))
lw[n].set(f'v{i}', rng.randrange(100), n)
if rng.random() < 0.6:
os_[n].add(f'e{rng.randrange(4)}', f'tag{n}-{i}')
else:
os_[n].remove(f'e{rng.randrange(4)}')
# random sync-graph: এলোমেলো pair-merge — order ইচ্ছামতোই থাক
for _ in range(40):
a, b = rng.sample(nodes, 2)
g[a].merge(g[b]); pn[a].merge(pn[b])
lw[a].merge(lw[b]); os_[a].merge(os_[b])
g[b].merge(g[a]); pn[b].merge(pn[a])
lw[b].merge(lw[a]); os_[b].merge(os_[a])
assert len({g[n].value() for n in nodes}) == 1, 'G diverged!'
assert len({pn[n].value() for n in nodes}) == 1, 'PN diverged!'
assert len({(lw[n].value, lw[n].ts, lw[n].writer)
for n in nodes}) == 1, 'LWW diverged!'
assert len({frozenset(os_[n].elements())
for n in nodes}) == 1, 'ORSet diverged!'
def intent_cases(): # documented intent-preservation cases
a, b = GCounter(), GCounter() # duplicate delivery harmless?
a.inc('n1'); a.inc('n1'); b.merge(a); b.merge(a)
assert b.value() == 2, 'idempotent merge broken'
p = PNCounter(); p.inc('n1', 5); p.inc('n2', -3)
assert p.value() == 2, 'PN arithmetic broken'
r1, r2 = LWWRegister(), LWWRegister()
r1.set('late', 10, 'n1'); r2.set('early', 5, 'n2')
r1.merge(r2)
assert r1.value == 'late', 'LWW ordering broken'
s1, s2 = ORSet(), ORSet() # add-wins under concurrency?
s1.add('x', 't1'); s2.remove('x')
s1.merge(s2); s2.merge(s1)
assert 'x' in s1.elements() and 'x' in s2.elements(), 'add-wins broken'
print('intent cases: PASS (idempotency, PN-arithmetic, LWW-order, add-wins)')
rng = random.Random(42)
for i in range(300): # property-based fuzz
random_schedule(rng)
print('fuzz: 300 random schedules — convergence holds everywhere')
intent_cases()নমুনা output:
fuzz: 300 random schedules — convergence holds everywhere
intent cases: PASS (idempotency, PN-arithmetic, LWW-order, add-wins)তিনটা পাঠ:
- Fuzz specific schedule না, property test করে: যেকোনো interleaving-এ
convergence — ৩০০ schedule মানে ৩০০ বিশ্ব, সবখানে একই শেষ। Intent-case
আরও কঠিন: ‘মিলেছে’ না, ঠিকটা মিলেছে কি। LWW-র
strict >-এ equal-ts- এ পুরনো জেতে — production-এ incarnation-number দিয়ে refine হয়।
নিজে বাড়ান:
- MV-Register — concurrent write দুটোই রেখে client-কে resolve করতে দেয়
- Delta-mode — last-sync watermark রেখে শুধু delta; bandwidth-সাশ্রয় মাপুন
- Fuzz-এ fault-model — half-merged sync drop-পরবর্তী recovery converge করে?
বাস্তব সিস্টেমে
বাস্তব জগতে
Cassandra/Dynamo-ঘরানার ring। Membership gossip-এ — coordinator নেই, হাজার-node ring নিজেকে সাজায়; conflict-resolution-এ LWW আর CRDT দুই-ই।
Consul/Serf আর Redis Cluster bus। SWIM-derived gossip-pool-এ service-discovery; Redis-এ node-রা bus-port-এ slot-map আর liveness ছড়ায়।
Riak shopping-cart। সবচেয়ে পরিচিত production OR-set — concurrent cart-edit-এ add-wins customer-পছন্দ বাঁচায়।
Automerge/Yjs/Figma multiplayer। Concurrent document-edit-এর মিলন sequence-CRDT — shared-editor-এর নিচে commutative-merge-ই চলছে।
যে ভুলগুলো সবাই করে
“Gossip-এ খবর ঠিক log N round-এই সবার কাছে পৌঁছাবেই।”
Log N expectation — boundary না; simulation-এর min-max spread দেখুন, rumor-mongering-এ deaf-node-gap-ও আছে। Production-এর জবাব push-pull, fanout-tuning, anti-entropy safety-net। ‘Usually fast’ ≠ ‘always bounded’।
“CRDT মানে conflict-এ কিছুই হারায় না — সব data বাঁচে।”
Guaranteed টা convergence — preservation না: LWW পুরনো write ফেলে, G-Counter decrement জানে না, OR-set remove concurrent add-এ হার মানে। আসল দাবি: merge deterministic + intent-compatible; data-type বাছাই = conflict-policy বাছাই।
“Strong eventual consistency মানে strong consistency।”
SEC শুধু বলে একই update-set-দেখা replica এক state-এ থাকবে; read-freshness ওয়াদা নেই — write-পরবর্তী stale-read স্বাভাবিক (staleness-window!)। Linearizability- এর সাথে পার্থক্য ওই window-টাই; read-after-write-এ session-hint আলাদা লাগে।
“Gossip-traffic negligible — সব state বারবার সবাইকে পাঠাই।”
Message-সংখ্যা মিতব্যয়ী, payload না: full-state exchange O(cluster-state) byte — node/state বড় হলে bandwidth bottleneck; এজন্যই digest-exchange, delta-sync, merkle-tree repair। Traffic-design = count আর payload দুই হিসাব।
বুঝেছেন কি না দেখুন
1Merge-এর তিন algebraic শর্ত আর প্রতিটা ঠিক কোন gossip-বাস্তবতা থেকে বাধ্য?
স্মরণ
Commutative — খবর যেকোনো order-এ আসে; associative — relay-grouping এলোমেলো; idempotent — retry/relay-এ duplicate অনিবার্য। তিনটা মিলে join-semilattice: ঊর্ধ্বমুখী হাঁটার destination এক।
2G-Counter plain integer হলে sum-merge আর max-merge ঠিক কী ভাঙে? Slot-vector
কীভাবে বাঁচায়?
যুক্তি
Sum-merge: idempotence ভাঙে — duplicate-এ দ্বিগুণ, divergence। Max-merge: algebra ঠিক কিন্তু আলাদা node-এর increment overwrite হয়ে হারায়। Per-node slot-এ node শুধু নিজের ঘর লেখে — max-ই সঠিক union; value = slot-sum।
3৫০০-node, tick ১s, fanout 2, delay উপেক্ষণীয় — worst-case staleness-window
মোটামুটি কত? k=1-এ?
প্রয়োগ
Push-pull-সহ rounds ≈ log₂500 ≈ ৮–৯ → window ≈ ৯ সেকেন্ড। k=1-এ ~১১–১৩ round → ~১২ সেকেন্ড, tail লম্বা। Fanout-ই freshness-latency knob।
4User item remove করল, পরের sync-এ item ফিরে এলো — কখন বৈধ, কখন bug?
যুক্তি
বৈধ: sync-এর মাঝে অন্য replica নতুন tag-এ re-add করেছে — add-wins সঠিক। Bug কোনো re-add ছাড়া ফিরলে: tombstone আগেই GC হয়েছে বা merge-এ rems-union missing — resurrection: delete-খবরের আগে পুরনো add-খবর জিতে গেছে।
5Shopping-cart, view-counter, user-status (last-write-wins ঠিক), collaborative
checklist — চারটায় কোন CRDT, কেন?
ডিজাইন
Cart → OR-Set: concurrent add বাঁচুক। Counter → G/PN-Counter: arithmetic- merge-ই intent। Status → LWW-Register: latest-state-ই অর্থ। Checklist → OR-Set/sequence-CRDT: item-wise add/remove + ordering। চার জায়গায় চার বাছাই — intent-ই driver।
এরপর কী
এরপর কী
Gossip দিল delivery, CRDT দিল coordination-free মীমাংসা। Production-এ প্রশ্ন এক ধাপ পরে: data কোন node-এ বসবে, request পৌঁছাবে কোথায়, node join/leave-এ কী নড়বে? সেই operational স্তরই পরের লেসন — sharding at scale; আজকের gossip-membership সেখানে node-map-এর vehicle হয়ে ফিরবে।
আরও পড়ুন
- A Comprehensive Study of Conventional and Universal CRDTs — Martin Kleppmann, Alastair R. Beresford · CRDT design-space-র systematic survey — প্রতিটা structure-এর intent-trade-off-এর মানচিত্র
- Probabilistically Bounded Staleness for Practical Partial Quorums — Peter Bailis, Shivaram Venkataraman, Michael J. Franklin, Joseph M. Hellerstein, Ion Stoica · Staleness window মাপার কাঠামো — staleness-honesty-র পেছনের research
- Designing Data-Intensive Applications, Chapter 9 — Consistency, Consensus and the Consensus Problem — Martin Kleppmann · Eventual consistency আর anti-entropy-র ভূমিকা — gossip-কে বড় ছবিতে বসানোর reference