Message Queues — Kafka আর RabbitMQ-র দুই দর্শন
Message Queues: Kafka and RabbitMQ
Queue শেষ-হওয়া message মুছে দেয়, log চিরকাল রেখে reader-কেই সময় ঘোরাতে দেয় — RabbitMQ আর Kafka-র দুই দর্শন, push-বনাম-pull backpressure, আর partition-parallelism-এর গণিত।
আগে এটা বুঝি
Checkout-service আর notification-service সরাসরি HTTP-call-এ জুড়লে? Notification down হলে checkout down; Black-Friday spike-এ notification ধীর হলে checkout-ও ধীর; নতুন consumer (fraud-check, analytics) যোগ করতেই checkout-এর code ছোঁয়া লাগে। তিন সমস্যার এক ঔষধ — মাঝে broker: checkout কাজটা message-এ মুড়িয়ে ছুঁড়ে দেয়, কে কখন নেবে তা নিয়ে আর মাথা ঘামায় না।
কিন্তু broker বলতে দুইটা প্রায়-বিপরীত দর্শন। RabbitMQ বলে: আমি বুদ্ধিমান router — message ছাঁটাই করব, ঠেলে দেব, receipt নেব, প্রয়োজনে ফেরত আনব। Kafka বলে: আমি মূর্খ — শুধু append-only log; কে কী কোথা থেকে পড়বে, দু’বার পড়বে কি না, সব তোমার ইচ্ছা। দুই দর্শনের যন্ত্র, trade-off আর ভুল-ব্যবহার — আজকের পাঠ।
মূল ধারণা
Broker-এর তিন কাজ
| ভূমিকা | যন্ত্র | উপকার |
|---|---|---|
| Temporal decoupling | Consumer অফলাইন থাকলেও message জমে থাকে | Service-গুলো একসাথে up থাকতে বাধ্য নয় |
| Load leveling | Spike buffer-এ জমে, consumer নিজের গতিতে খায় | Burst পেছনের tier-এ propagate হয় না |
| Failure isolation | Consumer crash → message redeliver | In-flight কাজ হারায় না |
RabbitMQ — smart broker, dumb consumer
producer ──publish──► EXCHANGE ──route──► QUEUE ──push──► CONSUMER
direct: exact-key | topic: pattern | fanout: broadcast
◄────────────ack──────────────┘- Exchange routing-এর সব বুদ্ধি — consumer শুধু queue-subscribe করে, কোথা থেকে কী এলো তাতে তার মাথা নেই; per-message routing-flexibility অসাধারণ, দাম broker-এর state-heavy জীবন (routing-table, unacked-tracking)।
- Push delivery: broker message ঠেলে দেয়;
prefetch-window-ই backpressure — window full মানে broker ওই consumer-কে আর পাঠায় না। - Ack-delete:
basic.ack-এ message মুছে যায়; crash/timeout-এ unacked requeue — at-least-once, duplicate-এর দায় consumer-এর (আগের লেসন!)।
Kafka — dumb broker, smart consumer
producer ──produce(key,val)──► PARTITIONED LOG
├─ p0: [k7][k2][k9]... ← append-only
├─ p1: [k1][k4][k3]...
consumer ──PULL(offset)──◄──────┴─ p2: ...
commit(offset) ──► group-storage: {p0:41, p1:38}- Append-only log: write হয় শেষে, modify/delete নেই। Broker জানেই না message ‘processed’ হয়েছে কি না — concept-টাই ওর ডিকশনারিতে নেই।
- Offset: consumer নিজের reading-position নিজে commit করে; crash → ফিরে এসে সেই offset থেকে আবার — at-least-once, idempotency-ই রক্ষাকর্তা।
- Replayability: offset পিছনে ঘুরিয়ে দিলেই history re-process — bug-fix-এর পরে গতকালের data আবার! Queue-তে অসম্ভব (already deleted); এটাই log ≠ queue-এর মূল পার্থক্য — queue communication-mechanism, log system-of-record।
- Retention time/size-based (
retention.ms=7 days) — delete consumer-এর ack-তে নয়, ঘড়িতে।
Consumer groups — sharing আর rebalance-storm
একই topic-এ একাধিক consumer-instance চললে group partition ভাগ করে দেয়: ৩ partition, ৩ instance → একজন একটা করে; instance মরলে তার partition বাকিরা নেয়। Group-এর ভেতরে = load-balancing; ভিন্ন group = সবাই পুরো stream-এর নিজের copy (fanout)।
Rebalance-trigger ছোট: join/leave, heartbeat-miss, session-timeout।
আর rebalance মানে stop-the-world — assignment নতুন হওয়া পর্যন্ত
consumption থামে। max.poll.interval.ms heavy-processing-এর সাথে মিললে
ঘূর্ণি: pause → rebalance → restart → pause — rebalance-storm।
Retention বনাম ack-delete — দুই দর্শনের চুক্তিপত্র
| প্রশ্ন | Queue (RabbitMQ) | Log (Kafka) |
|---|---|---|
| Message কবে মরে? | Ack-এ | Time/size-limit-এ |
| Slow consumer-এর দাম | Queue-depth বাড়ে, memory-pressure | Disk-space বাড়ে (সস্তা!), consumer-lag metric |
| পুরনো data আবার পড়া | অসম্ভব | Offset-reset |
| Storage দর্শন | Working-memory (transit) | System-of-record (history) |
ভেতরে কী ঘটছে
Kafka কেন এত দ্রুত — io-models-এর প্রতিশ্রুতি পূরণ
আগের module-এ io-models লেসনের মূল শিক্ষা ফিরে আসে:
- Sequential I/O: append-only log মানে disk-এ সোজা রেখায় লেখা — random-write-এর তুলনায় শতগুণ।
- Page cache: Kafka OS page-cache-কেই বিশ্বাস করে, নিজের application-cache প্রায় নেই; hot-stream পুরো memory-speed-এ।
- Zero-copy: consumer-delivery-তে
sendfilesyscall — data kernel-space থেকে socket-buffer-এ সরাসরি, user-space copy বাদ। তিনটাই mechanical sympathy — OS-এর যন্ত্র যেমন কাজ করে, design তেমনই।
Push বনাম pull — কে গতি ঠিক করে
PUSH (RabbitMQ): broker message ধরিয়ে দেয়; pace = prefetch-window
(window-ই throttle); latency কম
PULL (Kafka): consumer batch করে টানে; pace = poll-rate — প্রাকৃতিক
backpressure, batch-optimize-এর সুযোগ; idle-এ polling-
overhead (long-polling-এ মেটানো)Push-model-এ slow consumer-কে broker আটকে রাখে (window); pull-model- এ consumer নিজেই পিছিয়ে যায় — আর সেই পেছনে-থাকাটা first-class observable metric: consumer lag = log-tip − committed offset; production-Kafka monitoring-এর প্রাণ।
Rebalance ভেতরে কেমন
Group coordinator (একটা broker) member-heartbeat track করে; timeout/ join/leave-এ rebalance-protocol: সবাই consumption থামায়, assignment-generation বাড়ে, নতুন partition→member map broadcast, আবার শুরু। Safe কিন্তু world-stop — heartbeat-interval, session-timeout, max.poll.interval তিন knob production-tuning-এর প্রাণ।
RabbitMQ-র ব্যর্থতা-পথ: nack আর DLX
basic.nack(requeue=true) → message queue-র সামনে ফেরে — সঙ্গে
সঙ্গে আবার deliver হয়ে poison-loop (একই bad message infinite redelivery!)।
শিল্প-সমাধান dead-letter-exchange: retry শেষ হলে message চলে
যায় DLQ-তে — autopsy-র জন্য। Poison-handling broker-feature দিয়ে
না, discipline দিয়ে হয়।
উদাহরণ
একই order-events, দুই ব্রোকারে
workload: order-events; consumer processing-মাঝখানে crash; পরে fix-deploy।
RABBITMQ (ack-based):
t0 unacked window (prefetch=100) full → broker পাঠানো বন্ধ
t1 connection dead → ১০০ unacked REQUEUE → queue=৫০০
t2 নতুন consumer → push শুরু, ack-প্রতি delete
✔ হারানো নেই ✕ processed-ও ফিরে এলো (duplicate!)
✕ পুরো history আর দেখা যাবে না
KAFKA (offset-based):
t0 group A committed offset=1200, log-tip=1700
t1 consumer crash — broker কিছুই বোঝে না, log চলতেই থাকে
t2 fix-deploy, consumer ফিরল → poll(1200) → সেখান থেকে আবার
t3 ops: 'গতকালের ২৪ ঘণ্টা re-process করো' → offset reset → replay!
✔ history durable ✔ replay first-class
✕ duplicate effects → consumer-side idempotency (আগের লেসন)দুই path-ই বৈধ — প্রশ্ন হলো workload-এর দাবি: transient-work-distribution (RabbitMQ sweet-spot) না event-history + replay (Kafka sweet-spot)?
ছবিতে
┌──────────────────────────────┐
order.created ──► ┌──┴──┐ direct(key=order.created)│
[producer] │ EX ├──binding──────────► ┌─────────┐
│ │ │ Q-orders│──push──► order-svc
└──┬──┘ fanout(broadcast) └─────────┘
│ ┌──────────┐
└───────────────► │ Q-audit │──push──► audit-svc
prefetch-window └──────────┘
◄──────ack──────── (unacked crash → requeue → duplicate)LOG (append-only, retention 7d):
p0: ▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣ tip=20
p1: ▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣▣ tip=16
p2: ▣▣▣▣▣▣▣▣▣▣▣▣ tip=12
group A (3 instances, এক-এক partition): group B (analytics, slow):
a1 ← p0 (offset 17, lag 3) b1 ← p0,p1,p2 (offset 5.., lag বড়)
a2 ← p1 (offset 16, lag 0) ← এক instance সব টানে, pull-pacing
a3 ← p2 (offset 12, lag 0)
rebalance: a2 dies → coordinator ভাঙে assignment → a1,a3 নতুন mapনিজে চালিয়ে দেখুন
Twin mini-brokers — একই workload push-queue আর pull-log-এ
"""mini_brokers.py — push(queue) বনাম pull(log), একই event-stream"""
import collections
class TinyQueue:
"""RabbitMQ-স্বাদ: push + prefetch-window + ack-delete + requeue"""
def __init__(self, prefetch=2):
self.q = collections.deque()
self.unacked = collections.defaultdict(list)
self.prefetch = prefetch
def publish(self, msg):
self.q.append(msg)
def try_deliver(self, cid): # broker-এর push attempt
if len(self.unacked[cid]) >= self.prefetch:
return None # window-full → block!
if self.q:
m = self.q.popleft()
self.unacked[cid].append(m)
return m
return None
def ack(self, cid, msg):
self.unacked[cid].remove(msg) # delete forever
def crash(self, cid): # unacked → requeue
self.q.extendleft(reversed(self.unacked.pop(cid, [])))
class TinyLog:
"""Kafka-স্বাদ: append-only + pull-offsets + commit + replay"""
def __init__(self):
self.log, self.offsets = [], {}
def produce(self, msg):
self.log.append(msg)
def poll(self, group, limit=3): # consumer pulls
off = self.offsets.get(group, 0)
return off, self.log[off:off + limit]
def commit(self, group, new_offset):
self.offsets[group] = new_offset
def seek(self, group, offset): # replay-API!
self.offsets[group] = offset
EVENTS = [f'order-{i}' for i in range(50)]
# ── scenario A: consumer crash mid-flight — দুই ব্রোকারের প্রতিক্রিয়া ──
tq = TinyQueue(prefetch=4)
for e in EVENTS:
tq.publish(e)
got = [tq.try_deliver('c1') for _ in range(4)]
tq.ack('c1', got[0]); tq.ack('c1', got[1]) # প্রথম দুটো process-শেষ
# got[2], got[3] processing-এই ছিল → CRASH, ack হয়নি
tq.crash('c1') # unacked → requeue!
print(f'[queue] delivered={len(got)}, backlog-after-crash={len(tq.q)} '
f'(requeue সহ) — duplicate-redelivery incoming')
tl = TinyLog()
for e in EVENTS:
tl.produce(e)
tl.commit('g1', 4)
print(f'[log] committed=4, tip={len(tl.log)} — data immutable, replay-ready')
# ── scenario B: slow consumer — কে থামায়, কে জমায় ──
tq2, tl2 = TinyQueue(prefetch=2), TinyLog()
for e in EVENTS:
tq2.publish(e); tl2.produce(e)
taken = sum(1 for _ in range(5) if tq2.try_deliver('c-slow'))
print(f'[push] window=2: ৫টা deliver-request-এ broker দিল {taken}টা '
f'(বাকি queue-তে) — throttle broker-এর হাতে')
_, batch = tl2.poll('g-slow', limit=5)
tl2.commit('g-slow', 5)
lag = len(tl2.log) - tl2.offsets.get('g-slow', 0)
print(f'[pull] consumer এক poll-এই নিল {len(batch)}টা; '
f'pace নিজের — lag={lag} (monitoring-এর metric)')
# ── scenario C: replay — queue-তে অসম্ভব, log-এ এক-line ──
tl2.seek('g-slow', 0)
_, again = tl2.poll('g-slow', limit=3)
print(f'[replay] seek(0)-এর পরে আবার পড়া গেল: {again}')নমুনা output:
[queue] delivered=4, backlog-after-crash=48 (requeue সহ) — duplicate-redelivery incoming
[log] committed=4, tip=50 — data immutable, replay-ready
[push] window=2: ৫টা deliver-request-এ broker দিল 2টা (বাকি queue-তে) — throttle broker-এর হাতে
[pull] consumer এক poll-এই নিল 5টা; pace নিজের — lag=45 (monitoring-এর metric)
[replay] seek(0)-এর পরে আবার পড়া গেল: ['order-0', 'order-1', 'order-2']তিনটা পাঠ:
- Queue requeue duplicate দেয় (processed-ও ফের আসে); log-এ ‘duplicate’ আসে uncommitted-offset re-poll থেকে — mechanism ভিন্ন, ফল এক: idempotent consumer non-negotiable।
- Push-এ throttle broker-এর, pull-এ consumer-এর; replay queue-তে impossible (deleted), log-এ এক-line API।
Crash-before-ack queue-তে duplicate-redelivery দেয়, log-এ uncommitted-replay দেয়; slow-consumer-এ push prefetch-block করে, pull lag জমায়; আর offset-reset-ই replay-এর দরজা — সব চোখে হাতে।
নিজে বানান
MiniLogBroker — partitioned log, offsets, groups, lag metrics, replay
- Partitioned append — key-hash placement, per-partition sequence
- Group offset-store — consumer-position per partition
- Poll + commit — bounded batches, explicit progress
- Lag metric — log-tip minus committed offset, per partition
- Seek/replay — offset reset API আর same-key ordering proof
"""minilog.py — single-node partitioned log broker (~120 line)"""
import hashlib
def partition_for(key, count):
h = int(hashlib.md5(str(key).encode()).hexdigest(), 16)
return h % count
class MiniLogBroker:
def __init__(self, partitions=3):
self.logs = [[] for _ in range(partitions)] # append-only!
self.groups = {} # g -> {pid: offset}
def produce(self, key, value):
p = partition_for(key, len(self.logs))
self.logs[p].append((key, value))
return p, len(self.logs[p]) # (partition, next-offset)
def offsets(self, group):
return self.groups.setdefault(
group, {i: 0 for i in range(len(self.logs))})
def poll(self, group, max_records=4):
"""bounded pull — মোট max_records, partition ঘুরে ঘুরে"""
offs = dict(self.offsets(group))
batch, progress = [], True
while progress and len(batch) != max_records:
progress = False
for pid in range(len(self.logs)):
if len(batch) == max_records:
break
start = offs[pid]
if start >= len(self.logs[pid]):
continue
batch.append((pid, start, self.logs[pid][start]))
offs[pid] = start + 1
progress = True
return batch
def commit(self, group, positions): # consumer-ই progress-
self.offsets(group).update(positions) # এর একমাত্র মালিক
def lag(self, group):
offs = self.groups.get(group, {})
return {pid: len(log) - offs.get(pid, 0)
for pid, log in enumerate(self.logs)}
def total_lag(self, group):
return sum(self.lag(group).values())
def seek(self, group, pid, offset): # replay-API!
self.offsets(group)[pid] = max(0, offset)
# ── benchmark: 1 fast + 3 slow consumer-groups — lag dynamics ──
def benchmark():
broker = MiniLogBroker(partitions=3)
TICKS, PRODUCE_PER_TICK = 30, 8
SPEEDS = {'fast': 24, 'slow-1': 4, 'slow-2': 2, 'v-slow': 1}
print(f"{'tick':>4} {'produced':>8} " +
" ".join(f"{g:>7}" for g in SPEEDS))
produced = 0
for t in range(1, TICKS + 1):
for i in range(PRODUCE_PER_TICK): # steady traffic
broker.produce(f'user-{i % 5}', f'e{t}-{i}')
produced += PRODUCE_PER_TICK
row = ''
for g, speed in SPEEDS.items():
batch = broker.poll(g, max_records=speed) # pull, own pace
pos = dict(broker.offsets(g))
for pid, off, rec in batch:
pos[pid] = off + 1 # process → commit
broker.commit(g, pos)
row += f"{broker.total_lag(g):>7} "
if t == 1 or t % 10 == 0:
print(f"{t:>4} {produced:>8} {row}")
p0, _ = broker.produce('user-99', 'a') # same-key ordering
ok, prev = True, p0
for i in range(5):
p, nxt = broker.produce('user-99', f'step{i}')
ok = ok and p == p0 and nxt > prev
prev = nxt
print(f"\nsame-key -> same-partition, in-order: {ok}")
before = broker.total_lag('v-slow')
broker.seek('v-slow', 0, 0) # replay demo
print(f"seek(v-slow, p0->0): lag {before} -> "
f"{broker.total_lag('v-slow')} (replay-দায় idempotency)")
benchmark()নমুনা output:
tick produced fast slow-1 slow-2 v-slow
1 8 0 4 6 7
10 80 0 40 60 70
20 160 0 80 120 140
30 240 0 120 180 210
same-key -> same-partition, in-order: True
seek(v-slow, p0->0): lag 216 -> 246 (replay-দায় idempotency)তিনটা পাঠ:
- Fast lag শূন্য-ই; slow-group-গুলোর lag rate×time-এ linear — production alert-threshold-এর গণিত এখানেই।
- Same-key test প্রমাণ করে hash-placement-ই ordering-এর মূলধন।
- Seek-পরবর্তী lag-লাফ = replay-এর দাম — পুরনো record আবার process হবে, effect অবশ্যই idempotent (আগের লেসনের echo)।
নিজে বাড়ান:
- Rebalance simulate করুন — v-slow-এর এক partition অন্য group-member-কে দিন (assignment-map যোগ করে)
- Compacted-log mode — একই key-র শেষ record ছাড়া বাকি purge; lag-ganit তাতে কেমন বদলায় দেখুন
- Dead-letter path — malformed record আলাদা partition-এ quarantine
বাস্তব সিস্টেমে
বাস্তব জগতে
LinkedIn-এ Kafka-র জন্ম। Activity-feed pipeline হাতলে দিয়ে যেত না — ২০১১-র এই paper-project-ই আজ finance, ride-sharing, IoT-সব জায়গার event-backbone; log-darshan distributed-systems-এর সবচেয়ে সফল export-গুলোর একটা।
RabbitMQ-র দুর্গ। Task-distribution, RPC-over-message, complex per-message routing — banking payment-fan-out, CI job-dispatch; management-UI আর mature-routing এখনো এর জয়ের জায়গা।
CDC pipelines। Debezium → Kafka → downstream-everything — database-WAL event-stream হয়ে cache-invalidate, search-index, warehouse-feed চালায়; saga-লেসনের outbox-relay-এরই production-scale রূপ।
Consumer-lag আর SRE। Burrow, exporter-metrics, lag-alerting — Kafka-ops- এর প্রথম দিনই lag-dashboard; rebalance-storm postmortem ইন্টারনেটে আলাদা genre। DLQ hygiene। Poison-autopsy, replay-tooling, schema-registry — দুই ecosystem-এই pattern; মাপকাঠি একটাই: routing-flexibility না history-replay।
যে ভুলগুলো সবাই করে
“Kafka মানে আধুনিক, faster RabbitMQ — migration-ই upgrade।”
দুটো ভিন্ন abstraction: RabbitMQ queue (delete-on-ack, per-message routing), Kafka log (retention, partition-keyed streams)। Low-volume complex-routing task-queue Kafka-তে migrate করলে পাবেন extra-ops-burden, routing-ক্ষমতা হারাবেন; high-throughput replayable-event-stream RabbitMQ-তে আটকে যাবেন। Tool-choice = semantic-fit, fashion নয়।
“Kafka সবসময় ordering গ্যারান্টি দেয়।”
Ordering শুধু partition-এর ভেতরে, তাও producer-এর একই key ব্যবহার করলে। Keyless-produce বা retry-জনিত out-of-order batch মানেই ordering গ্যারান্টি শূন্য; global-order চাইলে single-partition — যেটা parallelism হারানো। Design-principle: ordering-need scope করে key-design করুন, default-এ নয়।
“Ack-delete-ছাড়া তো disk ফুলবেই — retention Kafka-র দুর্বলতা।”
উল্টো। Sequential-append + cheap-disk + time-based-retention মানে storage-cost predictable; queue-র ack-delete-এ ভুলে-যাওয়া consumer একটাই পুরো system hostage (আগের slot-lesson-এর WAL-retention-এর পরিচিত ছায়া)। আর ‘disk ফুলবে’-র উত্তর retention-policy — ৭ দিন, ৭ GB, যা দরকার।
“Consumer-group rebalance সাধারণ operation — মাঝে মাঝে হলেই চলবে।”
Rebalance stop-the-world: পুরো group consumption freeze, তারপর catch-up burst। Frequent rebalance মানে effective-throughput ভাঙা আর downstream-latency spike। Session-timeout, max.poll.interval, heartbeat — তিন knob মিলিয়ে tune করা mandatory; storm-mode ধরা পড়লে প্রথমেই ওখানেই তাকান।
বুঝেছেন কি না দেখুন
1RabbitMQ-র message-journey পাঁচটা স্টেজে — publish থেকে delete।
স্মরণ
Producer publish করে exchange-এ → binding-rule-এ route → queue-তে store → broker consumer-কে push (prefetch-window মেনে) → consumer ack → delete। Nack/crash হলে unacked requeue — at-least-once।
2‘Log ≠ queue’ — এক বাক্যে মৌলিক পার্থক্য, আর একটা capability যা
শুধু log-এ সম্ভব।
যুক্তি
Queue message-কে transit-item মনে করে (delivered=done, মুছে ফেলে); log message-কে immutable-record মনে রাখে, progress reader-এর offset-এ। শুধু log-এ সম্ভব: offset-reset replay — bug-fix-এর পরে পুরনো data পুরোটা re-process।
3৬ partition, key=user-id। Consumer-group-এ ৮ instance। কয়টা
instance কাজ করবে, বাকিরা কী করবে? Ordering-এর ফল কী?
প্রয়োগ
Partition-ই assignment-unit — ৬ instance এক-এক partition, ২টা idle; scaling-cap = partition-count। Ordering: user-এর সব event hash-এ এক partition-এ → প্রতি-user order অটুট; cross-user global-order নেই — সাধারণত দরকারও নেই।
4Slow consumer-এ push-model আর pull-model কীভাবে আলাদাভাবে ‘চোখে
পড়ে’? Metric কী, আর কে থামায়?
যুক্তি
Push: broker prefetch-window full করে পাঠানো বন্ধ — signal = unacked/window। Pull: consumer নিজের গতিতে poll, পিছিয়ে যায় — signal = consumer-lag (tip − committed offset)। Push-এ থামায় broker, pull-এ consumer; lag-ই monitoring-এর প্রাণ।
5Payment-events: audit-team ৭ দিনের সব event পরে re-process করতে
চায়; fraud-check real-time, per-card ordering জরুরি; email-notification
fire-and-forget। Topic/partition/broker কীভাবে ভাববেন?
ডিজাইন
Event-stream-এ history-দাবি → Kafka, retention ≥ ৭ দিন; key=card-id → per-card ordering। তিন consumer-group: fraud (real-time, lag-alert), audit (replay-friendly), notification (skip-on-lag policy)। Email fire-and-forget Kafka ছাড়াও চলে — RabbitMQ/SMTP queue-ই যথেষ্ট; choice-per-consumer, এক-size-fits-none।
এরপর কী
এরপর কী
Broker-এ message flow দেখলাম — সেই message-গুলোই যদি system-এর সত্যের উৎস হয়? পরের লেসনে event-driven architecture আর event sourcing: current-state rows-কে append-only event-stream থেকে derive — database-এর WAL ইতিমধ্যেই যে দর্শন মানে; aggregate-replay, snapshot-checkpoint, CQRS-projection, immutable log বনাম GDPR-delete-এর দ্বন্দ্ব।
আরও পড়ুন
- Apache Kafka Documentation — Design: The Log, Consumer Groups — Apache Software Foundation · Partitioned log, offset-management, group-coordination-এর official specification
- Designing Data-Intensive Applications, Chapter 11 — Stream Processing — Martin Kleppmann · Log-ভিত্তিক messaging, backpressure আর delivery-semantics-এর প্রামাণ্য আলোচনা
- Microservices Patterns — Messaging and events chapters — Chris Richardson · Broker নির্বাচন, DLQ, ordered-delivery trade-off-এর শিল্প-দৃষ্টি