Foundationপ্রথম নীতি থেকে
LEVEL 9লেসন ৫/১৯অ্যাডভান্সড৫০ মিনিট

Happens-before ও Causality — কার আগে কার পরে

Happens-Before and Causality

Global clock ছাড়াও cause-effect মাপা যায় — happens-before-এর তিন rule, concurrency-র আসল অর্থ, আর reply-before-parent-জাতীয় user-visible ভুল ঠেকানোর সস্তা কৌশল।

এই লেসন শেষে আপনি পারবেন

  • Happens-before relation-এর তিনটা rule — program order, message transit, transitive closure — মিলিয়ে event-pair-এর causal সম্পর্ক space-time diagram-এ trace করতে পারবেন
  • Concurrency-এর আনুষ্ঠানিক সংজ্ঞা দিতে পারবেন — simultaneous নয়, neither-before — আর wall-clock দূরত্ব থাকাও concurrent হওয়া বোঝাতে পারবেন
  • Comment-reply inversion আর cross-replica read-your-writes violation — user-visible causality-ভাঙা দুই ক্লাসিক দৃশ্য timeline এঁকে ব্যাখ্যা করতে পারবেন
  • Session causality (per-client version vector) আর causal-key partitioning — mechanism, খরচ আর সীমা তুলনা করে workload-ভেদে সঠিকটা বাছতে পারবেন
  • CausalBuffer-ধাঁচের delivery engine লিখে holdback queue-র বিবর্তন দেখিয়ে FIFO-per-sender আর causal order যাচাই করতে পারবেন

আগে যা বোঝা থাকা দরকার

আগে এটা বুঝি

গত লেসনে logical clock বানিয়েছিলাম — Lamport আর vector clock। মূল ফলাফল দুইটা: Lamport clock একমুখী promise দেয় (a → b হলে timestamp ছোট, উল্টোটা জানা যায় না); vector clock উল্টোটাও জানায় — timestamp তুলনায় cause-সম্পর্ক ধরা যায়।

কিন্তু clock শুধু detect করে। আজকের প্রশ্ন: সেই detection দিয়ে কী বানাব? উত্তরটা production-এর প্রতিদিনে। একটা thread খুলুন —

Q: PostgreSQL-এ autovacuum tuning কীভাবে করে?
   └─ A: scale_factor 0.01 রাখো, monitor চালাও     ← উত্তর!

আর কখনো replication-এর জন্য উল্টোটা: উত্তরটা আগে, প্রশ্নটা পরে — বা প্রশ্নটাই ক’মিনিট নিখোঁশ। User screenshot নিয়ে ticket খোলে: “সাইট ভবিষ্যদ্বাণী করছে।” Magic না — causality violation: কারণ-প্রভাবের ধার replica-জগতে হারিয়েছে।

আজ দুইটা কাজ। এক: happens-before relation-টা আনুষ্ঠানিকভাবে দাঁড় করানো — কোন শর্তে দুই event-এর মধ্যে “আগে” বলা যায়। দুই: সেই সংজ্ঞা কাজে লাগিয়ে সস্তা কৌশলে user-visible ক্ষতি ঠেকানো, আর শেষে একটা delivery engine — উল্টো-ক্রমে জিনিস এলে সেটা আটকে রাখে, যাতে কারণের আগে প্রভাব ছাড়া অসম্ভব হয়।

মূল ধারণা

Relation-টা দাঁড় করাই — তিনটা rule

World model গত লেসন থেকেই: ক’টা process, প্রত্যেকটার ভেতরে ঘটছে event — local computation, message send, message receive। Shared clock নেই, shared memory নেই। এই জগতে “আগে”র একমাত্র বৈধ সংজ্ঞা তিনটা rule থেকে আসে:

Ruleনামবলে
১Program orderএকই process-এর ভেতরে a কোডে b-র আগে হলে a → b
২Message transita যদি m-এর send আর b সেই m-এর receive হয়, a → b
৩Transitivitya → b আর b → c হলে a → c

Formal ভাষায়: happens-before relation হলো program-order edge আর message-edge-এর union-এর <Term id="transitive-closure">transitive closure</Term> — (po ∪ msg)-এর closure। এই এক লাইনই Lamport-এর ১৯৭৮ paper-এর হৃদয়।

