Foundationপ্রথম নীতি থেকে
LEVEL 9লেসন ১৯/১৯কঠিন৫০ মিনিট

Distributed Tracing — request-এর পুরো জীবন দেখা

Distributed Tracing: Spans, Propagation, Sampling

Microservice-jungle-এ এক request কোথায় সময় খেল — logs বা metrics বলে না; trace-span-tree, traceparent-header, sampling-economics আর critical-path-analysis মিলে সেই মানচিত্র আঁকে।

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

  • Trace-কে causal span-tree হিসেবে আঁকতে পারবেন — span anatomy (name, start, duration, tags, parent-id) structure থেকেই derive করে
  • W3C traceparent header-র byte-region dissect করে version, trace-id, parent-id, flags পড়তে পারবেন
  • HTTP আর async message-queue boundary জুড়ে context propagation-এর যন্ত্র ব্যাখ্যা করতে পারবেন
  • Head বনাম tail sampling-এর retention-economics হিসাব করে কোনটা failure-signal বাঁচায় বলতে পারবেন
  • Parallel spans-এর ভিড়ে critical path বের করে p99-এর আসল দোষী চিনতে পারবেন

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

আগে এটা বুঝি

আগের লেসনে shard-cluster সাজালাম। এখন production-এ checkout ঢুকল, p99 ২০০ ms → ১.৪ s; dashboard বলছে gateway-latency উঠেছে, logs ভরা সবুজ লাইন। প্রশ্ন উত্তর হচ্ছে না: সময়টা গেল কোথায়? Gateway? Cart? Inventory? নাকি মাঝের network?

এই darkness-ই microservice debugging-এর daily-reality — ঔষধ distributed tracing: প্রতিটা request-এ একটা বিশেষ ID রওয়ানা, service-সীমানায় ID হাতবদল, প্রতিটা service নিজের কাণ্ডের একটা span (শুরু-মেয়াদ-পরিচয়) রেকর্ড। শেষে span-জোড়া লাগিয়ে request-এর পুরো জীবনী — কে কাকে ডাকল, কে কত সময় নিল, দোষ কার ঘাড়ে।

Module-এর শেষ লেসন — observability-র তৃতীয় স্তম্ভ। আজ: span-tree anatomy, traceparent byte-dissection, HTTP/queue-boundary-তে context-ভ্রমণ, sampling- economics, critical-path — তারপর নিজের হাতে একটা TraceCollector।

মূল ধারণা

Trace = causal span-tree

Span-এর anatomy:

trace_id : পুরো request-জীবনের এক ID — সব span ভাগ করে
span_id  : এই কাণ্ডের নিজের ID
parent_id: কার ডাকে জন্ম — গাছের edge এখানেই
name     : 'GET /checkout' / 'db.query'
start, duration : wall-clock — অতএব সন্দেহপ্রবণ!
tags     : http.status=500, db.statement...

গাছ কেন? Call-graph-ই causality: root-span প্রবেশদ্বার, child ওর ডাকা downstream-work। Parallel calls = এক parent-এর একাধিক child — wall-clock sum না, max-chain: ধীরতম root→leaf শাখাই end-to-end-এর ঘড়ি — critical path।

W3C traceparent — ২৯ byte-এর identity-card

RegionByteHexউদাহরণমানে
version১২00Format-version; ff forbidden
trace-id১৬৩২4bf92f...4736Global request-ID; all-zero invalid
parent-id৮১৬00f067aa0ba902b7Caller-র span_id; all-zero invalid
trace-flags১২01Bit-0 sampled — ‘রাখবে?’
00 - 4bf92f3577b34da6a3ce929d0e0e4736 - 00f067aa0ba902b7 - 01
└┬┘ └──────────────┬───────────────────┘ └───────┬──────┘ └┬┘
version          trace-id                    parent-id   flags

দুই সূক্ষ্মতা: parent-id = immediate caller-এর span_id, তাই প্রতি hop-এ বদলায় — trace-id থিতিমান। আর flags-এর sampled-bit propagation-এর আগেই ঠিক হয় head-sampling-এ; tail-sampling-এ বিটটা প্রায় decorative।

