এই পর্বে যা শিখবেন
- Queue, topic, event bus আর stream চেনা, আর তিন cloud-এর সার্ভিসে সেগুলো মেলানো
- Visibility timeout, retry, redrive আর dead-letter queue ঠিকভাবে সাজানো
- Ordering-এর দাম, FIFO-র সীমা, batching আর at-least-once-এর বাস্তবতা বোঝা
- Topic দিয়ে fan-out, routing rule, event schema আর CloudEvents ব্যবহার করা
- Orchestration আর choreography-র মধ্যে বেছে নেওয়া, আর saga-র compensation সাজানো
- Queue depth দিয়ে autoscale করা, আর অনুমিত দামে খরচের হিসাব করা
এই পর্বের সংখ্যা সম্পর্কে: এখানে কোনো আসল cloud account চালানো হয়নি। Simulator-গুলো ছোট মডেল, আসল সার্ভিস নয়। Default, limit আর দাম provider ও সময়ভেদে বদলায়, তাই প্রতিটি সংখ্যা “লেখার সময়ে” ধরে নিন আর ব্যবহারের আগে নথি দেখে নিন। সব দাম অনুমিত।
১ · ফোনে দাঁড়িয়ে থাকা, না স্লিপ রেখে যাওয়া
একটি কাল্পনিক অনলাইন বাজার অ্যাপের কথা ভাবুন। ক্রেতা “অর্ডার করুন” চাপলেন। এরপর পাঁচটি কাজ: stock ধরে রাখা, টাকা কাটা, courier ডাকা, email পাঠানো, আর হিসাবের দলের জন্য তথ্য তোলা। প্রথম সংস্করণে order service পাঁচটি সার্ভিসকে পরপর HTTP দিয়ে ডাকে, আর উত্তরের জন্য দাঁড়িয়ে থাকে। একে বলে synchronous call.
হিসাবটি দেখুন। ধরুন প্রতিটি সার্ভিসের availability ৯৯.৯%. পাঁচটি সবই লাগলে পুরো অর্ডারের availability ০.৯৯৯⁵ ≈ ৯৯.৫%. মানে ৩০ দিনের মাসে প্রায় ৩.৬ ঘণ্টা অর্ডার আটকে থাকতে পারে। আর email সার্ভিস ধীর হলে ক্রেতা ঘুরতে থাকা চাকা দেখেন, যদিও টাকা কাটা ঠিকই হয়েছে। একটি ছোট সার্ভিস পুরো দরজা বন্ধ করে দিল। সার্ভিসগুলো খুব শক্ত করে বাঁধা।
ছোট হোটেলের ওয়েটারের কথা ভাবুন। তিনি যদি প্রতিটি অর্ডার নিয়ে রান্নাঘরে গিয়ে দাঁড়িয়ে থাকেন, ততক্ষণ বাকি টেবিল অপেক্ষায়। ভালো হোটেলে ওয়েটার স্লিপ লিখে বোর্ডে ঝুলিয়ে দেন, আর ফিরে আসেন। বাবুর্চি নিজের গতিতে স্লিপ তোলেন। ভিড় হলে স্লিপ জমে, কিন্তু অর্ডার হারায় না। একজন বাবুর্চি অসুস্থ হলেও স্লিপ বোর্ডেই থাকে।
সেই বোর্ডই message queue. পাঠানো পক্ষ বার্তা রেখে চলে যায়, কাজ করা পক্ষ পরে নেয়। এই আলগা জোড়াকে বলে loose coupling. এতে তিনটি লাভ: দুই পক্ষকে একসাথে চালু থাকতে হয় না, ভিড়ের চূড়া সমান হয়ে যায়, আর একটি অংশ পড়লে বাকিটা চলে।
দামও আছে। ক্রেতা তখনই চূড়ান্ত ফল জানেন না, অর্থাৎ eventual consistency. বার্তা দুবার আসতে পারে। ক্রম উল্টাতে পারে। ভুল ধরতে এক জায়গার log যথেষ্ট নয়। তাই সবকিছু queue-তে পাঠানো ঠিক নয়। ক্রেতার তখনই উত্তর দরকার হলে, যেমন “কার্ড গৃহীত হলো কি না”, সরাসরি ডাকাই ঠিক। উত্তর পরে দিলে চলে এমন কাজই queue-তে যায়।
তত্ত্ব আগেই আছে: message queue, pub/sub আর event-driven নকশা, log-ভিত্তিক streaming, delivery semantics আর workflow engine. এখানে সেগুলো আবার প্রমাণ করব না। এই পর্বের প্রশ্ন ব্যবহারিক: কোন managed সার্ভিস বাছবেন, কোন setting কীভাবে বসাবেন, কী মাপবেন, আর বিল কত আসবে।
২ · চার আকৃতি — queue, topic, event bus আর stream
Cloud-এর তালিকায় অনেক নাম। কিন্তু আকৃতি মাত্র চারটি। প্রথম প্রশ্নটি সবসময় একই: একটি বার্তা কতজন পাবে, আর কে ঠিক করবে?
Queue
হোটেলের স্লিপ বোর্ড। অনেক worker, কিন্তু একটি স্লিপ একজনই নেন। কাজ শেষে স্লিপ ফেলে দেওয়া হয়। একে বলে competing consumers.
একটি কাজ, একজনTopic
পাড়ার মাইকিং। একবার ঘোষণা, যাঁরা শুনছেন সবাই শোনেন। Topic-এ সদস্যরা আগে থেকে যুক্ত থাকেন, প্রত্যেকে নিজের কপি পান।
একটি খবর, সবাইEvent bus
ডাকঘরের বাছাই ঘর। প্রতিটি চিঠির ওপরের লেখা দেখে থলে ঠিক হয়। Event bus-এ নিয়ম ঘটনার ভেতরের তথ্য দেখে গন্তব্য বাছে।
নিয়ম বলে কে পাবেStream
ব্যাংকের খতিয়ান খাতা। লেখা মোছে না। প্রত্যেক পাঠক নিজের পাতার নম্বর মনে রাখেন, চাইলে আগের পাতায় ফিরতেও পারেন। এই পাতার নম্বরই offset, আর এমন খাতাই event stream.
খাতা, ঘুরে পড়া যায়Queue, topic আর event bus পাশাপাশি, নিচে stream
Who gets one message? One worker, every subscriber, or whoever matches a rule
| আকৃতি | কে পায় | পড়ার পরে বার্তা | AWS | Azure | Google Cloud |
|---|---|---|---|---|---|
| Queue | একজন worker | Delete হলে শেষ | SQS | Service Bus queue | Pub/Sub subscription-এ একাধিক worker; দেরিতে চালানোর কাজে Cloud Tasks |
| Topic | প্রতিটি subscriber-এর নিজের কপি | রাখে না, বা সীমিত সময় | SNS | Service Bus topic | Pub/Sub topic |
| Event bus | নিয়ম যাকে বাছে | সাধারণত রাখে না; archive আলাদা সুবিধা | EventBridge | Event Grid | Eventarc |
| Stream | প্রতিটি পাঠক দল, নিজের offset থেকে | Retention শেষ না হওয়া পর্যন্ত থাকে | Kinesis Data Streams, MSK (managed Kafka) | Event Hubs (Kafka-সমমান endpoint-সহ) | Pub/Sub-এর seek ও retention; managed Kafka |
মানচিত্রটি মোটা দাগে সত্য, হুবহু নয়। এক সার্ভিস একাধিক ঘরে বসে। Google Pub/Sub একাই topic আর queue-র কাজ করে: topic-এ পাঠানো হয়, আর প্রতিটি subscription ভেতরে queue-র মতো। Service Bus-এ queue আর topic দুটোই আছে। নাম মিলিয়ে নয়, আচরণ মিলিয়ে বাছুন।
বাছাইয়ের ছোট নিয়ম:
- একটি কাজ করিয়ে নিতে হবে (“এই ছবি ছোট করুন”): queue.
- একটি ঘটনা জানাতে হবে, শ্রোতা অনেক (“অর্ডার হয়েছে”): topic, প্রতিটি শ্রোতার সামনে নিজের queue.
- অনেক উৎসের ঘটনা, ভেতরের তথ্য দেখে পথ বাছতে হবে: event bus. Cloud-এর নিজের ঘটনা, যেমন storage-এর event notification, প্রায়ই এখানে আসে।
- বিপুল প্রবাহ, ক্রম লাগবে, আবার পড়তে হবে: stream. চলমান পাঠককে বলে stream consumer. বিস্তারিত তত্ত্ব log-streaming পর্বে.
৩ · একটি বার্তার জীবন — visibility timeout আর at-least-once
সবচেয়ে গুরুত্বপূর্ণ কথাটি প্রথমেই: Queue থেকে বার্তা নিলে সেটি মোছে না। বার্তা শুধু কিছুক্ষণের জন্য অন্যদের চোখের আড়ালে যায়। Worker কাজ শেষ করে আলাদা করে delete পাঠালে তবেই মোছে। এই delete-ই acknowledgement. আড়ালের সময়টিকে বলে visibility timeout.
বাবুর্চি বোর্ড থেকে স্লিপ তুলে পকেটে রাখলেন, সাথে একটি ঘড়ি চালু হলো। ৩০ মিনিটে রান্না শেষ করে স্লিপ ছিঁড়ে না ফেললে ম্যানেজার ধরে নেন তিনি পারেননি, আর স্লিপের কপি আবার বোর্ডে ঝুলিয়ে দেন। ম্যানেজার জানেন না বাবুর্চি অজ্ঞান হয়েছেন, না শুধু ধীরে রাঁধছেন। তাঁর হাতে শুধু ঘড়ি।
এই নকশার কারণ সহজ। Worker মাঝপথে মরে গেলে কেউ খবর দেবে না। তাই নীরবতাকেই ব্যর্থতা ধরা হয়। নিচে একটি বার্তার পুরো জীবন দেখুন, যেখানে প্রথম worker মাঝপথে পড়ে যায়।
- Producer
sendorder-101 - W1
receiveপ্রথম চেষ্টা - W2
receiveদ্বিতীয় চেষ্টা - মুছে গেছে
deleteকাজ শেষ
| সময় t | |
| দেখা যাচ্ছে (visible) | |
| কারো হাতে (in flight) | |
| ReceiveCount | |
| আড়ালে থাকবে |
- Send, t = ০। Order service বার্তা পাঠাল। Queue একাধিক copy-তে সেটি লিখে “পেয়েছি” জানাল। এখন বার্তাটি visible, ReceiveCount ০।
- Receive, t = ২। W1 বার্তা নিল। ReceiveCount হলো ১। বার্তা আড়ালে গেল, ঘড়ি বলছে t = ৩২ পর্যন্ত। সাথে W1 একটি রসিদ পেল, যা দিয়ে পরে delete করতে হয়।
- W1 মরে গেল, t = ১২। সংখ্যাগুলো দেখুন: কিছুই বদলায়নি। Queue জানেই না। ঘড়ি চলছে, এই ২০ সেকেন্ড অর্ডারটি কারো হাতে নেই।
- Timeout শেষ, t = ৩২। Delete আসেনি, তাই বার্তা আবার visible. ReceiveCount ১-ই থাকল, কারণ এটি নেওয়ার সংখ্যা গোনে, ব্যর্থতার নয়।
- দ্বিতীয় receive, t = ৩৩। W2 নিল। ReceiveCount ২, নতুন আড়াল t = ৬৩ পর্যন্ত। এটিই retry, কেউ আলাদা করে চালায়নি।
- কাজ, t = ৪১। W2 আগে idempotency key মিলিয়ে দেখল এই অর্ডার আগে হয়নি, তারপর টাকা কাটল। Queue এখনো বার্তা ধরে আছে। এই মুহূর্তে W2 মরলে বার্তা ফিরে আসবে, আর key-ই duplicate আটকাবে।
- Delete, t = ৪২। W2 রসিদ দিয়ে delete পাঠাল। এবারই বার্তা শেষ। মনে রাখুন ক্রমটি: আগে কাজ, পরে delete. উল্টো করলে মাঝপথে মরলে কাজটিই হারাবে।
এই আচরণের নাম at-least-once delivery: বার্তা হারায় না, কিন্তু একাধিকবার আসতে পারে। তিন cloud-এ ব্যবস্থাটি একই, নাম আলাদা:
| সার্ভিস | ঘড়ির নাম | লেখার সময়ের default ও সীমা (নথি দেখে নিন) |
|---|---|---|
| SQS | Visibility timeout | Default ৩০ s, সর্বোচ্চ ১২ ঘণ্টা |
| Service Bus | Peek-lock-এর lock duration | Default ১ মিনিট, সর্বোচ্চ কয়েক মিনিট; lock নবায়ন করা যায় |
| Google Pub/Sub | Ack deadline | Default ১০ s, সর্বোচ্চ ৬০০ s; client library বাড়াতে থাকে |
এবার সেই বিপজ্জনক ঘটনা। Worker মরেনি, শুধু ধীর। নিচের simulator-এ W1-এর কাজে ৪৫ সেকেন্ড লাগে, আর timeout ৩০ সেকেন্ড। তিনটি setting পরপর চলবে:
// One order, one queue, two workers. Simulated clock in seconds, no real waiting.
// W1 is slow (45 s of work). W2 is quick (10 s of work).
function run(visibility, useKey) {
const msg = { id: "order-101", visibleAt: 0, receives: 0, deleted: false };
const workers = [{ name: "W1", work: 45, busyUntil: 0 }, { name: "W2", work: 10, busyUntil: 0 }];
const finishing = [], processed = new Set();
let charges = 0;
console.log(`--- visibility ${visibility} s, idempotency key ${useKey ? "ON" : "OFF"} ---`);
for (let t = 0; t <= 120; t++) {
for (const job of finishing.filter(j => j.at === t)) {
if (useKey && processed.has(msg.id)) {
console.log(`t=${t} ${job.w}: key already used, skip the charge`);
} else {
processed.add(msg.id); charges++;
console.log(`t=${t} ${job.w}: CHARGE card (total charges ${charges})`);
}
if (!msg.deleted) { msg.deleted = true; console.log(`t=${t} ${job.w}: delete message`); }
}
const free = workers.find(w => w.busyUntil <= t);
if (!msg.deleted && msg.visibleAt <= t && free) {
msg.receives++;
msg.visibleAt = t + visibility;
free.busyUntil = t + free.work;
finishing.push({ at: free.busyUntil, w: free.name });
console.log(`t=${t} ${free.name}: receive #${msg.receives}, hidden until t=${msg.visibleAt}`);
}
}
console.log(`result: receives=${msg.receives} charges=${charges}\n`);
}
run(30, false); // timeout shorter than the slow worker
run(30, true); // same timing, consumer is idempotent
run(60, false); // timeout longer than the slow workerআউটপুট:
--- visibility 30 s, idempotency key OFF ---
t=0 W1: receive #1, hidden until t=30
t=30 W2: receive #2, hidden until t=60
t=40 W2: CHARGE card (total charges 1)
t=40 W2: delete message
t=45 W1: CHARGE card (total charges 2)
result: receives=2 charges=2
--- visibility 30 s, idempotency key ON ---
t=0 W1: receive #1, hidden until t=30
t=30 W2: receive #2, hidden until t=60
t=40 W2: CHARGE card (total charges 1)
t=40 W2: delete message
t=45 W1: key already used, skip the charge
result: receives=2 charges=1
--- visibility 60 s, idempotency key OFF ---
t=0 W1: receive #1, hidden until t=60
t=45 W1: CHARGE card (total charges 1)
t=45 W1: delete message
result: receives=1 charges=1
প্রথম run-এ charges=2: একজন ক্রেতার কার্ড দুবার কাটা। কেউ ভুল করেনি, দুই worker-ই সুস্থ। W1 t = ৩০-এ আড়াল হারিয়েছে, কিন্তু সে জানে না। দ্বিতীয় run-এ timing হুবহু এক, receives=2-ও এক, কিন্তু key থাকায় charges=1. তৃতীয় run-এ timeout কাজের চেয়ে লম্বা, তাই duplicate জন্মালোই না।
শিক্ষা দুটি। Timeout বাড়ালে duplicate কমে, কিন্তু কাজ কখনো ৭০ সেকেন্ড নিলে আবার ফিরবে। Idempotency থাকলে duplicate নিরীহ হয়। প্রথমটি গতির উপায়, দ্বিতীয়টি নিরাপত্তার।
নিজে বদলে দেখুন: W1-এর work ৭০ করুন, আর run(60, false) দেখুন।
Timeout বাছার ব্যবহারিক নিয়ম:
- কাজের tail latency-এর চেয়ে বড় রাখুন, গড়ের চেয়ে নয়। p99 ২০ সেকেন্ড হলে ৩০ সেকেন্ড কমই।
- খুব লম্বাও নয়। ১ ঘণ্টা দিলে worker মরলে ১ ঘণ্টা কাজ আটকে থাকে।
- লম্বা কাজে worker মাঝে মাঝে সময় বাড়ায়। একে heartbeat বলে, SQS-এ
ChangeMessageVisibility. - Queue-র পেছনে function থাকলে নিয়ম কড়া: লেখার সময়ে AWS-এর নথি timeout-কে function timeout-এর অন্তত ছয় গুণ রাখতে বলে, কারণ throttling হলেও পুনরায় চেষ্টা হয়। Function-এর আচরণ পর্ব ৬৯-এ।
Idempotency-র আসল রূপ হলো ডেটাবেসের একটি unique নিয়ম। “আগে দেখে নিই, তারপর লিখি” দুই ধাপে করলে দুই worker একসাথে “নেই” দেখতে পারে। তাই চিহ্ন আর ফল এক transaction-এ লিখুন:
-- PostgreSQL. processed_messages.event_id has a UNIQUE constraint.
BEGIN;
INSERT INTO processed_messages (event_id) VALUES ('evt-7f3a')
ON CONFLICT (event_id) DO NOTHING;
-- 0 rows inserted means a duplicate: ROLLBACK and delete the message.
UPDATE orders SET status = 'PAID' WHERE id = 'order-101';
COMMIT;টাকা কাটার মতো বাইরের কাজ আপনার transaction-এ ঢোকে না। তখন payment provider-কে idempotency token দিন, আর order id থেকে সেটি বানান, যাতে retry-তে একই token যায়। Idempotency-র পুরো কৌশল, যেমন dedup store-এর মেয়াদ আর inbox pattern, আছে delivery semantics পর্বে.
৪ · Retry, poison message আর dead-letter queue
ব্যর্থতা দুই রকম। সাময়িক: ডেটাবেস ব্যস্ত, network কেঁপেছে। আবার চেষ্টা করলে ঠিক হবে। স্থায়ী: বার্তার JSON ভাঙা, যে অর্ডারের কথা বলছে সেটি নেই। হাজারবার চেষ্টাতেও হবে না। এমন বার্তাকে বলে poison message.
Queue দুটোকে আলাদা করতে পারে না। সে শুধু দেখে delete আসেনি। তাই একটি সীমা লাগে: এতবার নেওয়ার পরেও শেষ না হলে বার্তাকে সরিয়ে আলাদা জায়গায় রাখা হবে। সেই জায়গাই dead-letter queue (DLQ), আর সীমাটি SQS-এ maxReceiveCount. ডাকঘরের “ঠিকানা মেলেনি” তাকের কথা ভাবুন: চিঠি ফেলে দেওয়া হয় না, পিয়নও বারবার ঘোরেন না, কেউ পরে হাতে দেখেন।
{
"deadLetterTargetArn": "arn:aws:sqs:ap-south-1:123456789012:orders-dlq",
"maxReceiveCount": 3
}নিচের simulator-এ তিনটি বার্তা: A সুস্থ, B একবার ব্যর্থ হয় (সাময়িক), C-র JSON ভাঙা (স্থায়ী)। সীমা ৩, timeout ৩০ সেকেন্ড।
// Dead-letter flow. A is healthy, B fails once, C is a poison message.
const VISIBILITY = 30, MAX_RECEIVES = 3, RETENTION = 4 * 24 * 3600;
const msgs = [
{ id: "A", failsFor: 0, why: "" },
{ id: "B", failsFor: 1, why: "db timeout" },
{ id: "C", failsFor: Infinity, why: "bad JSON" },
].map(m => ({ ...m, visibleAt: 0, receives: 0, state: "queue" }));
const dlq = [];
console.log("t(s) msg receive result");
for (let t = 0; t <= 200; t += 10) {
for (const m of msgs) {
if (m.state !== "queue" || m.visibleAt > t) continue;
if (m.receives >= MAX_RECEIVES) { // limit already used up: redrive policy acts
m.state = "dlq"; dlq.push(m.id);
console.log(`${String(t).padStart(4)} ${m.id} - moved to DLQ`);
continue;
}
m.receives++;
const col = `${String(t).padStart(4)} ${m.id} ${m.receives} `;
if (m.receives > m.failsFor) {
m.state = "done";
console.log(col + "ok, deleted");
} else {
m.visibleAt = t + VISIBILITY;
console.log(col + `FAIL ${m.why}, back at t=${m.visibleAt}`);
}
}
}
const stuck = Math.floor(RETENTION / VISIBILITY);
console.log(`\nleft in queue: ${msgs.filter(m => m.state === "queue").length}`);
console.log(`in DLQ: ${dlq.join(", ")}`);
console.log(`with no DLQ, C would fail ${stuck} times before retention ends`);আউটপুট:
t(s) msg receive result
0 A 1 ok, deleted
0 B 1 FAIL db timeout, back at t=30
0 C 1 FAIL bad JSON, back at t=30
30 B 2 ok, deleted
30 C 2 FAIL bad JSON, back at t=60
60 C 3 FAIL bad JSON, back at t=90
90 C - moved to DLQ
left in queue: 0
in DLQ: C
with no DLQ, C would fail 11520 times before retention ends
B দ্বিতীয় চেষ্টায় সেরে গেল: DLQ সাময়িক ব্যর্থতাকে ধরেনি, এটিই retry-র লাভ। C তিনবার নেওয়া হলো, আর t = ৯০-এ চতুর্থবার নিতে গিয়ে সীমা পার হওয়ায় DLQ-তে গেল। খেয়াল করুন, ঠিক তৃতীয় ব্যর্থতায় নয়, পরের receive-এর সময়ে সরে। শেষ লাইনের 11520 এসেছে ৩,৪৫,৬০০ ÷ ৩০ থেকে: DLQ না থাকলে খারাপ বার্তাটি ৪ দিন ধরে worker-এর সময় নষ্ট করত।
নিজে বদলে দেখুন: MAX_RECEIVES ১ করুন। B-ও DLQ-তে যাবে, যদিও সে সেরে উঠত।
সীমা কত হবে? খুব কম হলে সুস্থ বার্তা DLQ-তে যায়। খুব বেশি হলে খারাপ বার্তা দীর্ঘ সময় worker আটকায়। সাধারণ শুরু ৩ থেকে ৫, আর ব্যর্থ হওয়ার সাধারণ সময় মেপে ঠিক করুন। মনে রাখুন, মোট অপেক্ষা ≈ সীমা × timeout. Service Bus-এ MaxDeliveryCount (লেখার সময়ে default ১০), আর Pub/Sub-এ dead-letter topic-এর maximum delivery attempts একই কাজ করে।
Queue-র নিজের retry-তে backoff নেই। ব্যর্থ বার্তা স্থির timeout-এর পরেই ফেরে। ডেটাবেস ভেঙে পড়লে সব worker একই তালে আঘাত করতে থাকে, আর ক্ষতি বাড়ে, যাকে বলে retry storm. Exponential backoff নিজে যোগ করতে হয়: ব্যর্থ হলে worker receive count দেখে timeout বাড়িয়ে দিন, সাথে jitter রাখুন। Pub/Sub-এর subscription-এ retry policy হিসেবে backoff বসানো যায়।
DLQ-তে পড়ার পরে কী
DLQ কোনো সমাধান নয়, একটি হাসপাতালের ওয়ার্ড। সেখানে বার্তা জমে থাকলে অর্ডার আসলে থেমে আছে। তাই তিনটি অভ্যাস:
- Alarm: DLQ-র গভীরতা ০-এর বেশি হলেই খবর। “কত জমলে” প্রশ্ন নয়, প্রতিটি বার্তাই একজন গ্রাহকের কাজ (পর্ব ৬৬-এর alert নিয়ম দেখুন)।
- কারণ গোনা: বার্তার সাথে ব্যর্থতার কারণ লগ করুন, তারপর দলবদ্ধ করুন। সব এক কারণ হলে একটি বাগ, ছড়ানো হলে অন্য কিছু।
- Redrive: কারণ সারার পরে বার্তা মূল queue-তে ফেরান। কিন্তু কারণ না সারিয়ে ফেরালে সব আবার DLQ-তে আসবে। আর সব বার্তা একবারে না ফিরিয়ে হার সীমিত করুন, নইলে নিচের সার্ভিস নতুন করে ডুববে।
এই Bash block ছোট একটি নমুনা গুনে দেখায়, কোন বার্তা ফেরানো নিরাপদ। ইনপুট হাতে লেখা JSON lines. প্রয়োজনীয় হাতিয়ার পর্ব ৯-এ দেখেছেন।
# Count why messages reached the DLQ. Sample data, one JSON object per line.
set -euo pipefail
dlq=$(cat <<'EOF'
{"id":"m-01","queue":"orders","reason":"bad_json","receives":3}
{"id":"m-02","queue":"orders","reason":"timeout","receives":3}
{"id":"m-03","queue":"email","reason":"bad_json","receives":3}
{"id":"m-04","queue":"orders","reason":"unknown_field","receives":3}
{"id":"m-05","queue":"orders","reason":"bad_json","receives":3}
{"id":"m-06","queue":"email","reason":"timeout","receives":3}
{"id":"m-07","queue":"orders","reason":"timeout","receives":3}
{"id":"m-08","queue":"orders","reason":"bad_json","receives":3}
{"id":"m-09","queue":"email","reason":"throttled","receives":3}
{"id":"m-10","queue":"orders","reason":"bad_json","receives":3}
EOF
)
field() { sed -E "s/.*\"$1\":\"([^\"]+)\".*/\1/"; }
count() { sort | uniq -c | awk '{print $1, $2}' | sort -k1,1nr -k2,2; }
echo "total: $(printf '%s\n' "$dlq" | wc -l | tr -d ' ')"
echo "== by reason =="
printf '%s\n' "$dlq" | field reason | count
echo "== by queue and reason =="
printf '%s\n' "$dlq" | awk -F'"' '{print $8 "/" $12}' | count
echo "== safe to redrive (temporary failures) =="
printf '%s\n' "$dlq" | grep -E '"reason":"(timeout|throttled)"' | field id | tr '\n' ' '
echoআউটপুট:
total: 10
== by reason ==
5 bad_json
3 timeout
1 throttled
1 unknown_field
== by queue and reason ==
4 orders/bad_json
2 orders/timeout
1 email/bad_json
1 email/throttled
1 email/timeout
1 orders/unknown_field
== safe to redrive (temporary failures) ==
m-02 m-06 m-07 m-09
১০টির মধ্যে ৫টি bad_json, আর তার ৪টি orders queue-তে। এটি একটি producer-এর বাগের চিহ্ন: redrive করে লাভ নেই, আগে producer সারাতে হবে। timeout আর throttled সাময়িক, তাই শেষ লাইনের ৪টি বার্তা নিচের সার্ভিস সুস্থ হলে ফেরানো চলে। unknown_field সম্ভবত schema-র সমস্যা, পরের অংশে দেখবেন। আসল কাজে jq বেশি নিরাপদ; sed এখানে নেওয়া হয়েছে, যাতে বাড়তি কিছু install করতে না হয়।
একটি ফাঁদ: DLQ-র retention মূল queue-র চেয়ে লম্বা রাখুন। লেখার সময়ে SQS standard queue-তে বার্তার মেয়াদ গোনা হয় প্রথম পাঠানোর সময় থেকে, DLQ-তে পৌঁছানোর সময় থেকে নয়। ৩ দিন মূল queue-তে ঘোরা বার্তা ৪ দিনের DLQ-তে মাত্র ১ দিন বাঁচবে। Retention-এর পরে বার্তা নীরবে মুছে যায়।
৫ · ক্রম, FIFO আর batching
সাধারণ queue ক্রমের প্রতিশ্রুতি দেয় না। SQS standard-এ ক্রম “best-effort”: আগে পাঠানো বার্তা পরে আসতে পারে। ক্রম রাখা দামি, কারণ অনেক machine জুড়ে সমান্তরালে চলতে গেলে কাউকে থামতেই হয়।
আগে জিজ্ঞেস করুন, সত্যিই ক্রম লাগবে কি না। অনেক সময় বার্তায় একটি version বা sequence সংখ্যা রাখলেই চলে: পুরোনো সংখ্যা এলে worker সেটি ফেলে দেয়। “Stock ১০ হলো” ধরনের পুরো মান পাঠালে ক্রম আরও কম গুরুত্বপূর্ণ।
FIFO queue আর message group
সত্যিই ক্রম লাগলে FIFO queue. কিন্তু লক্ষ করুন, ক্রম পুরো queue-তে নয়, প্রতিটি message group-এর ভেতরে। লঞ্চঘাটের টিকিটের কাউন্টার ভাবুন: এক কাউন্টারের লাইনে ক্রম আছে, কিন্তু দুই কাউন্টারের মধ্যে কে আগে, তার কোনো মানে নেই। একটি group-এর সামনের বার্তা শেষ না হলে পরেরটি কেউ পায় না।
| সার্ভিস | ক্রমের একক | লক্ষ রাখার বিষয় (লেখার সময়ে) |
|---|---|---|
| SQS FIFO | Message group ID | Standard-এর চেয়ে throughput কম; ডিফল্টে API action-প্রতি সেকেন্ডে ৩০০, batch-এ ৩,০০০, high-throughput mode-এ বেশি। Duplicate চেনার সময়সীমা (dedup window) ৫ মিনিট। |
| Service Bus | Session | Session চালু করতে হয়; প্রতিটি session একসাথে একজন receiver-এর হাতে |
| Google Pub/Sub | Ordering key | একই region দিয়ে publish করতে হয়; retry-তে একই key-র পরের বার্তাও আবার আসতে পারে |
| Kinesis, Kafka, Event Hubs | Shard বা partition | Partition key ঠিক করে কে কোন ঘরে; একটি key খুব ব্যস্ত হলে hot partition |
ধরুন প্রতি বার্তায় ১ সেকেন্ড লাগে, মোট ১০০০ বার্তা, ১০টি worker. সব বার্তার group একটি হলে কাজ একে একে: ১০০০ সেকেন্ড, worker যতই থাক। প্রতি গ্রাহকের নিজের group আর সমান ভাগ হলে ১০টি worker একসাথে চলে: ১০০ সেকেন্ড। তাই group ID বাছুন যতটুকু ক্রম সত্যিই লাগে ততটুকুর জন্য: সাধারণত এক গ্রাহক বা এক অর্ডার, পুরো দোকান নয়।
এবার poison message যোগ করুন। Group-এর মাথায় খারাপ বার্তা থাকলে পেছনের সবাই আটকে যায়, একে বলে head-of-line blocking. Timeout ৩০ s আর সীমা ৩ হলে ওই group ৯০ সেকেন্ড থেমে থাকে। Stream-এ একই সমস্যা: একটি ভাঙা record পুরো shard আটকে রাখতে পারে, তাই function trigger-এ retry-র সীমা, record-কে ভাগ করে চেষ্টা আর ব্যর্থতার গন্তব্য ঠিক করে দিতে হয়।
“Exactly-once” দেখলে সাবধান। SQS FIFO “exactly-once processing” বলে। এর অর্থ, dedup window-র ভেতরে একই deduplication ID-র বার্তা দ্বিতীয়বার ঢোকে না, আর delete হওয়া বার্তা ফেরে না। কিন্তু আপনার worker টাকা কাটার পরে, delete-এর আগে মরলে বার্তা ফিরবেই। তাই FIFO নিলেও idempotent consumer লাগবে। কেন “exactly-once delivery” অসম্ভব, সেই যুক্তি delivery semantics পর্বে.
Batching: বড় হাতা, বড় বিপদ
ঘরে ঘরে একটি করে পার্সেল দেওয়ার বদলে ভ্যান ভরে যাওয়াই সস্তা। Queue-তেও প্রতি API call-এ একাধিক বার্তা (batch) পাঠানো, নেওয়া আর মোছা যায়। SQS-এ লেখার সময়ে একবারে ১০টি পর্যন্ত। লাভ দুটি: কম call মানে কম বিল, আর worker-এর ডেটাবেসে একসাথে লেখা যায়।
আরেকটি সহজ সাশ্রয় long polling. খালি queue-তে বারবার “কিছু আছে?” জিজ্ঞেস না করে worker বলে “২০ সেকেন্ড অপেক্ষা করে বলুন”।
# Example only. Real use needs an account and may cost money.
aws sqs receive-message \
--queue-url https://sqs.ap-south-1.amazonaws.com/123456789012/orders \
--max-number-of-messages 10 \
--wait-time-seconds 20 \
--visibility-timeout 60Batching-এর দুটি ফাঁদ:
- এক ব্যর্থ, সবাই আবার। ১০টির batch-এ ৯টি সফল আর ১টি ব্যর্থ হলে, এবং পুরো batch-কে ব্যর্থ জানালে, ৯টি আবার চলবে। সমাধান partial batch response: function শুধু ব্যর্থগুলোর ID ফেরত দেয়। লেখার সময়ে AWS-এ এটি আলাদাভাবে চালু করতে হয়।
- সময় ভাগ হয়ে যায়। ১০টি বার্তা ৩০ সেকেন্ডের timeout পেয়েছে, কাজ একটির পর একটি হলে শেষের দিকেরগুলো সময়ের বাইরে যেতে পারে। Batch-এর মোট সময় দিয়ে timeout হিসাব করুন। আর batch জমার অপেক্ষা বাড়লে latency-ও বাড়ে।
৬ · Fan-out, routing rule, schema আর CloudEvents
অর্ডারের খবর তিনটি সার্ভিসের লাগবে। Topic থেকে সরাসরি সার্ভিসে না পাঠিয়ে প্রায়ই মাঝে প্রতিটির নিজের queue বসানো হয়। এই নকশাকে বলে topic-to-queue fan-out. কেন queue? কারণ topic ধরে রাখতে জানে না, queue জানে। প্রতিটি শ্রোতা নিজের গতিতে চলে, নিজের DLQ রাখে। নিচে দেখুন email সার্ভিস পড়ে গেলে কী হয়।
| stock-queue | |
| email-queue | |
| report-queue |
- Publish. Order service একবারই
OrderPlacedপাঠাল। সে জানে না কে শুনছে, কতজন শুনছে। নতুন শ্রোতা এলেও তার কোড বদলাবে না। - Fan-out. Topic তিনটি queue-তে একটি করে কপি দিল। এখন প্রতিটি queue-তে ১টি বার্তা। কপিগুলো স্বাধীন, একটি মুছলে অন্যগুলো থাকে।
- Stock দ্রুত। Stock service নিজের কপি শেষ করে delete করল। তার queue ০, বাকি দুটি ১।
- Email পড়ে গেল। এর মধ্যে order-102 আর 103 এলো। Stock দুটিই সেরে ফেলল, তার queue ০-তেই। Email-এর queue ৩, Report-এর queue-ও ৩, কারণ Report এখনো নেয়নি। অর্ডার থামল না: খারাপ খবর শুধু এক queue-তে আটকে আছে।
- Report একসাথে। Report service batch-এ চলে, ৩টি একসাথে নিল। তার queue ০. Email এখনো বন্ধ, তার queue ৩।
- Email ফিরল। জমে থাকা ৩টি চিঠিই পাঠাল। এটিই backpressure সামলানোর সবচেয়ে সরল রূপ: চাপ queue-তে জমে, সার্ভিসে নয়। সব queue এখন ০।
একটি সূক্ষ্ম বিপদ: ডেটাবেসে লেখা আর publish দুটি আলাদা কাজ। Order service ডেটাবেসে লিখে publish-এর আগেই মরলে ঘটনা হারায়। সমাধান transactional outbox: ঘটনাটি একই transaction-এ একটি টেবিলে লেখা, আর আলাদা process সেটি পাঠায়। বিস্তারিত distributed transaction পর্বে.
Routing rule: কে শুনবে, সেটি বাইরে লেখা
Event bus-এ event pattern লিখে বলা হয় কোন ঘটনা কোন গন্তব্যে যাবে। উদাহরণ: বাংলাদেশের ৫,০০০ টাকা বা বেশি মূল্যের অর্ডার শুধু fraud-check সার্ভিসে। Topic-এও SNS filter policy বা Service Bus subscription filter এই কাজ করে। নিচে EventBridge-ধাঁচের একটি pattern:
{
"source": ["shop.orders"],
"detail-type": ["OrderPlaced"],
"detail": {
"country": ["BD"],
"amount": [{ "numeric": [">=", 5000] }]
}
}লাভ হলো producer-এর কোড অপরিবর্তিত থাকে। বিপদ হলো নিয়মে বানান ভুল হলে কোনো error আসে না: ঘটনা শুধু কোথাও যায় না। তাই নিয়মও কোড, IaC-তে রাখুন, পরীক্ষা করুন, আর “কোনো নিয়মে মেলেনি” ধরনের metric দেখুন।
Schema: ঘটনা একবার ছাড়লে সেটি public API
একটি ঘটনার কতজন পাঠক, producer জানে না। তাই amount নাম বদলে total করলে এমন কারো কোড ভাঙবে, যাঁকে আপনি চেনেনই না। এজন্য event schema লিখে রাখা হয়, আর নিয়ম সরল: নতুন field যোগ করুন, পুরোনো অর্থ বদলাবেন না, মুছবেন না। ভাঙতেই হলে নতুন version-এর নতুন event type আনুন, কিছুদিন দুটোই চালান। Schema registry কেন্দ্রীয়ভাবে schema রাখে আর পরিবর্তন যাচাই করে: যেমন EventBridge Schema Registry, Confluent Schema Registry. Format আর evolution-এর তত্ত্ব serialization পর্বে.
ঘটনায় কী রাখবেন? দুটি পথ। শুধু পরিচয় পাঠালে (“order-101 হয়েছে”) পাঠককে আবার ফিরে ডাকতে হয়, এতে জোড়া বাড়ে। পুরো তথ্য পাঠালে আকার বাড়ে, আর queue-র আকারের সীমাও আছে। ছবি বা বড় ফাইল হলে object storage-এ রেখে শুধু ঠিকানা পাঠান, একে বলে claim check, লঞ্চের মালের রসিদের মতো। আর গ্রাহকের সংবেদনশীল তথ্য ঘটনায় যত কম, তত ভালো, কারণ সেটি অনেক queue, DLQ আর log-এ ছড়ায়।
CloudEvents: খামের একটি সাধারণ মাপ
প্রতি provider-এর ঘটনার আকার আলাদা হলে প্রতিটি পাঠকে আলাদা কোড লেখা লাগে। CloudEvents হলো CNCF-এর একটি spec, যা ঘটনার খামের মাপ ঠিক করে, ভেতরের চিঠির নয়। Version ১.০ আসে ২০১৯ সালে, আর লেখার সময়ে এটি CNCF-এর graduated প্রকল্প। আবশ্যিক attribute চারটি: id, source, specversion, type. source আর id মিলে ঘটনাকে অনন্য করে, তাই এই জোড়াই ভালো idempotency key.
{
"specversion": "1.0",
"id": "evt-7f3a",
"source": "/shop/orders",
"type": "com.example.order.placed.v1",
"subject": "order-101",
"time": "2026-10-31T09:30:00Z",
"datacontenttype": "application/json",
"data": { "orderId": "order-101", "amount": 500, "currency": "BDT" }
}Azure Event Grid আর Google Eventarc CloudEvents ব্যবহার করে। EventBridge-এর নিজের envelope আছে, তাই সেখানে অনুবাদ লাগতে পারে। এটি vendor lock-in কিছুটা কমায়, কিন্তু মুছে দেয় না: খুঁটিনাটি আচরণ সব জায়গায় এক নয়।
৭ · Workflow — orchestration, choreography আর saga
ধরুন একটি অর্ডারে তিন ধাপ: stock রাখা, টাকা কাটা, courier ডাকা। কোনো ধাপ ব্যর্থ হলে আগেরগুলো ফেরাতে হবে। একাধিক সার্ভিস জুড়ে ডেটাবেসের transaction চলে না। তাই ধাপগুলোকে আলাদা local transaction ধরে, প্রতিটির উল্টো কাজ রাখা হয়। একেই বলে saga, আর উল্টো কাজকে compensating action. ধারণাটি Hector Garcia-Molina আর Kenneth Salem-এর ১৯৮৭ সালের প্রবন্ধ থেকে, পুরো তত্ত্ব distributed transaction পর্বে. এখানে প্রশ্ন একটিই: সাজাবে কে?
Orchestration
বিয়েবাড়ির ক্যাটারিং ম্যানেজার। হাতে তালিকা, প্রত্যেককে বলেন কখন কী করতে হবে, কে পারল না সেটিও তিনি জানেন। Orchestration-এ একটি workflow engine ধাপ চালায়।
এক জায়গায় নকশাChoreography
অভিজ্ঞ নাচের দল। পরিচালক নেই, প্রত্যেকে অন্যের নড়াচড়া দেখে নিজের অংশ করে। Choreography-তে প্রতিটি সার্ভিস ঘটনা শুনে নিজের কাজ করে, আর নতুন ঘটনা ছাড়ে।
নকশা ছড়িয়ে থাকেনিচে একটি workflow-এর ব্যর্থতা ধাপে ধাপে দেখুন। Courier API তিনবার চেষ্টাতেও উত্তর দেয় না, তাই engine compensation চালায়।
- ReserveStock১টি পণ্য
- ChargePayment৫০০ টাকা
- BookCourier৩ বার চেষ্টা
- Refundউল্টো কাজ
- ReleaseStockউল্টো কাজ
| Stock | |
| টাকা কাটা | |
| Courier চেষ্টা | |
| অর্ডার |
- শুরু। অর্ডার এলো, engine একটি execution চালু করল। এর অবস্থা engine-এর নিজের ভাণ্ডারে লেখা থাকে, কোনো worker-এর memory-তে নয়।
- Stock ধরা হলো। প্রথম ধাপ সফল। Engine ফল লিখে রাখল। এখন পুরো সার্ভারটি বন্ধ হলেও এখান থেকেই আবার শুরু হতে পারবে।
- টাকা কাটা হলো। ৫০০ টাকা। এটি ফেরাতে হলে পরে আলাদা refund লাগবে।
- Courier: প্রথম ব্যর্থতা। API 503 দিল। Retry নিয়ম আছে, তাই ২ সেকেন্ড অপেক্ষা।
- Courier: দ্বিতীয় ব্যর্থতা। অপেক্ষা দ্বিগুণ হয়ে ৪ সেকেন্ড। এটিই exponential backoff, আর queue-র মতো হাতে বানাতে হয়নি।
- Courier: তৃতীয় ব্যর্থতা। Retry শেষ। Catch নিয়ম workflow-কে compensation-এর পথে পাঠাল। খেয়াল করুন, ব্যর্থতার পথটিও নকশায় লেখা।
- Refund. শেষ সফল ধাপ আগে ফেরানো হয়, তাই আগে টাকা। Compensation-ও ব্যর্থ হতে পারে, তাই এটিও idempotent আর retry-সহ হওয়া দরকার।
- Stock ছাড়া হলো। উল্টো ক্রমে দ্বিতীয় compensation. Stock আবার বিক্রির জন্য খোলা।
- অর্ডার বাতিল। অবস্থা CANCELLED, ক্রেতাকে জানানো হলো। Engine-এর দৃষ্টিতে workflow সফলভাবে শেষ, কারণ ব্যর্থতার পথটিই সে ঠিকমতো চালিয়েছে। History-তে পুরো গল্প এক জায়গায়।
ওপরের BookCourier ধাপটি AWS Step Functions-এর Amazon States Language-এ লিখলে এমন দেখায়। MaxAttempts: 2 মানে প্রথম চেষ্টার পরে আরও ২ বার, মোট ৩:
{
"BookCourier": {
"Type": "Task",
"Resource": "arn:aws:lambda:ap-south-1:123456789012:function:book-courier",
"Retry": [{
"ErrorEquals": ["States.TaskFailed"],
"IntervalSeconds": 2,
"BackoffRate": 2,
"MaxAttempts": 2
}],
"Catch": [{ "ErrorEquals": ["States.ALL"], "Next": "RefundPayment" }],
"Next": "ConfirmOrder"
}
}এবার একই ব্যর্থতা দুই পথে চালিয়ে দেখুন। তৃতীয় run-এ এক দল ভুলে একটি subscription তুলে দিয়েছে।
// One order, courier booking fails. Same business, two ways of joining services.
const act = {
reserve: s => { s.stock = 1; }, charge: s => { s.money = 500; },
refund: s => { s.money = 0; }, release: s => { s.stock = 0; }, cancel: s => { s.order = "CANCELLED"; } };
const fresh = () => ({ money: 0, stock: 0, order: "PENDING" });
function orchestrate() {
const s = fresh(), history = []; let messages = 0;
const call = (name, ok = true) => { // one command + one reply
messages += 2; if (ok) act[name](s);
history.push(name + (ok ? " ok" : " FAIL"));
};
call("reserve"); call("charge"); call("book", false);
call("refund"); call("release"); call("cancel"); // compensate in reverse
console.log(`ORCHESTRATION messages=${messages}\n ${history.join(" > ")}`);
console.log(" final", JSON.stringify(s));
}
function choreograph(rules, label) {
const s = fresh(), seen = [], bus = ["OrderPlaced"];
while (bus.length) {
const ev = bus.shift(), rule = rules[ev] || {}; // no rule: the event just ends
seen.push(ev);
if (rule.do) act[rule.do](s);
if (rule.emit) bus.push(rule.emit);
}
console.log(`CHOREOGRAPHY ${label} events=${seen.length}\n ${seen.join(" > ")}`);
console.log(" final", JSON.stringify(s));
}
const rules = { OrderPlaced: { do: "reserve", emit: "StockReserved" },
StockReserved: { do: "charge", emit: "PaymentDone" }, PaymentDone: { emit: "CourierFailed" },
CourierFailed: { do: "refund", emit: "Refunded" }, Refunded: { do: "release", emit: "StockFreed" },
StockFreed: { do: "cancel", emit: "OrderCancelled" } };
orchestrate(); choreograph(rules, "all rules");
const broken = { ...rules }; delete broken.Refunded; // one team dropped a subscription
choreograph(broken, "rule missing");আউটপুট:
ORCHESTRATION messages=12
reserve ok > charge ok > book FAIL > refund ok > release ok > cancel ok
final {"money":0,"stock":0,"order":"CANCELLED"}
CHOREOGRAPHY all rules events=7
OrderPlaced > StockReserved > PaymentDone > CourierFailed > Refunded > StockFreed > OrderCancelled
final {"money":0,"stock":0,"order":"CANCELLED"}
CHOREOGRAPHY rule missing events=5
OrderPlaced > StockReserved > PaymentDone > CourierFailed > Refunded
final {"money":0,"stock":1,"order":"PENDING"}
দুই পথেই শেষ ফল এক: টাকা ০, stock ০, অর্ডার CANCELLED. Choreography-তে বার্তাও কম, ৭ বনাম messages=12, আর কোনো কেন্দ্রীয় engine-এর ওপর নির্ভরতা নেই। এটি তার সত্যিকারের গুণ।
তৃতীয় run-টি আসল শিক্ষা। Refunded-এর পরে কেউ নেই, ঘটনা নীরবে শেষ। ফল: "stock":1, "order":"PENDING". টাকা ফেরত গেছে, কিন্তু পণ্য আটকে, অর্ডার ঝুলে আছে, আর কোথাও কোনো error নেই. প্রতিটি সার্ভিস নিজের চোখে ঠিক। Orchestration-এ এমন ভুল একটি ফাইলে দেখা যায়, আর execution ঝুলে থাকলে timeout ধরে ফেলে। Choreography-তে দরকার সময়সীমা-ভিত্তিক পাহারাদার, আর distributed tracing.
| Engine | নকশা লেখা হয় | লক্ষ রাখার বিষয় |
|---|---|---|
| AWS Step Functions | JSON (Amazon States Language) | Standard: দীর্ঘ, পূর্ণ history, ধাপ-প্রতি দাম। Express: ছোট, দ্রুত, বেশি পরিমাণে; নিশ্চয়তা আলাদা। |
| Azure Durable Functions | C#, Python, JavaScript ইত্যাদির কোড | Orchestrator কোড replay হয়, তাই একই ইনপুটে একই পথে চলতে হবে (deterministic): সরাসরি বর্তমান সময় বা random নয়। |
| Google Cloud Workflows | YAML বা JSON | HTTP-ভিত্তিক ধাপ জোড়ার সহজ পথ |
| Temporal (open source, নিজে চালানো বা managed) | সাধারণ কোড (Go, Java, TypeScript, Python ইত্যাদি) | Cloud-নিরপেক্ষ, কিন্তু নিজে চালালে ক্লাস্টার ও ডেটাবেস আপনার দায়িত্ব |
এই engine-গুলোর মূল ধারণা durable execution: প্রতিটি ধাপের ফল লিখে রাখা, যাতে crash-এর পরে মাঝখান থেকে চলা যায়। তত্ত্ব আছে workflow engine পর্বে.
কখন কোনটি? ধাপে ক্রম আছে, compensation আছে, টাকা জড়িত, আর কেউ জানতে চায় “এই অর্ডার এখন কোথায়?”: orchestration. স্বাধীন প্রতিক্রিয়া, যেখানে কেউ ব্যর্থ হলেও বাকিরা চিন্তিত নয়, যেমন email, report, cache মোছা: choreography. বাস্তবে দুটোই মেশে: এক দলের সার্ভিসগুলোর ভেতরে workflow, দলগুলোর মধ্যে ঘটনা।
৮ · চালানো আর মাপা — queue depth, autoscaling আর খরচ
Queue-ভিত্তিক সিস্টেম নীরবে পিছিয়ে পড়ে। সব service সবুজ, error নেই, অথচ ক্রেতার email আসে ৪০ মিনিট পরে। তাই CPU নয়, queue-কেই মাপতে হয়:
| Metric | কী বলে | কী করবেন |
|---|---|---|
| Queue depth (visible) | কত কাজ অপেক্ষায় | Scaling-এর সংকেত |
| In flight | কত কাজ এখন worker-দের হাতে | Depth ০ হলেও কাজ চলতে পারে; scale-in-এর আগে দেখুন |
| সবচেয়ে পুরোনো বার্তার বয়স | গ্রাহক কতক্ষণ অপেক্ষা করছেন | SLO আর alert-এর সেরা সংকেত |
| DLQ depth | কত কাজ হাল ছেড়েছে | ০-এর বেশি হলেই alert |
| Receive ÷ delete অনুপাত | কত retry হচ্ছে | ১-এর অনেক বেশি হলে timeout বা বাগ দেখুন |
মনে রাখুন, শুধু “মোট depth” autoscaling-এর ভালো সংকেত নয়। ১,০০০ বার্তা ২টি worker-এর জন্য বিপদ, ১০০টির জন্য সামান্য। ভালো সংকেত হলো worker-প্রতি জমে থাকা কাজ, যা পর্ব ১৭-এর target tracking-এ বসানো যায়। Kubernetes-এ একই কাজ করে KEDA, যা queue দেখে worker Pod বাড়ায়, ০-তেও নামাতে পারে।
একটি কাজে ০.৫ s লাগে, অর্থাৎ worker-প্রতি ২ বার্তা/s. বার্তা আসে ৩০/s. লক্ষ্য: কোনো বার্তা ৬০ s-এর বেশি অপেক্ষা করবে না। এখন depth ৩,০০০.
- গ্রহণযোগ্য backlog: ৬০ ÷ ০.৫ = ১২০ বার্তা worker-প্রতি।
- এই জটের জন্য: ৩,০০০ ÷ ১২০ = ২৫ worker.
- স্বাভাবিক সময়ের জন্য: ৭০% ব্যস্ততা ধরে ৩০ ÷ (২ × ০.৭) = ২১.৪, অর্থাৎ ২২ worker.
- জট কাটতে কত সময়: ২৫ worker করে ৫০/s, আসে ৩০/s, নিট ২০/s. তাই ৩,০০০ ÷ ২০ = ১৫০ s.
- Max ২০ হলে: নিট (৪০ − ৩০) = ১০/s, সময় ৩০০ s. আর worker ১৫-তে থেমে থাকলে নিট ০: জট কখনোই কাটবে না।
Worker বাড়ালেই শেষ নয়। সব worker একই ডেটাবেসে লিখলে queue-র চাপ সেখানে যায়। Consumer-এর সংখ্যায় একটি উপরের সীমা রাখুন। Function trigger-এও concurrency-র সীমা বসান (পর্ব ৬৯)। Queue-এর কাজই চাপ ধরে রাখা, পুরোটা ঢেলে দেওয়া নয়।
সারিরও একটি সীমা ভাবুন। Retention পার হলে বার্তা নীরবে মুছে যায়, আর “৫ মিনিটে আসবে” ধরনের OTP ১ ঘণ্টা পরে অর্থহীন। তাই বার্তার বয়সের একটি ছাদ ঠিক করুন। সেই ছাদ ছুঁলে হয় পাঠানো পক্ষকে ধীর করুন, হয় পুরোনো কাজ ফেলে দিন আর গণনা রাখুন।
খরচের হিসাব (সব দাম অনুমিত)
নিচের দাম শেখার জন্য ধরা গোল সংখ্যা, কোনো provider-এর আসল দাম নয়। পরিকল্পনার আগে pricing পাতা দেখুন। ধরে নিই: queue-তে প্রতি ১০ লাখ API call $০.৪০, topic-এ প্রতি ১০ লাখ publish $০.৫০, queue-তে পৌঁছে দেওয়া বিনা খরচে, workflow-এ প্রতি ১০ লাখ ধাপ $২৫. মাসে ৩০ লাখ অর্ডার, ৩০ দিনের মাস।
| নকশা | হিসাব | মাসে খরচ |
|---|---|---|
| এক queue, প্রতি বার্তায় send + receive + delete | ৩০ লাখ × ৩ = ৯০ লাখ call | $৩.৬০ |
| একই, ১০টির batch-এ | ৯ লাখ call | $০.৩৬ |
| খালি queue-তে ১০ worker, প্রতি সেকেন্ডে poll | ১০ × ২৫,৯২,০০০ s = ২.৫৯২ কোটি call | $১০.৩৭ |
| একই, ২০ s long polling-এ | ১২,৯৬,০০০ call | $০.৫২ |
| Topic-এ fan-out, তিনটি queue | Publish ৩০ লাখ ($১.৫০) + ২.৭ কোটি queue call ($১০.৮০) | $১২.৩০ |
| প্রতি অর্ডারে ৬ ধাপের workflow | ১.৮ কোটি ধাপ | $৪৫০ |
তিনটি শিক্ষা। এক, খালি queue-র poll আসল কাজের চেয়েও বেশি বিল আনতে পারে: $১০.৩৭ বনাম $৩.৬০। Long polling এক লাইনের সমাধান। দুই, batching এখানে বিল দশ ভাগের এক করে। তিন, workflow fan-out-এর প্রায় ৩৭ গুণ। এটি অপচয় নয়, আপনি দৃশ্যমানতা আর ব্যর্থতা সামলানোর দাম দিচ্ছেন। কিন্তু প্রতিটি email-এর জন্য নয়, টাকা-জড়িত পথের জন্যই সেটি মানায়। লেখার সময়ে Step Functions-এর Express ধরনের হিসাব আলাদা, তাই বেশি পরিমাণে সেটি যাচাই করুন। Queue-র বিল সাধারণত worker-এর compute আর ডেটা পাঠানোর খরচের তুলনায় ছোট। পুরো বিলের নকশা পর্ব ৭২-এ। আর নিজে চেষ্টা করতে চাইলে মনে রাখুন, আসল cloud-এ খরচ হতে পারে; পরীক্ষা শেষে queue, topic আর rule মুছে দিন।
আরও গভীরে: “Dead Letter Channel” আর “Competing Consumers” নামগুলো Gregor Hohpe ও Bobby Woolf-এর Enterprise Integration Patterns (২০০৩) থেকে পরিচিত। Queue-কে ভিড় সামাল দেওয়ার উপায় হিসেবে Little's law দিয়ে বোঝা যায়: গড় অপেক্ষা ≈ গড় depth ÷ বের হওয়ার হার, যেমন ৩,০০০ depth আর ৫০/s-এ ৬০ s. তবে নতুন বার্তা আসতে থাকলে আসল অপেক্ষা এর চেয়ে বেশি হতে পারে, তাই বয়সের metric-ই শেষ কথা। আর SQS-এর সংখ্যা বিতরিত ব্যবস্থার গণনা, তাই নথিতেই “approximate” লেখা। ঠিক ০ দেখার ওপর যুক্তি দাঁড় করাবেন না।
পরীক্ষা ও ইন্টারভিউতে:
- Visibility timeout কী? Receive-এর পরে বার্তা যতক্ষণ অন্যদের কাছে আড়ালে থাকে; delete না এলে আবার আসে।
- SNS আর SQS পার্থক্য? SNS push করে সবাইকে কপি দেয়, ধরে রাখে না। SQS ধরে রাখে, worker নিজে টেনে নেয়।
- DLQ কেন? Poison message আলাদা করা, যাতে সে অন্য কাজ আটকাতে বা খরচ বাড়াতে না পারে, আর পরে বিশ্লেষণ করা যায়।
- Kafka না SQS? আবার পড়া, ক্রম, একাধিক পাঠক দল: stream. স্বাধীন কাজ ভাগ করে দেওয়া: queue.
- CloudEvents-এর আবশ্যিক attribute?
id,source,specversion,type.
৯ · AI-এর যুগে queue আর workflow-এর নকশা যাচাই
AI খুব দ্রুত queue, Lambda আর Terraform লিখে দেয়। সব ঠিক থাকলে যে পথ চলে, সেটি সাধারণত ঠিকই থাকে। সমস্যা থাকে ঠিক সেখানে, যা এই পর্বের বিষয়: duplicate, ব্যর্থতা, খরচ।
| AI-এর ভুল | বিপদ | যাচাই |
|---|---|---|
| DLQ, alarm আর সীমা নেই | Poison message চক্র, নীরব ব্যর্থতা | Redrive policy আর DLQ alarm খুঁজুন |
| Consumer idempotent নয় | দুবার টাকা কাটা | একই বার্তা দুবার পাঠিয়ে পরীক্ষা করুন |
| Receive-এর পরেই delete, কাজের আগে | Crash-এ কাজ হারায় | কোডের ক্রম পড়ুন |
| Visibility timeout default রেখে দেওয়া | ধীর কাজে duplicate | p99 কাজের সময়ের সাথে মেলান |
IAM-এ sqs:* আর Resource: "*" | যেকোনো queue মোছা বা পড়া সম্ভব | নির্দিষ্ট action আর ARN |
Queue policy-তে Principal: "*" | বাইরের কেউ বার্তা ঢোকাতে পারে | পরিষ্কার principal আর condition |
| বার্তায় secret, কার্ড বা ব্যক্তিগত তথ্য | DLQ আর log-এ ছড়িয়ে পড়ে | Payload পড়ুন; শুধু id পাঠান, encryption চালু করুন |
| নেই এমন attribute বা ভুল সীমা | Apply-তে ব্যর্থতা বা ভুল আচরণ | Provider-এর নথি, আর terraform plan |
পরামর্শ: “purge করে দিন” | আসল বার্তা চিরতরে হারায় | আগে কারণ গুনুন; দরকারে archive |
| খরচের কথা নেই: short polling, প্রতি ধাপে workflow | বিল ফুলে ওঠে | পরিমাণ × দাম হিসাব, budget alert |
AI-কে কোড লিখতে বলার চেয়ে আগে ব্যর্থতার তালিকা চাইলে উত্তর অনেক ভালো হয়। কোনো secret, আসল account id বা গ্রাহকের তথ্য prompt-এ দেবেন না।
AI-এর নকশা হাতে পেলে — চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন।
প্র ০১Cloud-এর শুরুর দিকের সার্ভিসের মধ্যে একটি queue কেন ছিল? S3 আর EC2-র আগেই SQS-এর জনসমক্ষে আসার তাৎপর্য কী?
Amazon SQS-এর beta সংস্করণ ২০০৪ সালে আসে, আর সাধারণ ব্যবহারের জন্য খোলে ২০০৬ সালে। S3 আর EC2 আসে ২০০৬ সালে। অর্থাৎ একটি queue-ই AWS-এর প্রথম দিকের পরিকাঠামো সার্ভিসগুলোর একটি। এটি কাকতালীয় মনে হয় না।
Message queue নতুন ধারণা ছিল না। IBM MQSeries ১৯৯০-এর দশকের শুরুতেই ব্যাংকের মতো প্রতিষ্ঠানে চলছিল। AMQP আর RabbitMQ এসেছে ২০০০-এর দশকে। কিন্তু সেসব নিজের হাতে চালাতে হতো: cluster, disk, replication, লাইসেন্স। একটি বিশ্বাসযোগ্য broker চালানোই আলাদা বিশেষজ্ঞতা। ফলে ছোট দল queue এড়াত, আর সরাসরি ডাকত, বা ডেটাবেসের টেবিলকে queue বানাত।
Managed queue সেই দেয়ালটি ভেঙে দিল। একটি API call-এ queue, আর দাম ব্যবহার অনুযায়ী। কিন্তু দামও ছিল: সাধারণ SQS-এ ক্রমের নিশ্চয়তা নেই, duplicate আসতে পারে। এই শিথিলতাই তাকে বিপুল আকারে নিয়ে যেতে সাহায্য করে। Provider কঠিন নিশ্চয়তা বাদ দিয়ে আকার দিল, আর idempotency-র দায় চলে গেল application-এর কাঁধে। FIFO queue এসেছে অনেক পরে, ২০১৬ সালে, আর কম throughput নিয়ে।
একই সময়ে বড় ওয়েব সিস্টেম বুঝছিল যে ব্যর্থতা স্বাভাবিক ঘটনা। Machine অনেক হলে কোনো না কোনোটি সবসময় পড়ে থাকে। তাহলে সার্ভিসগুলোকে এমনভাবে জুড়তে হবে যাতে একজনের সমস্যা অন্যকে না থামায়। Queue ঠিক সেই জোড়া। আজকের serverless নকশাও এই ভিত্তিতে দাঁড়িয়ে: ঘটনা আসে, function জাগে, বার্তা না মেটা পর্যন্ত থাকে। ঈদের আগে বাসের টিকিটের ভিড়ের মতো হঠাৎ চাপ এলে queue সেটি ধরে রাখে, আর পেছনের সার্ভিস নিজের গতিতে কাজ করে। ছোট দলের জন্য এই সুবিধাটি আগে প্রায় নাগালের বাইরে ছিল।
মূল উপলব্ধি: Cloud শুধু machine ভাড়া দেয়নি, কঠিন distributed primitive-কে একটি API call বানিয়েছে। আর প্রতিটি সহজ API-র পেছনে একটি শর্ত আছে। Queue-এর শর্ত হলো: duplicate আপনি সামলাবেন।
প্র ০২Provider “exactly-once” লিখলে কি idempotent consumer বাদ দেওয়া যায়?
না, এবং কারণটি বোঝা দরকার, কারণ বাক্যটি মিথ্যা নয়, শুধু সীমিত। SQS FIFO “exactly-once processing” বলে। Kafka ০.১১ (২০১৭) “exactly-once semantics” আনে, যা তখন ব্যাপক আলোচনা তোলে। Google Pub/Sub-এও pull subscription-এ exactly-once delivery-র সুবিধা এসেছে, নির্দিষ্ট শর্তে। প্রতিটি দাবি তার নিজের সীমার ভেতরে সত্য।
সীমাটি কোথায়? Broker-এর ভেতরে। SQS FIFO একটি ৫ মিনিটের window-তে একই deduplication ID দ্বিতীয়বার ঢুকতে দেয় না। Kafka-র transaction, Kafka topic থেকে পড়ে Kafka topic-এ লেখার একটি শিকলকে atomic করে। কিন্তু ভাবুন আপনার worker কী করে: বার্তা পড়ে, বাইরের payment API ডাকে, তারপর ack পাঠায়। Payment সফল হওয়ার পরে আর ack-এর আগে যদি machine পড়ে যায়, তাহলে broker-এর কাছে বার্তাটি অসমাপ্ত। সে আবার দেবে, এবং সে সঠিক কাজই করছে। আবার ack আগে দিলে crash-এ কাজ হারায়। Two Generals problem-এর মূল কথাই এটি: অনির্ভরযোগ্য পথে দুই পক্ষ নিশ্চিতভাবে একমত হতে পারে না।
৫ মিনিটের window-র বাইরের ঘটনাও আছে। Producer timeout পেয়ে ৬ মিনিট পরে আবার পাঠালে সেটি নতুন বার্তা। DLQ থেকে redrive করলে একই কাজ আবার চলে। মানুষ একটি পুরোনো ঘটনা হাতে ফেরালেও একই।
তাহলে FIFO-র লাভ কী? Duplicate অনেক কমে, আর ক্রম মেলে, যা অনেক নকশাকে সরল করে। কিন্তু টাকা, টিকিট, stock-এর মতো কাজে নিরাপত্তা বসে consumer-এর দিকে: unique constraint, idempotency token, অথবা শর্তসাপেক্ষ লেখা। এই সমন্বয়কেই অনেকে “effectively-once” বলেন: delivery একাধিকবার হতে পারে, কিন্তু effect একবার। পরীক্ষাও সহজ: একই বার্তা হাতে দুবার পাঠিয়ে দেখুন শেষ ফল বদলায় কি না। ফল বদলালে provider-এর কোনো প্রতিশ্রুতি আপনাকে রক্ষা করবে না।
মূল উপলব্ধি: “Exactly-once” পড়লে জিজ্ঞেস করুন: কোন সীমার ভেতরে, কত সময়ের জন্য, আর আপনার বাইরের side effect কি তার ভেতরে? সাধারণত উত্তর হলো, side effect বাইরে।
প্র ০৩Queue পরিচালিত সার্ভিস হলে কি নির্ভরতার ঝুঁকি শেষ? ২০২০ সালের Kinesis ঘটনা কী শেখায়?
Managed মানে ঝুঁকি শূন্য নয়, ঝুঁকি অন্য জায়গায়। ২৫ নভেম্বর ২০২০-এ AWS-এর US-EAST-1 region-এ Kinesis Data Streams-এ বড় সমস্যা হয়। AWS-এর প্রকাশিত সারাংশ অনুযায়ী, front-end fleet-এ capacity বাড়ানোর পরে প্রতিটি server অন্য সব server-এর সাথে যোগাযোগের জন্য যত thread রাখে, তা operating system-এর সীমা পার করে। ঠিক করতে সময় লাগে, কারণ server-গুলো ধীরে ধীরে আবার চালু করতে হয়েছিল। আর যেসব AWS সার্ভিস ভেতরে Kinesis ব্যবহার করে, যেমন Cognito আর CloudWatch-এর অংশ, সেগুলোতেও প্রভাব পড়ে। সবার চোখে না পড়া একটি নির্ভরতাই পাশের সার্ভিসে সমস্যা ছড়াল।
আপনার সিস্টেমের জন্য এর অর্থ কী? প্রথমত, broker নিজেই একটি নির্ভরতা। “Queue-তে পাঠানো” call-ও ব্যর্থ হতে পারে। Order service সেই ব্যর্থতায় কী করে? ক্রেতাকে error দেয়, নাকি outbox-এ লিখে রাখে আর পরে পাঠায়? Outbox থাকলে broker-এর পতন শুধু দেরি, ক্ষতি নয়।
দ্বিতীয়ত, queue ব্যর্থতাকে লুকায়। সরাসরি ডাকলে ক্রেতা সাথে সাথে ব্যর্থতা দেখেন। Queue-তে সব সবুজ থাকে, অথচ কাজ পিছিয়ে পড়ে। আবার ফেরার পরে জমে থাকা কাজ নিচের সার্ভিসকে একসাথে আঘাত করে, তাই ধীরে ফেরাতে হয়। সেজন্য বার্তার বয়স মাপা জরুরি।
তৃতীয়ত, region নকশা। Queue সাধারণত region-ভিত্তিক সার্ভিস। Region পড়লে কী হবে, সেই প্রশ্ন পর্ব ২৪-এর RTO আর RPO-র হিসাবের অংশ। ভেতরে জমে থাকা বার্তা region ফিরে না এলে পাওয়া নাও যেতে পারে। তাই কিছু দল দুই region-এ পাঠায়, আর idempotency দিয়ে duplicate সামলায়, কিন্তু তাতে খরচ আর জটিলতা বাড়ে। সবার জন্য এটি দরকার নেই।
মূল উপলব্ধি: Managed সার্ভিস আপনার কাছ থেকে চালানোর দায় নেয়, কিন্তু নির্ভরতার দায় নেয় না। প্রতিটি জোড়ায় জিজ্ঞেস করুন: এটি না থাকলে আমার সিস্টেম কী করবে?
প্র ০৪Saga কি সত্যিই transaction-এর বিকল্প? “Compensation” আর “undo” কি একই?
এক নয়, আর এই পার্থক্যই saga-র আসল কঠিন দিক। Garcia-Molina আর Salem ১৯৮৭ সালে SIGMOD-এ “Sagas” প্রবন্ধে দেখান, দীর্ঘ transaction ডেটাবেসের resource অনেকক্ষণ আটকে রাখে। তাঁদের প্রস্তাব: একে ছোট ছোট transaction-এ ভাগ করা, প্রতিটির একটি compensating transaction থাকবে। তখন লক্ষ্য ছিল একই ডেটাবেসের ভেতরের দীর্ঘ কাজ। Microservice যুগে একই ধারণা সার্ভিস জুড়ে ব্যবহার হচ্ছে, কারণ two-phase commit সেখানে কম ব্যবহারযোগ্য: সবাইকে একই protocol বুঝতে হয়, lock আটকে থাকে, আর বেশিরভাগ cloud সার্ভিস ও বাইরের API তাতে অংশ নেয় না।
Undo মানে যেন কিছুই ঘটেনি। Compensation মানে একটি নতুন ঘটনা, যা ব্যবসার দিক থেকে আগের প্রভাব কাটায়। Refund করলে টাকা ফেরে, কিন্তু ক্রেতার statement-এ দুটি লাইন থাকে, আর হয়তো ফি কাটা যায়। ক্রেতাকে পাঠানো email ফেরানো যায় না, শুধু “দুঃখিত” পাঠানো যায়। ছাপানো টিকিট ফেরাতে কাউকে হাতে কাজ করতে হয়।
আরেকটি ফাঁক হলো isolation. Saga চলার সময়ে মাঝের অবস্থা অন্যরা দেখে ফেলে। Stock ধরা আছে দেখে অন্য ক্রেতা “স্টক শেষ” দেখলেন, অথচ এক মিনিট পরে অর্ডার বাতিল হয়ে পণ্য ফিরল। এর সমাধান হিসেবে semantic lock রাখা হয়, যেমন PENDING অবস্থা। ধাপের ক্রমও ভাবনার বিষয়: যে ধাপ ফেরানো কঠিন, তাকে শেষে রাখুন। Courier বুকিং ফেরানো সহজ, ক্রেতাকে “পাঠানো হয়েছে” জানানো নয়।
Compensation নিজেও ব্যর্থ হতে পারে। Refund API পড়ে থাকলে? তখন retry, আর শেষ পর্যন্ত মানুষের হাতে পাঠানোর পথ। এমন হাতে-দেখার তালিকা না থাকলে ব্যবস্থাটি সম্পূর্ণ নয়। Temporal-এর পূর্বসূরি Uber-এর Cadence-এর মতো engine এই অসমাপ্ত অবস্থা ধরে রাখতে সাহায্য করে, কিন্তু কী করতে হবে সেটি ব্যবসার সিদ্ধান্ত।
মূল উপলব্ধি: Saga কারিগরি কৌশল হলেও, প্রশ্নগুলো ব্যবসার: কী ফেরানো যায়, কতক্ষণে, কার খরচে। এগুলো ঠিক না করে workflow লিখলে কোডটি সুন্দর হবে, কিন্তু সমস্যা থেকে যাবে।
প্র ০৫Event-driven নকশায় loose coupling কি সত্যিই আলগা? CloudEvents কোন সমস্যা সারে, আর কোনটি সারে না?
Producer শ্রোতাকে চেনে না, তাই মনে হয় জোড়া নেই। কিন্তু জোড়া সরে গেছে, মুছে যায়নি। আগে জোড়া ছিল API call-এ, যা কোড পড়লে দেখা যায়। এখন জোড়া ঘটনার আকৃতি আর অর্থে, যা দেখা যায় না। Hyrum Wright-এর নামে পরিচিত পর্যবেক্ষণটি এখানে খাটে: যথেষ্ট ব্যবহারকারী থাকলে আপনার সিস্টেমের প্রতিটি দৃশ্যমান আচরণের ওপর কেউ না কেউ নির্ভর করবে। ঘটনার field-এর ক্রম, একটি সংখ্যা string না number, এমনকি একটি ঘটনা আরেকটির আগে আসে কি না, সবই।
দ্বিতীয় জোড়া হলো সময়ের। Saga-র পরের ধাপ যদি একটি ঘটনার ওপর নির্ভর করে, তাহলে কেউ সেই ঘটনার type-এর নাম বদলালে পুরো ব্যবসার প্রবাহ থামে। Simulator-এ আমরা ঠিক এটিই দেখেছি: একটি subscription হারাল, কোনো error নেই। Martin Fowler-এর মতো লেখকেরা “event notification” আর “event-carried state transfer”-এর পার্থক্য দেখিয়ে সতর্ক করেছেন: ঘটনায় বেশি তথ্য দিলে কম ফিরে ডাকতে হয়, কিন্তু আকৃতির ওপর নির্ভরতা বাড়ে।
CloudEvents কোন সমস্যা সারে? CNCF-এর Serverless Working Group থেকে শুরু হওয়া এই spec ১.০-তে পৌঁছায় ২০১৯ সালে। আগে প্রতিটি উৎসের খাম আলাদা, তাই M উৎস আর N পাঠকের জন্য M × N রকম parsing. একটি সাধারণ খামে সেটি M + N-এ নামে। Routing, tracing, audit, idempotency-র মতো সাধারণ কাজ খাম দেখেই করা যায়, ভেতরের ব্যবসার ডেটা না বুঝেই।
কী সারে না? ভেতরের data অংশের অর্থ। CloudEvents বলে না amount পয়সায়, না টাকায়। সেজন্য schema, registry আর পরিবর্তনের নিয়ম লাগে। Team-গুলোর মধ্যে সমঝোতা লাগে: কোন ঘটনা public, আর কোনটি একটি সার্ভিসের ভেতরের। ভেতরের ঘটনা বাইরে ফাঁস করলে ভেতরের নকশা বদলানো কঠিন হয়। একটি সরল অভ্যাস হলো event catalog রাখা: কোন ঘটনা, কে মালিক, কে পাঠক।
মূল উপলব্ধি: Loose coupling মানে জোড়া নেই, এমন নয়। মানে জোড়া বদলানো যায়, যদি সেটি লেখা থাকে। Schema আর মালিকানা ছাড়া event-driven নকশা হলো অদৃশ্য তারে বাঁধা সিস্টেম।
অনুশীলন
কোনো cloud account লাগবে না। Simulator বদলে চালানোই যথেষ্ট।
-
Worker-এর হিসাব (numerical): বার্তা আসে ৪০/s, প্রতিটিতে ০.২৫ s লাগে। লক্ষ্য অপেক্ষা ১২০ s-এর কম। এখন depth ৬,০০০. (ক) worker-প্রতি গ্রহণযোগ্য backlog কত? (খ) এই জটের জন্য কত worker? (গ) ৭০% ব্যস্ততায় স্বাভাবিক সময়ে কত? (ঘ) ১৫ worker-এ জট কাটতে কত সময়?
(ক) ১২০ ÷ ০.২৫ = ৪৮০ বার্তা। (খ) ৬,০০০ ÷ ৪৮০ = ১২.৫, অর্থাৎ ১৩ worker. (গ) worker-প্রতি ৪/s, তাই ৪০ ÷ (৪ × ০.৭) = ১৪.৩, অর্থাৎ ১৫ worker. (ঘ) ১৫ worker করে ৬০/s, নিট ৬০ − ৪০ = ২০/s, সময় ৬,০০০ ÷ ২০ = ৩০০ s. লক্ষ করুন, জটের হিসাবের চেয়ে স্বাভাবিক হিসাবই বড়। তাই দুটোর বড়টি নিন, ১৫. ১৩ worker-এ নিট মাত্র ১২/s, তখন জট কাটতে ৫০০ s লাগত, আর স্বাভাবিক সময়েও ব্যস্ততা থাকত প্রায় ৭৭%.
-
আউটপুট অনুমান করুন:
visibility_dup.js-এ W1-এরwork৭০ করে শুধুrun(60, false)চালালে শেষ লাইনে কী আসবে? চালানোর আগে লিখুন, তারপর মিলিয়ে দেখুন।শেষ লাইন:
result: receives=2 charges=2. W1 t = ০-তে নেয়, আড়াল t = ৬০ পর্যন্ত। কাজ শেষ হবে t = ৭০-এ, তার আগেই t = ৬০-এ আড়াল ফুরায়, আর W2 নেয়। W2-র কাজ ১০ s, সেও t = ৭০-এ শেষ করে। দুজনেই কার্ড কাটে। তাই timeout বাড়িয়ে নিশ্চিন্ত হওয়া যায় না: কাজ একটু ধীর হলেই সমস্যা ফেরে। সমাধানuseKeyআর heartbeat. -
সীমা বদলে অনুমান করুন:
dlq_flow.js-এMAX_RECEIVES১ আর ২ করলে DLQ-তে কে কে যাবে, আর কখন?সীমা ১: t = ৩০-এ B আর C দুটোই DLQ-তে,
in DLQ: B, C. B সামান্য সাময়িক ব্যর্থতায় ভুলভাবে সরে গেল। সীমা ২: B t = ৩০-এ দ্বিতীয় চেষ্টায় সফল, আর C t = ৬০-এ DLQ-তে। মানে সীমা ৩ থেকে ২ করলে C ৩০ সেকেন্ড আগে সরে, কিন্তু সাময়িক ব্যর্থতার পরিসর কমে। সীমার বাছাই এই দুইয়ের মধ্যে আপস। -
নকশা সমালোচনা: একটি দলের নকশা: অর্ডার হলে topic থেকে সরাসরি email function আর courier function ডাকা হয়। কোনো queue বা DLQ নেই। Courier function টাকা কাটারও কাজ করে। ঘটনায় ক্রেতার পুরো ঠিকানা, ফোন আর কার্ডের শেষ ১২ সংখ্যা আছে। সব ধরনের ঘটনার জন্য একটিই FIFO queue আর একটিই message group. কী কী সমস্যা, আর কী বদলাবেন?
- ধরে রাখার জায়গা নেই: Function বন্ধ বা throttled হলে retry-র সীমা শেষে বার্তা হারাতে পারে। প্রতিটির সামনে queue আর DLQ বসান।
- এক function-এ দুই কাজ: Courier ব্যর্থ হলে retry-তে টাকা আবার কাটা যায়। টাকা কাটা আলাদা idempotent ধাপে নিন, workflow-তে saga বানান।
- Payload-এ সংবেদনশীল তথ্য: কার্ডের সংখ্যা ঘটনায় একেবারেই নয়। শুধু order id পাঠান, পাঠক নিজের অনুমতি দিয়ে যা লাগে সেটুকু আনবে।
- এক message group: পুরো ব্যবসা একটি সারিতে। একটি poison message সব থামাবে, throughput-ও সীমিত। Group ID দিন order-ভিত্তিক, বা ক্রম না লাগলে standard queue.
- মাপ নেই: বার্তার বয়স, DLQ depth, trace id যোগ করুন।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমএকটি নিরাপদ consumer-এর দিক থেকে বার্তার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- Producer বার্তা queue-তে পাঠায়
- Worker বার্তা নেয়, বার্তা আড়ালে যায় আর ঘড়ি শুরু হয়
- Worker idempotency key দেখে, duplicate কি না
- Worker কাজ করে আর ফল ও key একসাথে লেখে
- Worker রসিদ দিয়ে বার্তা delete করে
অর্ডার হলে একই ঘটনা stock, email আর report তিনটি সার্ভিসের দরকার। প্রত্যেকে নিজের গতিতে চলবে, আর একজন বন্ধ থাকলে বাকিদের সমস্যা হবে না। সবচেয়ে উপযুক্ত নকশা কোনটি?
- একটি queue, তিন সার্ভিস সেখান থেকে পড়বে
- Order service পরপর তিনটি সার্ভিসকে সরাসরি HTTP-তে ডাকবে
- একটি topic, প্রতিটি সার্ভিসের সামনে নিজের queue
- একটি topic, সরাসরি তিন সার্ভিসে, কোনো queue ছাড়া
একটি queue-র visibility timeout ৩০ সেকেন্ড। একটি worker-এর কাজে ৪৫ সেকেন্ড লাগে, আর আরেকটি worker খালি আছে। Worker দুটি সুস্থ। কী ঘটবে?
- Queue অপেক্ষা করবে, কারণ worker সুস্থ আছে
- ৩০ সেকেন্ডে বার্তা ফিরে আসবে আর একই কাজ দুবার হতে পারে
- বার্তা সাথে সাথে DLQ-তে যাবে
- বার্তা মুছে যাবে, কাজ হারাবে
একটি poison message প্রতিবার ব্যর্থ হয়, আর worker ব্যর্থতায় delete করে না। Visibility timeout ৬০ s, maxReceiveCount ৫, প্রথম receive t = ০-তে, আর বার্তা দেখা গেলেই worker নেয়। সীমা পার হওয়ার পরের receive-এর চেষ্টায় বার্তা DLQ-তে যায়। সেটি কখন?
- t = ৬০ s
- t = ২৪০ s
- t = ৩৬০ s
- t = ৩০০ s
একটি FIFO queue-তে ১০০০ বার্তা, প্রতিটিতে ১ সেকেন্ড লাগে, ১০টি worker. সব বার্তার message group ID একটিই। মোট সময় কত হবে, আর কেন?
- প্রায় ১০০০ সেকেন্ড, কারণ একটি group-এ বার্তা একে একে চলে
- প্রায় ১০০ সেকেন্ড, কারণ ১০টি worker ভাগ করে নেবে
- প্রায় ১ সেকেন্ড, কারণ FIFO batch করে
- প্রায় ১০,০০০ সেকেন্ড, কারণ প্রতিটি worker সব বার্তা পড়ে
Queue-তে ৪,৮০০ বার্তা জমে আছে। প্রতিটিতে ০.২৫ সেকেন্ড লাগে, আর লক্ষ্য হলো কোনো বার্তা ৪০ সেকেন্ডের বেশি অপেক্ষা করবে না। Backlog per worker ধরে এই মুহূর্তে কত worker লাগবে?
- ৪০
- ১২
- ৩০
- ১,২০০
একটি saga-তে stock রাখা আর টাকা কাটা সফল হয়েছে, courier বুকিং চূড়ান্তভাবে ব্যর্থ। সঠিক compensation কোনটি?
- সবগুলো সার্ভিসে একসাথে ডেটাবেস ROLLBACK চালানো
- উল্টো ক্রমে: আগে refund, তারপর stock ছেড়ে দেওয়া
- শুধু stock ছেড়ে দেওয়া, টাকা কাটা অবস্থায় অর্ডার রেখে দেওয়া
- কিছুই না করা, কারণ পরে ব্যবহারকারী আবার চেষ্টা করবেন
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- আকৃতি চারটি: queue, topic, event bus, stream. নাম নয়, “একটি বার্তা কে পায়” দিয়ে বাছুন।
- Receive মোছে না। আগে কাজ, পরে delete. Duplicate ধরে নিন, consumer idempotent করুন।
- প্রতিটি queue-তে DLQ, সীমা আর alarm. কারণ সারিয়ে, ধীরে redrive করুন।
- ক্রম দামি: message group ছোট রাখুন। Batching আর long polling বিল কমায়, কিন্তু timeout আর partial failure হিসাব করুন।
- টাকা-জড়িত ধাপে orchestration, স্বাধীন প্রতিক্রিয়ায় choreography. মাপুন depth, বার্তার বয়স আর worker-প্রতি backlog.
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৭১ · Serverless architecture — API, fan-out, idempotency আর কখন নয়পরবর্তী পর্বএই পর্বের queue আর workflow নিয়ে পুরো নকশা।
- ক্লাউড কম্পিউটিং ও DevOps · কোর্স হোম৮১টি পর্বপুরো কোর্সের রোডম্যাপ।
- Message queue — System Design কোর্সতত্ত্বProducer, consumer, acknowledgement আর DLQ-এর ভিত্তি।
- Pub/sub আর event-driven architectureতত্ত্বFan-out আর choreography বনাম orchestration-এর গভীর আলোচনা।
- Distributed transactionগভীরে যানTwo-phase commit, saga আর outbox pattern.