Relation-টা irreflexive, transitive, asymmetric (a → b হলে b → a অসম্ভব) — strict partial order: কিছু pair-এর ক্রম নির্ধারিত, অনেক pair অনির্ধারিতই থাকে। Total order-এর মতো সবাইকে সারি করে না — এটাই এর সৌন্দর্য আবার খরচ।

Concurrency — neither-before, simultaneous নয়

Partial order-এ অনির্ধারিত pair-দেরই নাম concurrent:

a ∥ b   ⟺   ¬(a → b)  এবং  ¬(b → a)

এবার সবচেয়ে বেশি ভুল হওয়া শব্দটা ঠিক করি: concurrent মানে একই সময়ে ঘটা নয়। মানে শুধু — message-path (সরাসরি বা পরোক্ষ) নেই। Laptop boot আর মঙ্গলগ্রহের rover-ঘোরা — wall- clock-এ বছরখানেকের ফারাক — তবু concurrent, খবর কারো কাছে পৌঁছায়নি। উল্টোটাও সত্য: একই সেকেন্ডের দুই event causal-ভাবে ordered হতে পারে — মাঝে message থাকলে।

Detection tool — vector clock এক লাইনে

গত লেসনের যন্ত্রটা এখানে ঢুকে পড়ে:

a → b   ⟺   V(a) <= V(b) componentwise  এবং  V(a) != V(b)
a ∥ b   ⟺   কোনো দিকেই comparable নয়

ঘড়ি-তুলনাই relation-এর decision procedure। আজ এটা দুই কাজে: display-order ঠিক করা (experiment), delivery আটকে-রাখা (build)।

Knowledge আর cut — informal ফ্রেম

Process p-র knowledge = যে সব event তার causal past-এ পৌঁছেছে (নিজেরগুলো + received-message-দের পূর্বপুরুষ)। Happens- before-এ downward-closed event-সেটকে বলে consistent cut; snapshot algorithm-গুলো এটাই ধরার চেষ্টা। আমাদের জন্য একটাই নিয়ম যথেষ্ট — holdback principle: যার causal past এখনো পৌঁছায়নি, সেটা দেখাও না। বাকি লেসনটা এই এক বাক্যের বাস্তবায়ন।

ভেতরে কী ঘটছে

Violation #1 — reply-before-parent, জন্মে কীভাবে

Setup: parent-comment গেল partition P1-এ, reply গেল partition P2 (thread-key routing ভাঙল, বা multi-region write); display-service দুই partition-ই থেকে async pull করে:

t1  alice -> P1 : parent (version 41)
t2  bob   -> P2 : reply  (version 87; bob-এর client parent-এর
                          vector দেখেছিল)
t3  P2 -> display : reply পৌঁছাল        (দ্রুত link!)
t4  P1 -> display : parent *এখনো* পথে
t5  user refresh -> [reply] একা ভেসে আছে

খেয়াল করুন: clock কেউ মিথ্যা বলেনি, packet হারায়নি — শুধু কেউ causal chain-টা বহন করেনি। Bob-এর client-এর কাছে parent-এর জ্ঞান ছিল; সেটা reply-র সাথে metadata হয়ে travel করেনি।

Violation #2 — replica-switch-এ read-your-writes হারানো

t1  profile-edit -> region-A primary (ack)
t2  পরের request <Term id="load-balancer">load balancer</Term>
    region-B lagging replica-তে পাঠাল
t3  B-তে edit এখনো replay হয়নি
t4  user: আমার পরিবর্তন *উধাও*

User নিজের কাজের causal past-এ দাঁড়িয়ে, server past অস্বীকার করল। খারাপ সংস্করণ: password-change lagging replica-তে দেরিতে পৌঁছালে পুরনো password-ই জীবিত — security incident।

সস্তা ট্র্যাকিং — দুইটা production-কৌশল

পূর্ণাঙ্গ causal broadcast ব্যবহার না করে বাস্তব system-গুলো সস্তা উপায়ে শুধু user-visible অংশটুকু রক্ষা করে:

কৌশল A — session causality (per-client version vector): client নিজের session-এর vector context ধরে রাখে, প্রতিটা request-এর সাথে পাঠায়; server data-তে context stamp করে, আর read- করা replica check করে — requester-এর context আমার কাছে আছে তো? না থাকলে wait/redirect। COPS-paper এটাকেই scalable করেছিল।