Context propagation — boundary পেরোনো

এক checkout-request-এ context-এর ভ্রমণ
  1. Client → Gatewaygateway নতুন trace_id + span_id জন্ম দেয়; call-path-এ traceparent header ঢোকে
  2. Gateway span closestart/duration/tags রেকর্ড, async exporter — request-path ব্লক হয় না
  3. Gateway → Cartheader থেকে trace_id পড়ে, নিজের span_id দিয়ে নতুন traceparent — parent-id এবার gateway-র span_id
  4. Cart → QueueHTTP নেই — context message-carrier-এ (headers/attributes); envelope-এর ভেতর আরেক খাম
  5. Consumer → Inventoryconsumer context extract করে child-span বানায় — chain queue-জুড়েও ভাঙে না
  6. Collector assemblyspan আসে এলোমেলো order-এ; parent_id দিয়েই গাছ ফিরে গড়ে — timestamp দিয়ে না

মূল নীতি: in-process-এ thread-local, cross-process-এ wire, async-এ carrier — mechanism বদলায়, identity ভাঙে না; queue-world-এ producer-context consumer-এ পৌঁছানোই design-requirement।

Sampling — retention economics

সব trace? 10,000 req/s × ৮ span × ~250 byte ≈ 20 MB/s raw — দিনে ~1.7 TB, বেশিরভাগ healthy-request-এর নকল গল্প। দুই ঔষধ:

Strategyসিদ্ধান্ত কখনরাখেহারায়
Head samplingRequest-শুরুতে coin-flipRandom 1% সমভাগেদুর্লভ error-trace-ও!
Tail samplingTrace-complete-এর পরে100% errors + slowest + baseline %Buffer-cost, delayed visibility

1%-এ error-burst (200 failed) থেকে captured ≈ ২টা — কোনো দিন শূন্যও। Tail errors always-keep; দাম decision-window পর্যন্ত buffering। Retention-ও policy: hot-index ৭ দিন, cold ৩০ — query-pattern-ই দাম ঠিক করে।

Critical path — ভিড়ের মধ্যে দোষী

Parallel children: auth-check 33, cart-call 537, cache-lookup 12; end-to-end 562। Sum 582 — অসম্ভব: overlap, আর দৈর্ঘ্য ঠিক করে longest cumulative chain — gateway→cart→inventory। auth-check optimize = বৃথা; inventory-র index = p99 অর্ধেক।

ভেতরে কী ঘটছে

Clock-skew — waterfall যখন মিথ্যা বলে

Spans wall-clock timestamp আনে — আর machine-ঘড়ি agree করে না। Assembled tree-তে child parent-এর start-এর আগে দেখাতে পারে — impossible, routine। তিন প্রতিকার: causality-clamp (child-interval parent-এ clip), relative-time rendering, skew-detection-as-signal — build-এ তৃতীয়টাই। Duration মাপা monotonic-clock-এ, cross-service alignment wall-clock-এ — division-of-labor না বুঝলে skew-bug debug-এ সপ্তাহ যায়।

Assembly — out-of-order যে নিয়ম, exception না

Exporter async — retry, batch-flush, queue-lag; span আসে inventory-আগে gateway-পরে। Assembly তাই arrival-order-assumption রাখেই না:

1. ingest(span): trace_id → {span_id → span} map — ব্যস
2. children_map: parent_span_id-key-এ group
3. roots: parent_span_id সেটে নেই — তারাই গোড়া
4. missing-parent → ORPHAN-flag — গাছ ভাঙার বদলে report

কোথাও timestamp-sort নেই — order-এর উৎস parent-edge, time না; physical-time অবিশ্বস্ত, logical-relation ভরসা — module-এর পুরনো শিক্ষা।

OpenTelemetry landscape — কে কী

  • OTel API — code-এর span-vocabulary, vendor-neutral
  • SDK + auto-instrumentation — http/grpc/kafka hook; span+propagation auto
  • Collector + OTLP — receive-process-export pipeline; backend pluggable

OTel storage/UI দেয় না — instrumentation-standard দেয়; lock-in এখন config-file-এর ব্যাপার।