কৌশল B — causal-key partitioning trick: যে operation-গুলো একে অপরের cause হতে পারে সবগুলো এক key-তে bind — thread-এর সবকিছু thread_id-এ এক partition-এ। Single-partition FIFO নিজেই causal order দেয়; vector-এর দরকারই নেই। খরচ: hot-key skew, আর thread-এর বাইরের causality ধরা যায় না।

Session causalityCausal-key partition
Mechanismper-client vector + server checksame-key single ordering
Cross-entity causalityধরা যায়ধরা যায় না
Hot-spot riskনেইআছে
Client complexityমাঝারিপ্রায় শূন্য
display-pipeline — holdback principle কোথায় বসে
  1. event arrivessender-id + vector-clock payload সহ
  2. merge knowledgedisplay-node-র known-vector max-update করে
  3. predecessor checkevent-এর vector known-এর সব জায়গায় covered?
  4. deliver / holdbackcovered হলে render, না হলে holdback queue-তে অপেক্ষা
  5. queue re-scanপ্রতিটা নতুন delivery-র পরে আটকে-থাকাদের আবার বিচার

উদাহরণ

Crafted log — একই ডেটা, দুই renderer, দুই সত্য

তিন actor-এর interleaved log; arrival order নেটওয়ার্কের মনমতো উল্টো। bob-এর vector-এ alice: 1 — সে parent দেখে reply লিখেছিল:

arrivalactorlamportvectortext
1carol1{carol: 1}আমার ফ্যান গরম করছে
2bob2{bob: 1, alice: 1}reply — version-stamp ব্যবহার করো
3alice4{alice: 1}parent — cache invalidate কীভাবে?

Renderer ১ — naive lamport-sort: ছোট timestamp আগে → carol, bob(reply), alice(parent)। Reply প্রশ্নের আগে দেখায় — inversion reproduced: lamport number ক্রস-process তুলনায় অর্থহীন, bob-এর counter ভাগ্যক্রমে ছোট ছিল, এটুকুই।

Renderer ২ — vector-aware topological sort: প্রতিটা pair-এ vector-compare → edge — alice → bob পাওয়া যায় ({alice:1} <= {alice:1, bob:1}); carol কারো সাথে comparable নয়। topological sort চালালে carol, parent, reply — edge যেদিকে, display সেদিকে, arrival যা-ই হোক। মোড়ের কথা: carol-এর post order-হীন — সে concurrent, যেকোনো position legal; causality-র দাবি শুধু chain-এর ভেতরে।

ছবিতে

Space-time ছবি — arrow-ই সত্য

        P1            P2            P3
        │             │             │
  a1 ───●             │             │
        │\            │             │
        │ \ m1        │             │
  a2 ───●  \      b1 ●───────────● c1   m2 (P1->P3)
        │   ↘       │             │\
        │        b2 ●             │ \ r2
  a3 ───●           ∥            ●  │
        │             │           c2 ●

chain : a1 → b1 → c1 (message-সেতু + transitivity)
pair  : a3 ∥ c2      (কোনো পথ নেই — যে ক্রমই দিন, legal)
তিন process-এর space-time diagram — সময় উপর-থেকে-নিচ; diagonal হলো message; a1→b1→c1 causal chain, a3∥c2 concurrent pair।
  alice                     bob
    │                        │
 parent(41)             reply(87, vc={bob:1, alice:1})
    ▼                        ▼
   P1 ═══╗ async      ╔═══ P2        arrival: reply FIRST
         ▼            ▼
       ┌──────────────────┐
       │ display node     │
       │ holdback: [reply]│ ← vector-এ alice:1 আছে, known-এ নেই
       │ parent এলো → parent, তারপর reply
       └──────────────────┘
Violation #1-এর anatomy — vector বহন করলে display নিজেই reply-কে holdback করত।

নিজে চালিয়ে দেখুন

EXPERIMENT

Naive timestamp-sort বনাম vector-aware sort — এবং concurrency-র ছড়িয়ে পড়া