তিন স্তম্ভের সেতু

Metrics বলে কী খারাপ, logs কী ঘটেছে, traces কোথায়। সেতু trace_id: log-line-এ ছাপলে সেই log waterfall-এ click-away; exemplar-link dashboard থেকে দোষী span-এ। Maturity মাপা যায় এই সেতুর সংখ্যায়।

উদাহরণ

একটা trace-এর পুরো জীবন — হাতে পড়া

Checkout-request, তিন service + এক queue-hop; collector-এ পৌঁছানো spans (ms):

g1  gateway    GET /checkout   start=0    dur=562  parent=''
a9  auth-check verify_token    start=2    dur=33   parent=g1   parallel!
c1  cart       process_cart    start=8    dur=537  parent=g1
i1  inventory  reserve_stock   start=15   dur=503  parent=c1
q1  cart.publish order.event   start=549  dur=2    parent=c1
k1  notifier.consume           start=601  dur=41   parent=q1(link)

তিন নিয়মে পড়ুন: (১) tree — g1-এর children a9,c1; c1-এর i1,q1। (২) wall vs sum — children sum ৫৭০ ≠ ৫৬২: a9 parallel, c1-ই নিয়ন্ত্রক। (৩) chain — g1→c1→i1 longest = critical path; reserve_stock-ই ৫০৩ ms-এর মালিক। Queue-hop: k1-এর parent q1 — consumer producer-span-এর সন্তান, মাঝে সেকেন্ধণ্টা আর এক broker; causality preserved, timing decoupled।

ছবিতে

SPAN TREE                       WATERFALL (width = end-to-end)
g1 gateway                      g1        │█████████████████████████ 562ms
├─ a9 auth-check (parallel)     ├─a9      │███ 33ms
└─ c1 cart                      └─c1      │·████████████████████████ 537ms ←critical
   ├─ i1 inventory                 ├─i1   │··███████████████████████ 503ms ←critical
   │                               │      │   (৫০৩ ms — এইখানেই যুদ্ধ)
   └─ q1 publish                   └─q1   │·······················█ 2ms
      └─ k1 consume                   └─k1│·······················████ 41ms
Span-tree আর waterfall — এক data-র দুই projection: গাছ causality দেখায়, waterfall time-budget; ←critical চিহ্নিত শাখাই end-to-end ঠিক করে।

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

EXPERIMENT

তিন-service toy chain — manual propagation, waterfall, ×10 slowdown

macOS / Linux (Python 3.9+, stdlib-only)· ২৫ মিনিট
"""trace_lab.py — তিন-service toy chain, manual propagation"""
import threading
import time
import urllib.request
import uuid
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer

SPANS, LOCK = [], threading.Lock()
DELAY_C = 0.05                       # scenario-2 তে ০.৫ করে ×10 slow


def make_handler(name, downstream):
    class H(BaseHTTPRequestHandler):
        def do_GET(self):
            t0 = time.perf_counter()
            # extraction — থাকলে নাও, না থাকলে root-span জন্ম দাও
            tid = self.headers.get('X-Trace-Id', uuid.uuid4().hex[:12])
            parent = self.headers.get('X-Parent-Span', '')
            sid = uuid.uuid4().hex[:6]
            time.sleep({'gateway': 0.02, 'cart': 0.03}.get(name, 0))
            if name == 'inventory':
                time.sleep(DELAY_C)          # C-র নিজস্ব কাজ
            child_ms = 0.0
            if downstream:                   # injection — context হাতবদল
                port, _ = downstream
                req = urllib.request.Request(
                    f'http://127.0.0.1:{port}/call',
                    headers={'X-Trace-Id': tid, 'X-Parent-Span': sid})
                tc = time.perf_counter()
                urllib.request.urlopen(req).read()
                child_ms = (time.perf_counter() - tc) * 1000
            dur = (time.perf_counter() - t0) * 1000
            with LOCK:
                SPANS.append(dict(tid=tid, sid=sid, pid=parent,
                                  name=name, t0=t0, dur=dur,
                                  child_ms=child_ms))
            self.send_response(200)
            self.end_headers()
            self.wfile.write(b'ok')

        def log_message(self, *_a): pass
    return H


def start_chain(base_port, c_delay):
    global DELAY_C
    DELAY_C = c_delay
    ports = {nm: base_port + i for i, nm in
             enumerate(['gateway', 'cart', 'inventory'])}
    servers = []
    for nm, port in ports.items():
        down = ((ports['cart'], 'cart') if nm == 'gateway'
                else ((ports['inventory'], 'inventory')
                      if nm == 'cart' else None))
        srv = ThreadingHTTPServer(('127.0.0.1', port),
                                  make_handler(nm, down))
        threading.Thread(target=srv.serve_forever, daemon=True).start()
        servers.append(srv)
    return servers


def one_request(port, label):
    SPANS.clear()
    time.sleep(0.15)
    urllib.request.urlopen(f'http://127.0.0.1:{port}/').read()
    spans = sorted(SPANS, key=lambda s: s['t0'])
    origin = spans[0]['t0']
    total = max(s['t0'] + s['dur'] / 1000 for s in spans) - origin
    W = 60
    print(f'\n--- {label} (end-to-end ~{total*1000:.0f}ms) ---')
    for s in spans:
        off = round((s['t0'] - origin) / total * W)
        wdt = max(1, round((s['dur'] / 1000) / total * W))
        wait = '' if s['child_ms'] == 0 else \
            f' (downstream-wait {s["child_ms"]:.0f}ms)'
        print(f'{s["name"]:>10} │{"·"*off}{"█"*wdt} '
              f'{s["dur"]:6.1f}ms{wait}')


s1 = start_chain(8930, c_delay=0.05)
one_request(8930, 'scenario-1: healthy chain (C fast)')
for srv in s1:
    srv.shutdown()

start_chain(8940, c_delay=0.50)
one_request(8940, 'scenario-2: C slowed x10')
print('\nদেখুন: downstream-wait ফুলেছে, self-time (dur − wait) একই —'
      '\nদোষী C, waterfall-ই proof।')

নমুনা output (ms-মান machine-load-এ ওঠানামা করবে):

--- scenario-1: healthy chain (C fast) (end-to-end ~113ms) ---
   gateway │██████████████████████████████████████████████████████████  111.7ms (downstream-wait 90ms)
      cart │·············██████████████████████████████████████████████   88.9ms (downstream-wait 55ms)
 inventory │·······························████████████████████████████   51.0ms

--- scenario-2: C slowed x10 (end-to-end ~562ms) ---
   gateway │████████████████████████████████████████████████████████████  562.3ms (downstream-wait 540ms)
      cart │··█████████████████████████████████████████████████████████  538.9ms (downstream-wait 506ms)
 inventory │······██████████████████████████████████████████████████████  504.7ms

তিনটা পাঠ:

  • Gateway-র 112 ms-এর ভেতর নিজের কাজ ~22 ms — waterfall ছাড়া dashboard-এ gateway-ই দোষী দেখত। End-to-end ৫× হলেও কারো self-time বদলায়নি।
  • Header ছিল মাত্র trace-id + parent-id — traceparent-এর হ্রাসকৃত রূপ।
এটা কী প্রমাণ করে

Header-দুটো হাতে বহন করলেই তিন process-এর span এক tree-তে বাঁধে — magic নেই; service C ×10 ধীর করলে upstream-দের duration ফুলে যায় কিন্তু self-time একই থাকে — critical path C-তে সরেছে, সংখ্যায় প্রমাণ।

নিজে বানান

BUILD IT

TraceCollector — out-of-order assembly, waterfall, critical-path, diagnostics

Python · ●●●○○
  1. Ingest — JSON span, যেকোনো order-এ trace-map-এ
  2. Assembly — children_map parent-edge দিয়ে, roots missing-parent logic-এ
  3. Waterfall renderer — origin-relative bar-chart, width-normalized
  4. Critical path — root→leaf max cumulative-duration chain highlight
  5. Diagnostics — orphan, clock-skew, parent-overrun report