macOS / Linux / Windows (Python 3)· ১৫ মিনিট
# comments_causality.py — naive timestamp sort vs vector-aware causal sort
ARRIVALS = [
    # (arrival#, actor, lamport, vector, text)
    (1, "carol", 1, {"carol": 1},           "carol: আমার ফ্যান গরম করছে"),
    (2, "bob",   2, {"bob": 1, "alice": 1}, "bob: reply — version-stamp"),
    (3, "alice", 4, {"alice": 1},           "alice: parent — invalidate?"),
]

def hb(a, b):    # a -> b ? va <= vb componentwise এবং va != vb
    va, vb = a[3], b[3]
    keys = set(va) | set(vb)
    return all(va.get(k, 0) <= vb.get(k, 0) for k in keys) and va != vb

def naive_sort(events):
    return sorted(events, key=lambda e: e[2])          # lamport-সাজ

def causal_sort(events):
    order = []
    while len(order) < len(events):
        # ready = এখনো-বাকিদের মধ্যে যার undelivered predecessor নেই
        ready = [e for e in events if e not in order and
                 all(not hb(q, e) or q in order for q in events)]
        order.append(min(ready, key=lambda e: e[0]))   # arrival tie-break
    return order

print("— NAIVE: lamport timestamp sort —")
for e in naive_sort(ARRIVALS):
    print(f"  ts={e[2]} {e[4]}")
print("— CAUSAL: vector-aware topological sort —")
for e in causal_sort(ARRIVALS):
    print(f"  vc={e[3]} {e[4]}")

naive = naive_sort(ARRIVALS)
inv = any("reply" in x[4] and "parent" in y[4]
          for i, x in enumerate(naive) for y in naive[i+1:])
print(f"naive display-এ inversion: {inv}")

# ---------- অংশ ২: k process-এ concurrent pair-এর হিস্যা ----------
def concurrent_ratio(k, m=30):
    """প্রতি process m event; মাঝপথে আগের process-থেকে একটা
    message-merge (ring) — বাকি সব independent।"""
    clocks = [[0] * k for _ in range(k)]
    events = []
    for r in range(m):
        for proc in range(k):
            if r == m // 2:
                src = clocks[(proc - 1) % k]
                clocks[proc] = [max(a, b)
                                for a, b in zip(clocks[proc], src)]
            clocks[proc][proc] += 1
            events.append(tuple(clocks[proc]))
    conc = comp = 0
    n = len(events)
    for i in range(n):
        for j in range(i + 1, n):
            a, b = events[i], events[j]
            le = all(x <= y for x, y in zip(a, b))
            ge = all(x >= y for x, y in zip(a, b))
            if le or ge: comp += 1
            else:        conc += 1
    return 100 * conc / (comp + conc)

for k in (2, 3, 4, 5, 6):
    print(f"k={k}: concurrent pairs = {concurrent_ratio(k):.1f}%")

প্রত্যাশিত output (deterministic — হুবহু এটাই আসবে):

— NAIVE: lamport timestamp sort —
  ts=1 carol: আমার ফ্যান গরম করছে
  ts=2 bob: reply — version-stamp
  ts=4 alice: parent — invalidate?
— CAUSAL: vector-aware topological sort —
  vc={'carol': 1} carol: আমার ফ্যান গরম করছে
  vc={'alice': 1} alice: parent — invalidate?
  vc={'bob': 1, 'alice': 1} bob: reply — version-stamp
naive display-এ inversion: True
k=2: concurrent pairs = 24.6%
k=3: concurrent pairs = 38.2%
k=4: concurrent pairs = 46.0%
k=5: concurrent pairs = 51.0%
k=6: concurrent pairs = 54.5%

পড়ার সময় তিনটা লক্ষ্য: (১) naive ক্রমে reply আগে — একই ডেটা; (২) causal ক্রমে edge-এর দিকই final word; (৩) k বাড়ার সাথে concurrent-হিস্যা বাড়ছে — comparable pair তত বিরল, asymptotic-ভাবেই। “সবকিছু order করা” scale-এর হিসেবেই হার মানে।

এটা কী প্রমাণ করে

Lamport timestamp দিয়ে display-সাজালে reply-before-parent inversion হুবহু reproduce হয়, vector-compare + topological sort সেটা deterministic-ভাবে ঠিক করে; আর process-সংখ্যা বাড়লে comparable-pair-এর হিস্যা কমে গিয়ে দেখায় কেন causality শুধু chain-এ দাবি করে, সব pair-এ না।

নিজে বানান