"""trace_collector.py — ingestion, out-of-order assembly, waterfall,
critical-path, orphan/skew diagnostics (~110 line)"""
import json


class TraceCollector:
    """Backend-collector-স্বাদ: spans যেকোনো order-এ আসবে বলেই ধরে নাও"""

    def __init__(self):
        self.traces = {}                     # trace_id -> {span_id: span}

    def ingest(self, raw):                   # str-json বা dict, order-free
        sp = json.loads(raw) if isinstance(raw, str) else dict(raw)
        self.traces.setdefault(sp['trace_id'], {})[sp['span_id']] = sp

    def spans(self, tid):
        return list(self.traces.get(tid, {}).values())

    def by_id(self, tid):
        return self.traces.get(tid, {})

    def children_map(self, tid):
        ch = {}
        for s in self.spans(tid):
            ch.setdefault(s['parent_span_id'], []).append(s)
        for lst in ch.values():
            lst.sort(key=lambda s: s['start_ms'])
        return ch

    def roots(self, tid):
        ids = set(self.by_id(tid))
        return [s for s in self.spans(tid)
                if s['parent_span_id'] not in ids]

    def critical_path(self, tid):   # root→leaf max cumulative (simplified!)
        ch = self.children_map(tid)
        memo = {}

        def best(s):
            if s['span_id'] not in memo:
                kids = ch.get(s['span_id'], [])
                kids = ch.get(s['span_id'], [])
                if kids:
                    bk, bv = max((best(k) for k in kids),
                                 key=lambda t: t[1])
                    memo[s['span_id']] = ([s] + bk, s['duration_ms'] + bv)
                else:
                    memo[s['span_id']] = ([s], s['duration_ms'])
            return memo[s['span_id']]

        rs = self.roots(tid)
        return max((best(r) for r in rs), key=lambda t: t[1])[0] if rs else []


    def waterfall(self, tid, width=58):
        spans = sorted(self.spans(tid), key=lambda s: s['start_ms'])
        if not spans:
            print('(empty trace)')
            return
        cp_ids = {s['span_id'] for s in self.critical_path(tid)}
        origin = min(s['start_ms'] for s in spans)
        total = max(max(s['start_ms'] + s['duration_ms'] for s in spans)
                    - origin, 1e-9)
        for s in spans:
            off = round((s['start_ms'] - origin) / total * width)
            w = max(1, round(s['duration_ms'] / total * width))
            star = ' <-critical' if s['span_id'] in cp_ids else ''
            print(f"{s['name']:>12} │{'·'*off}{'█'*w}"
                  f" {s['duration_ms']:6.1f}ms{star}")

    def diagnostics(self, tid):
        byid = self.by_id(tid)
        issues = []
        for s in sorted(self.spans(tid), key=lambda x: x['start_ms']):
            pid = s['parent_span_id']
            if pid and pid not in byid:
                issues.append(f"ORPHAN  : '{s['name']}'-র parent {pid} "
                              f"এই trace-তে নেই (dropped export?)")
                continue
            p = byid.get(pid)
            if p:
                if s['start_ms'] < p['start_ms']:
                    issues.append(f"CLOCK-SKEW: '{s['name']}' parent-এর "
                                  f"{p['start_ms'] - s['start_ms']:.1f}ms আগে শুরু!")
                over = (s['start_ms'] + s['duration_ms']) - \
                       (p['start_ms'] + p['duration_ms'])
                if over > 1e-6:
                    issues.append(f"OVERRUN : '{s['name']}' parent-কে "
                                  f"{over:.1f}ms পেরিয়ে শেষ")
        return issues


def sp(trace_id, span_id, parent, name, start, dur):
    return dict(trace_id=trace_id, span_id=span_id, parent_span_id=parent,
                name=name, start_ms=start, duration_ms=dur)

c = TraceCollector()
T = 'tr-42'
SHUFFLED = [                                   # ইচ্ছা-করে এলোমেলো order
    sp(T, 'i1', 'c1', 'inventory', 15, 503),
    sp(T, 'o7', 'zz9', 'lost-span', 400, 10),      # orphan!
    sp(T, 'a9', 'g1', 'auth-check', -3, 33),       # clock-skew!
    sp(T, 'g1', '', 'gateway', 0, 562),
    sp(T, 'd4', 'c1', 'db-query', 40, 38),
    sp(T, 'c1', 'g1', 'cart', 8, 537),
]
for s in SHUFFLED:
    c.ingest(s)

print('waterfall (trace tr-42):')
c.waterfall(T)
chain = c.critical_path(T)
cum = sum(s['duration_ms'] for s in chain)
print('\ncritical path:', ' -> '.join(s['name'] for s in chain),
      f'(cumulative {cum:.0f}ms)')
print('\ndiagnostics:')
for line in c.diagnostics(T):
    print(' ', line)

নমুনা output:

waterfall (trace tr-42):
  auth-check │███   33.0ms
     gateway │██████████████████████████████████████████████████████████  562.0ms <-critical
        cart │·███████████████████████████████████████████████████████  537.0ms <-critical
   inventory │··████████████████████████████████████████████████████  503.0ms <-critical
    db-query │····████   38.0ms
   lost-span │·········································█   10.0ms

critical path: gateway -> cart -> inventory (cumulative 1602ms)

diagnostics:
  CLOCK-SKEW: 'auth-check' parent-এর 3.0ms আগে শুরু!
  ORPHAN  : 'lost-span'-র parent zz9 এই trace-তে নেই (dropped export?)

তিনটা পাঠ:

  • Ingest-order এলোমেলো হলেও tree perfect — order-এর ভরসা timestamp না, parent_edge; production-collector-এর প্রথম design-rule।
  • Critical-path cumulative 1602 > wall 565 — overlapping parent-duration double-count-এর সৎ স্বীকারোক্তি; real tools interval-arithmetic-এ refine করে, teaching-version intent-ই দেখায়।
  • Diagnostics demo-data-তে দুই bug-class ধরল — dropped-export (orphan), unsynced-clock (skew); তৃতীয় detector (overrun — instrumentation-bug) trigger করতে inventory-র duration ৫৬০ করে চালান।

নিজে বাড়ান:

  1. Interval-arithmetic critical-path — overlap বাদ দিয়ে true self-time chain
  2. Per-service latency histogram — হাজার trace-ingest করে p50/p99 রিপোর্ট
  3. Tail-sampling filter — keep-errors + keep-slowest policy, capture-ratio মাপুন

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

বাস্তব জগতে

Dapper-থেকে Zipkin। Google-এর 2010 paper concept industrialize করে; Twitter-এর Zipkin ওপেন-সোর্স রূপ — আজ OTel-যুগেও span-model হুবহু Dapper-এরই।

OpenTelemetry adoption। Vendor-রা OTel SDK back করছে — CNCF-র second-most-active project; fragmentation শেষ, এখন backend-competition।

Postmortem culture। Latency incident-এ প্রথম exhibit প্রায়ই comparative- trace: deploy-আগে-পরে waterfall diff — logs-এ ঘণ্টা, traces-এ এক নজর।

Sampling economics। Ad-tech pipelines-এ tail-sampling errors + slow always-keep, baseline 1–5% — storage-bill এক-digit percent। Queue-boundary continuity-ই অনেক org-এর কঠিনতম milestone।

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

“Waterfall-এ span start-time sort করলেই সত্যিকারের ক্রম।”

Sort display-order, truth না — machine-ঘড়ি unsynced, child-parent impossible- ordering routine (build-এর CLOCK-SKEW)। Structure parent_edge থেকে আসে; serious tools skew-কে signal-ও করে।

“Sampling মানে data নষ্ট — 100% trace-ই আসল engineering।”

100%-এর দাম storage (~TB/day), ingestion-cost, আর noise — healthy-request- এর million-copy-র forensic-value শূন্য। Tail-sampling-এর চুক্তি বুদ্ধিমান: errors/outliers 100%, boring baseline সামান্য। Signal-per-byte-ই metric।

“Trace-id ঢোকানো মানেই distributed tracing — debugging সহজ।”