BUILD IT

CausalBuffer — holdback queue-সহ causal delivery engine

Python · ●●●●○
  1. প্রতিটা message-এ (sender, vector, payload) — vector-ই তার causal resume
  2. deliverable শর্ত: sender-এর slot ঠিক পরের সংখ্যা (FIFO-per-sender) এবং বাকি slot-গুলো known-এ covered
  3. deliverable না হলে holdback queue-তে — মুছবেন না, অপেক্ষা
  4. প্রতিটা delivery-র পরে গোটা queue re-scan — এক delivery আরোগুলো ছাড় করতে পারে
  5. state-printer রাখুন — holdback-র বিবর্তনই এই build-এর প্রমাণ

লক্ষ্য: network উল্টো করে জিনিস পাঠাক — engine এমন deliver করবে যে parent কখনো reply-র পরে আসবে না, আর একই sender-এর msg1 কখনো msg2-র পরে না। শেষের দুই assertion এটাই যাচাই করে।

# causal_buffer.py — CausalBuffer: holdback-queue সহ causal delivery
class CausalBuffer:
    def __init__(self):
        self.known = {}          # delivered-সমূহের merged vector clock
        self.holdback = []       # এসেছে, কিন্তু deliverable নয়
        self.order = []          # চূড়ান্ত delivery sequence

    def _deliverable(self, sender, V):
        # (১) নিজের slot: sender-এর ঠিক পরের message হতে হবে (FIFO)
        if V.get(sender, 0) != self.known.get(sender, 0) + 1:
            return False
        # (২) বাকি slot: অন্য কার কাজও কি আমি সব দেখে ফেলেছি?
        return all(V.get(k, 0) <= self.known.get(k, 0)
                   for k in V if k != sender)

    def receive(self, sender, V, payload):
        self.holdback.append((sender, V, payload))
        self._show(f"receive  {payload!r}")
        self._pump()
    def _pump(self):
        progressed = True
        while progressed:
            progressed = False
            for ev in list(self.holdback):
                s, V, p = ev
                if self._deliverable(s, V):
                    self.holdback.remove(ev)
                    for k, v in V.items():            # knowledge merge
                        self.known[k] = max(self.known.get(k, 0), v)
                    self.order.append(p)
                    self._show(f"DELIVER  {p!r}")
                    progressed = True
    def _show(self, tag):
        hb = [f"{s}:{p}" for s, _, p in self.holdback]
        print(f"    {tag:<24} holdback=[{', '.join(hb) or '-'}]")


buf = CausalBuffer()
print("— arrival order: ইচ্ছা করেই উল্টো ফেড —")
buf.receive("bob",   {"bob": 1, "alice": 1}, "reply(parent)")
buf.receive("carol", {"carol": 2},           "carol-msg2")
buf.receive("alice", {"alice": 1},           "parent")
buf.receive("carol", {"carol": 1},           "carol-msg1")

print("— চূড়ান্ত delivery order —")
for i, item in enumerate(buf.order, 1):
    print(f"  {i}. {item}")

assert buf.order.index("parent") < buf.order.index("reply(parent)")
assert buf.order.index("carol-msg1") < buf.order.index("carol-msg2")
print("OK: causality + per-sender FIFO দুই-ই রক্ষা")

প্রত্যাশিত output — holdback queue-র শ্বাস-প্রশ্বাসটাই মূল পাঠ:

— arrival order: ইচ্ছা করেই উল্টো ফেড —
    receive  'reply(parent)' holdback=[bob:reply(parent)]
    receive  'carol-msg2'    holdback=[bob:reply(parent), carol:carol-msg2]
    receive  'parent'        holdback=[bob:reply(parent), carol:carol-msg2, alice:parent]
    DELIVER  'parent'        holdback=[bob:reply(parent), carol:carol-msg2]
    DELIVER  'reply(parent)' holdback=[carol:carol-msg2]
    receive  'carol-msg1'    holdback=[carol:carol-msg2, carol:carol-msg1]
    DELIVER  'carol-msg1'    holdback=[carol:carol-msg2]
    DELIVER  'carol-msg2'    holdback=[-]
— চূড়ান্ত delivery order —
  1. parent
  2. reply(parent)
  3. carol-msg1
  4. carol-msg2