ID শুধু thread; গাছ parent-edge-এ গড়ে, পড়ে critical-path lens-এ। Broken propagation, inconsistent span-name, tag-cardinality explosion — ‘tracing আছে তবু কিছু দেখা যায় না’-র তিন classic কারণ।

“OpenTelemetry নিজেই tracing-backend — install করলেই UI পাব।”

OTel API/SDK/Collector instrumentation আর transport-এর standard; storage, query-language, waterfall-UI সব backend-এর কাজ (Jaeger, Tempo, commercial)। Promise উল্টোটাই: backend-swappable — antenna OTel, receiver আলাদা কিনতে হয়।

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

1

Span-এর core-field আর traceparent-এর চার region — মিলিয়ে লিখুন। কোনটা প্রতি hop-এ বদলায়?

স্মরণ

Span: trace_id, span_id, parent_id, name, start/duration, tags। traceparent: version, trace-id (16B), parent-id (8B), flags (bit0 sampled)। প্রতি hop-এ parent-id বদলায় — immediate-caller-র span_id; trace-id constant।

2

Queue-boundary-তে HTTP-header propagation কেন fails, context যায় কোথায়? Consumer-span-এর parent কে?

যুক্তি

Queue-hop-এ live-connection নেই — header transport নেই। Context যায় message-carrier-এ: producer inject, consumer extract। Consumer-span parent = producer-র publish-span — causality টিকে, timing decoupled।

3

10,000 req/s × ৬ span × 250 byte। Head 1% হলে দিনে কত GB? Error-rate 0.1%-এ 1% head-sample-এ দিনে কত error-trace বাঁচে — tail keep-all-errors-এ?

প্রয়োগ

Raw 15 MB/s; 1% → ~12.7 GB/দিন (raw ~1.27 TB থেকে)। Errors: 864k/দিন; 1%-এ ≈ 8,640 — burst-day variance বিশাল, minute-এ শূন্যও। Tail keep-all: 864k-ই — দাম buffer+compute।

4

Parallel children A 40 ms, B 350 ms, C 90 ms; parent duration 360 ms। Critical path কোনটা? C-কে অর্ধেক করলে end-to-end? B-কে?

যুক্তি

Path parent→B। C-halving: অপরিবর্তিত ~360 ms — C path-এ ছিলই না। B-halving: নতুন ceiling ≈ parent-self + max(A,C) ≈ 100 ms — প্রায় 3.6× লাভ। Optimize critical-path member-কেই; বাকি optimization দৃশ্যমানই হয় না।

5

200 service, 50k req/s platform-এ tracing-rollout-এর প্রথম ৩০ দিন: কোথা থেকে শুরু, কী sampling, কী success-metric?

ডিজাইন

Week-1: top-revenue-path-এর ৫ service-এ instrumentation + propagation — depth-first। Week-2: collector + tail-sampling (errors/slow 100%, baseline 2%) + log-এ trace_id। Week-3: dependency-map + critical-path dashboards। Week-4: expansion-playbook + span-naming RFC। Metric: postmortem-এ ‘tracing found root-cause’ count — coverage-% না, answer-rate।

এরপর কী

Module wrap — আর সামনের পথ

Level ৯ শেষ — module-arc ফিরে দেখুন: fallacies (‘network reliable, latency zero’) প্রতিটা লেসনের প্রেত থেকে clocks (physical-time অবিশ্বস্ত, happens-before/vector-clock-এ causality), consensus (CAP, quorum, Raft, saga), operations (locks, queues, event-sourcing), শেষে scale আর observability — gossip/CRDT convergence, sharding-yajna, আর আজ tracing-এর আলো, যা না থাকলে বাকি সব অন্ধকারে চলে।

সামনে Level ১০ — Security: environment নিজেই hostile — authN/authZ-গণিত, TLS-handshake-র wire-anatomy (networking-জ্ঞান ফিরবে), threat-modeling, hash/MAC থেকে JWT-session, secret-management, trust-boundary আঁকার শিল্প। Consensus-cluster যত সুন্দর, প্রথম malicious-node-এর কাছে তত মূল্যবান — security-module সেটাই শেখাবে। দেখা হবে Level ১০-এ।

আরও পড়ুন