OK: causality + per-sender FIFO দুই-ই রক্ষা

তিন মুহূর্ত: reply সবার আগে এসেও parent-এর অপেক্ষায় holdback-এ জীবন কাটাল; parent-delivery-র সাথেই cascade হয়ে বেরিয়ে এলো; carol-msg2 ভাইয়ের জন্য FIFO-রক্ষায় অপেক্ষা করল।

নিজে বাড়ান:

  1. Duplicate delivery inject করুন (একই (sender, seq) দুইবার) — idempotency guard যোগ করুন
  2. এক sender চিরতরে মরলে holdback-এ কোনগুলো আটকে থাকবে — timeout/flush policy ডিজাইন করুন
  3. Multi-key version — প্রতি key আলাদা buffer, কোন state share?

বাস্তব সিস্টেমে

বাস্তব জগতে happens-before

Git — relation-টাই explicit। প্রতিটা commit-এর parent-pointer ঘোষিত happens-before edge; history মানেই partial order-এর দলিল। Merge conflict মানে দুই concurrent branch — Git রায় দেয় না, আপনাকে দেয়।

Riak/Dynamo sibling। Vector clock incomparable হলে sibling জন্মায় — system detect করে, resolve আপনার কাজ। “Conflict মানে bug” ভুল: এটা concurrency-র স্বাভাবিক ফসল।

MongoDB causal sessions। Driver-level session context — কৌশল A-ই: client token বহন করে, server chain ধরে; read-your-writes session-boundary-র ভেতরে guaranteed।

Reddit/HN-ধাঁচের threads + COPS। Thread-key partitioning হলো কৌশল B-র mass-market রূপ — পুরো discussion এক shard-এ, reply কখনো question-এর আগে আসে না। COPS (SOSP 2011) প্রমাণ: wide-area store-এ causal-plus eventual-কে ছাড়িয়ে latency দেয়।

যে ভুলগুলো সবাই করে

“Concurrent events মানে একই সময়ে ঘটেছে।”

সংজ্ঞাটা আবার: concurrent মানে কোনো causal path নেই — neither-before। Wall-clock relation-হীন: বছর-দূরের দুই event concurrent হতে পারে, একই মাইক্রোসেকেন্ডের দুই event ordered হতে পারে। “P1-এ t=10ms, P2-তে t=10ms — concurrent?” — উত্তর: path দেখাও, clock নয়।

“Happens-before মানে wall-clock-এ আগে ঘটেছে।”

Relation-টা logical — physical time লাগে না, ব্যবহৃতও হয় না। Machine-clock sync যা-ই হোক, a → b রায় শুধু program-order আর message-path দেখে দেয়। উল্টোদিকটাও সতর্ক: clock বলল a আগে — তবু a ∥ b হলে distributed semantics-এ কেউ আগে না।

“Vector clock ব্যবহার করলে conflict ঠিক হয়েই যায়।”

Vector clock শুধু detect করে — ordered নাকি concurrent। Resolution আলাদা পলিসি: merge-function, LWW, user-prompt, CRDT-math। Riak sibling মানে clock কাজ করেছে; কোন অর্ধেক জিতবে — উত্তর clock-এ নেই। Detection আর resolution আলাদা স্তর।

“সঠিক system মানে সব event-pair-এর order ঠিক করতে হবে।”

Total order পাওয়া যায় — consensus-এর দামে (module-এর পরের অধ্যায়)। কিন্তু user-visible correctness বেশিরভাগ সময় শুধু causal subset চায়। Concurrent-pair-এর হিস্যা k-র সাথে বাড়ে — সব pair-এর order-শ্রম ঢালাই হয় এমন pair-এ, যার ভুল কেউ দেখেই না। আসল প্রশ্ন: কোন chain user-এর চোখে?

বুঝেছেন কি না দেখুন

1

Happens-before relation-এর তিনটা rule এক বাক্যে করে — আর formalভাবে relation-টা কোন union-এর কী?

স্মরণ

Rule ১: একই process-এ program-order হলে সেটাই →। Rule ২: send-এর receive উল্টোদিকে →। Rule ৩: transitivity — chain জুড়ে দাও। Formal: (po ∪ msg)-এর transitive closure।

2

Diagram-এ a3 আর c2 — একটার timestamp অন্যটার চেয়ে অনেক বড় হলেও দুটোকে concurrent বলা হলো। কোন তথ্যের অনুপস্থিতিতে এই রায়?

যুক্তি

Path-এর অনুপস্থিতিতে। a3 → c2 (বা উল্টো) কোনো message-chain পৌঁছায়নি — vector-compare incomparable। Timestamp-value relation-এর উৎস নয়; edges-ই উৎস। Clock-ছোট/বড় শুধু detection-tool-এর input।

3

Violation #2-তে edit region-A-তে acked, পরের read region-B থেকে পুরনো মান দিল। Session causality বসলে কোন মুহূর্তে কী বদলাবে?

প্রয়োগ

Edit-ack-এ A session-context vector bump করে response-এ ফেরত দেবে; client সেটা রেখে পরের request-এ বহন করবে; B নিজের applied-vector-এর সাথে তুলনা করে deficit দেখল serve না করে wait/redirect — fix সেই blind-routing মুহূর্তেই।

4

CausalBuffer-এ _deliverable-এর শর্ত (১) কেন ঠিক known+1, mere <= known নয়?

যুক্তি

V[sender] == known[sender]+1 মানে sender-এর gap-হীন ঠিক-পরের message। <= দিলে মাঝের পুরনো message শেষমুহূর্তে deliver হয়ে ক্রম উল্টাতে পারে; আর gap থাকলে শর্ত (১) fail — পরেরটা অপেক্ষায় বাধ্য, এটাই মাঝেরটার আসার guarantee।

5

Product: প্রতি document-এ collaborative comment-thread + global activity-feed। কোথায় causal-key partition, কোথায় session causality?

ডিজাইন

Thread: causal-key partition — conversation-local causality single-shard FIFO-তে বিনামূল্যে, client-জটিলতা শূন্য। Feed: cross-thread causality partitioning পারবে না; session-context vector (কৌশল A) যথেষ্ট — user নিজের action-এর order নিজের feed-এ দেখবে, পরের মানুষের feed eventual। Hot-document risk থেকে যায়।

6

Experiment-এ k=2 → 24.6% থেকে k=6 → 54.5% concurrent-pair। এই growth total-order-everything design-এর বিরুদ্ধে কী argument দেয়?

যুক্তি

Process যত বাড়ে comparable-pair তত বিরল — order-enforcement-শ্রম ক্রমবর্ধমানভাবে এমন pair-এ যায় যার ক্রম কারো অভিজ্ঞতা বদলায় না; scale-এ সেই শ্রম consensus-cost, তাই দাবি causality-তেই সীমাবদ্ধ রাখুন।

এরপর কী

এরপর কী

আজ হাতে এসেছে relation — কোন pair আগে-পরে, কোন pair স্বাধীন। কিন্তু user-এর প্রশ্ন বৃহত্তর ভাষায়: “আমি যা লিখলাম তা আমি দেখব? পরশুর লেখা আজ অদৃশ্য হবে না তো?” — এগুলো consistency model-এর ভাষা। পরের লেসনে ladder-টা আনুষ্ঠানিক হবে: linearizability-র real-time recency, sequential-এর mind-bending reorder, causal-এর আজকের relation-ভিত্তিক চুক্তি, eventual-এর ন্যূনতম promise — প্রতিটার জন্য history বানিয়ে বিচারক-কোড চালাব।

আরও পড়ুন

  • Time, Clocks, and the Ordering of Events in a Distributed System — Leslie Lamport — Communications of the ACM 21(7), 1978 · Happens-before relation-এর জন্মপত্র — এই লেসনের প্রতিটা সংজ্ঞার মূল উৎস
  • Virtual Time and Global States of Distributed Systems — Friedemann Mattern — Parallel and Distributed Algorithms, 1989 · Vector clock আর knowledge-ফ্রেমের পাঠ্যপুস্তক treatment
  • Don't Settle for Eventual: Scalable Causal Consistency for Wide-Area Storage with COPS — Wyatt Lloyd, Michael J. Freedman, Michael Kaminsky, David G. Andersen — SOSP 2011 · Session causality-র production-গ্রেড রূপ — hood অংশের ভিত্তি
  • Designing Data-Intensive Applications, Chapter 9 — Martin Kleppmann · Causality আর consistency-র সম্পর্কের সেরা practitioner-আলোচনা