Course-এর সূচি

13.2

Visibility Timeout, Long Polling আর Dead-Letter Queue

একটা message শুরু থেকে শেষ পর্যন্ত কোন পথে যায়, একই message দুবার process হওয়া, long polling, DLQ, delay queue আর বড় payload।

26 মিনিট পড়া + Animation

টিপস: ডটেড-আন্ডারলাইন করা যেকোনো শব্দে ট্যাপ বা Hover করলে তার অর্থ দেখবে।

এই লেসন শেষে তুমি পারবে

  • একটা SQS message-এর পুরো জীবন ধরে ধরে বলতে: sent, delayed, visible, in flight, deleted, expired, কিংবা dead-letter queue-তে চলে যাওয়া, আর কোন জিনিস তাকে এক অবস্থা থেকে আরেক অবস্থায় নিয়ে যায়।
  • Visibility timeout কীসের হাত থেকে বাঁচায় তা বোঝাতে, কোনো কাজের জন্য ঠিক মান বেছে নিতে (default ৩০ সেকেন্ড, ০ সেকেন্ড থেকে ১২ ঘণ্টা), আর লম্বা timeout বা ChangeMessageVisibility heartbeat দিয়ে একই কাজ দুবার হওয়া বন্ধ করতে।
  • Short polling আর long polling আলাদা করে চিনতে, empty আর false empty response কী তা বোঝাতে, long polling (২০ সেকেন্ড পর্যন্ত) চালু করতে, আর এতে কত সাশ্রয় হয় হিসাব করতে।
  • Redrive policy আর maxReceiveCount দিয়ে dead-letter queue সাজাতে, retention-এর ফাঁদ এড়াতে, তার ওপর alarm বসাতে, আর bug ঠিক হলে message-গুলো redrive করে ফেরত পাঠাতে।
  • Delay queue আর message timer (১৫ মিনিট পর্যন্ত) ব্যবহার করতে, আর ১ MiB-এর চেয়ে বড় payload Extended Client Library আর S3 দিয়ে পাঠাতে।
  • এমন consumer লিখতে, যেটা at-least-once delivery সামলে টিকে থাকে: idempotent, ঠিক timeout, ঠিক batch size আর ঠিকঠাক clean-up call সহ।

#একটা message-এর জীবন

লেসন ১৩.১-এ আমরা বড় ছবিটা দেখেছি: producer queue-তে message পাঠায়, consumer নিজের গতিতে টেনে নেয়, আর হঠাৎ ভিড় এলে queue সেটা সামলে নেয়। এই লেসনে বাক্সটা খুলে দেখব, SendMessage থেকে শুরু করে message হারিয়ে যাওয়া পর্যন্ত একটা message-এর আসলে কী হয়। SQS-এর বেশিরভাগ পরীক্ষার প্রশ্ন, আর production-এর বেশিরভাগ ঝামেলা, ঠিক এই খুঁটিনাটি থেকেই আসে।

#Send, receive, লুকানো, delete

  1. Sent। Producer SendMessage call করে। SQS message-টা কয়েকটা Availability Zone জুড়ে একাধিক server-এ কপি করে রাখে, আর একটা message ID ফেরত দেয়।
  2. Delayed (ঐচ্ছিক)। Queue-তে delay থাকলে, বা message-এর নিজের timer থাকলে, শুরুতে message-টা ততক্ষণ অদৃশ্য থাকে। এটা নিয়ে পরে বলছি।
  3. Visible (জমা আছে)। Message queue-তে অপেক্ষা করে। যে consumer ReceiveMessage call করবে, সে-ই এটা পেতে পারে। কতগুলো message জমা থাকতে পারবে, তার কোনো সীমা নেই।
  4. In flight। কোনো consumer message-টা receive করে। SQS এটা delete করে না; বরং visibility timeout-এর সময়টুকু সব consumer-এর চোখ থেকে লুকিয়ে রাখে। এই ফাঁকে consumer কাজটা করে।
  5. Deleted। কাজ শেষ হলে consumer DeleteMessage call করে। শুধু এটাই message-টা চিরতরে মুছে দেয়।

In flight অবস্থা থেকে আরও দুটো বের হওয়ার পথ আছে। Visibility timeout শেষ হওয়ার আগে delete না এলে message আবার visible হয়ে যায় (৩ নম্বর ধাপে ফেরত), আর অন্য কেউ চেষ্টা করতে পারে। আর message যদি অনেকবার receive হয়ে থাকে, কিংবা retention period-এর (default ৪ দিন, ১ মিনিট থেকে ১৪ দিন, লেসন ১৩.১-এ দেখেছ) চেয়ে বেশি সময় queue-তে থাকে, তাহলে সে queue ছেড়ে যায়: প্রথম ক্ষেত্রে dead-letter queue-তে (যদি সেটা বসানো থাকে), দ্বিতীয় ক্ষেত্রে সোজা মুছে যায়।

SQS কখনো কাউকে message ঠেলে পাঠায় না, আর নিজে থেকে কখনো ঠিক করে না যে কাজ শেষ। সে জানে শুধু দুটো কথা: কেউ message-টা receive করেছে, আর কেউ সেটা delete করেছে (কিংবা করেনি)।

#Receipt handle আর receive count

প্রতিটা receive-এ দুটো জিনিস ফেরত আসে, যেগুলো তোমার জানা দরকার:

  • Receipt handle। Message-এর এই receive-টার একটা টিকিট। DeleteMessage আর ChangeMessageVisibility-এ এটাই লাগে, message ID না। একই message পরে আবার receive হলে নতুন receive একটা নতুন receipt handle পায়; সবসময় সবচেয়ে নতুনটা দিয়েই delete করো, কারণ পুরোনো handle দিয়ে delete করলে call সফল দেখালেও message না-ও মুছতে পারে।
  • ApproximateReceiveCount। Message-এর গায়ে একটা গোনার যন্ত্র: এখন পর্যন্ত কতবার এটা receive হয়েছে। তৃতীয়বার receive হওয়া message-এর count ৩। Dead-letter queue এই সংখ্যাটাই ব্যবহার করে।

একটা receive call ১ থেকে ১০টা message ফেরত দিতে পারে (MaxNumberOfMessages); প্রত্যেকটার নিজের receipt handle, নিজের visibility timer।

একটা order, দুজন worker: একটা message-এর জীবন

একটা food-delivery app প্রতিটা নতুন order একটা SQS queue-তে রাখে। দুজন worker order টেনে নিয়ে invoice লেখে। একটা message-কে send থেকে delete পর্যন্ত অনুসরণ করো, আর দেখো মাঝপথে একজন worker মরে গেলে কী হয়।

পরিস্থিতি

Receive, process, delete: সব ঠিকঠাক চললে যা হয়।

Order appproducer
orders queuetimeout ৩০ s১টা visible
Worker A
Worker B
Invoice DB
SendMessage
ধাপ 1 / 5
1

Order app order #৪৫২১ queue-তে পাঠিয়ে সাথে সাথে customer-কে উত্তর দিয়ে দেয়। Message এখন জমা আছে আর visible: যে consumer চাইবে, সে-ই পেতে পারে।

#Visibility timeout

#জিনিসটা কী, আর কেন আছে

EC2 instance বা Lambda function-এর মতো কোনো consumer যখন একটা message receive করে, SQS তখন নির্দিষ্ট একটা সময়ের জন্য message-টা সব consumer-এর কাছ থেকে লুকিয়ে রাখে। এই সময়টাই visibility timeout। এর কাজ একটাই: একই message যেন একসাথে কয়েকজন consumer process করে না ফেলে।

  • Consumer সময়ের মধ্যে কাজ শেষ করে DeleteMessage call করলে message চিরতরে চলে যায়।
  • Consumer ব্যর্থ হলে বা crash করলে কোনো delete আসে না। সময় ফুরালে message আবার visible হয়, আর অন্য কোনো consumer (বা সেই একই consumer) সেটা process করতে পারে।

মালিকের note-এ এই ব্যবস্থাটাকে বলা হয়েছে SQS-এর হৃৎস্পন্দন (heartbeat): এটাই নিশ্চিত করে যে কাজ করতে থাকা machine মরে গেলেও message হারায় না।

#Default, সীমা, আর কোথায় বসাও

  • Default ৩০ সেকেন্ড, সর্বনিম্ন ০, সর্বোচ্চ ১২ ঘণ্টা (৪৩,২০০ সেকেন্ড)।
  • Queue-তে (VisibilityTimeout attribute): অন্য কিছু বলা না থাকলে প্রতিটা receive এই মানটাই নেয়।
  • একটা receive call-এ (ReceiveMessage-এর VisibilityTimeout parameter): এই call যে message-গুলো ফেরত দেয়, তাদের জন্য আলাদা মান।
  • Receive করা একটা message-এ ChangeMessageVisibility দিয়ে: কাজ চলার সময় timeout বাড়াও বা কমাও। নতুন মান গোনা শুরু হয় call করার মুহূর্ত থেকে, প্রথম receive থেকে না।
  • ১২ ঘণ্টার সীমা গোনা হয় প্রথম receive থেকে; বাড়ালেও এটা নতুন করে শুরু হয় না। যে কাজে ১২ ঘণ্টার বেশি লাগে, সেটা একটা SQS message-এর জিনিসই না: ছোট ছোট ধাপে ভাগ করো, বা AWS Step Functions ব্যবহার করো (chapter ১৫)।

#খুব ছোট, নাকি খুব লম্বা

Default ৩০ সেকেন্ড মানে consumer-এর হাতে ৩০ সেকেন্ড আছে message process করে delete করার জন্য। ভুল মান বাছলে দুই উল্টো দিকে বিপদ হয়:

Timeoutকী ঘটেলক্ষণ
খুব ছোট (কাজের চেয়ে কম)প্রথম worker ব্যস্ত থাকতেই message আবার দেখা দেয়; দ্বিতীয় worker একই কাজ শুরু করেএকই কাজ দুবার: দুবার email, দুবার টাকা পাঠানো, compute নষ্ট
খুব লম্বা (২ মিনিটের কাজে কয়েক ঘণ্টা)Crash হলে বাকি সময়টুকু message লুকিয়ে থাকে, কেউ আবার চেষ্টা করতে পারে নাধীরে সেরে ওঠা: একটা crash করা worker একটা order-কে কয়েক ঘণ্টা আটকে রাখে

খুব ছোট, খুব লম্বা, ঠিকঠাক

একটা wallet app-এর payout worker-এর প্রতিটা message-এ প্রায় ২ মিনিট লাগে, কারণ সে একটা ধীর bank API call করে। Visibility timeout সময়ের সাথে এই কাজের কী হাল করে, দেখো।

পরিস্থিতি

কাজ চলতে চলতেই সময়ের জানালা বন্ধ হয়ে যায়।

Worker A
payouts queue৩০ s পর্যন্ত লুকানো
Worker B
Bank API
payout #৮৮ (t = ০)
ধাপ 1 / 5
1

t = ০-তে Worker A payout #৮৮ পায়। Queue-তে default visibility timeout, ৩০ সেকেন্ড।

#Timeout বাড়ানো: ChangeMessageVisibility আর heartbeat

যে worker জানে কাজটা লম্বা হবে, কিংবা জানেই না কতক্ষণ লাগবে, সে কাজ চলার সময়ই আরও সময় চেয়ে নিতে পারে:

  • একবারেই বাড়ানো। লম্বা কাজ শুরুর আগে এমন একটা timeout দিয়ে ChangeMessageVisibility call করো, যেটা পুরো কাজ ঢেকে দেয়।
  • Heartbeat। যে কাজের দৈর্ঘ্য ওঠানামা করে, তার জন্য এটা ভালো: শুরু করো মাঝারি একটা timeout দিয়ে, আর পেছনের একটা timer থেকে সেটা শেষ হওয়ার আগেই আবার বাড়াও (যেমন প্রতি ২০ সেকেন্ডে আরও ৬০ সেকেন্ড)। Worker যতক্ষণ বেঁচে আছে, message লুকানো থাকে; worker মরে গেলে heartbeat থামে, আর message এক মিনিটের মধ্যেই আবার দেখা দেয়, কয়েক ঘণ্টা পরে না।
  • আগেই ছেড়ে দেওয়া। ০ দিয়ে ChangeMessageVisibility করলে message সাথে সাথে visible হয়ে যায়। Worker বন্ধ হতে চললে (Auto Scaling-এর scale-in, Spot interruption-এর নোটিশ) বা এমন message পেলে যেটা সে জানে সামলাতে পারবে না, তখন এটা কাজে লাগে।

#মান বেছে নেওয়া

শুরু করার একটা ভালো জায়গা: visibility timeout হবে একটা message-এর স্বাভাবিক কাজের সময়ের একটু বেশি, তোমার code-এর ভেতরের retry সহ (একটা receive-এ ১০টা message এলে আর একটার পর একটা process করলে, ১০টারই মোট সময়)। কদাচিৎ আসা লম্বা কাজের জন্য heartbeat যোগ করো। SQS যখন একটা Lambda function-কে message দেয়, তখন AWS-এর পরামর্শ হলো queue-এর visibility timeout হবে function timeout-এর অন্তত ছয় গুণ, কারণ throttle হলে Lambda একটা batch আবার চেষ্টা করতে পারে; বিস্তারিত serverless chapter-এ (লেসন ১৫.১)।

নিজে চালিয়ে দেখো: এমন একটা combination খোঁজো যেটায় কাজ দুবার হয়, একটা যেটা নিরাপদ, আর ছোট ও খুব লম্বা timeout-এ crash হলে কী হয় দেখো।

Visibility timeout lab

কাজের দৈর্ঘ্য, visibility timeout আর worker কী করে, বেছে নাও। Timeline দেখাবে message কখন লুকানো, কখন ফিরে আসে, আর শেষ পর্যন্ত কতজন worker একই কাজ করে ফেলে।

প্রতি message-এ কাজের সময়

Queue-এর visibility timeout

Worker

০ মিনিট৫৩ s১০৫ s২.৬ মিনিট৩.৫ মিনিটMessageWorker AWorker BWorker CWorker D
লুকানো (in flight)আবার visibleকাজ চলছে

একই কাজ একাধিকবার: ৪ জন worker একই message process করে

কাজ শেষ হওয়ার আগেই timeout ফুরিয়ে যায়, তাই প্রথম worker ব্যস্ত থাকতেই message আবার দেখা দেয়। Timeout কাজের সময়ের ওপরে তোলো, বা heartbeat চালু করো।

#At-least-once মানেই idempotent consumer

একেবারে নিখুঁত visibility timeout-ও exactly-once processing-এর কথা দেয় না। Standard queue অন্তত একবার পৌঁছায় (at least once): খুব কদাচিৎ একটা দ্বিতীয় কপি চলে আসে, যেমন SQS তার কোনো একটা server-এ delete নিশ্চিত করতে না পারলে, বা worker কাজ করে ফেলার পর কিন্তু delete করার আগে মরে গেলে। তাই consumer-কে হতে হবে idempotent: একই message দুবার process করার ফল একবার process করার মতোই হতে হবে।

সাধারণ উপায়গুলো:

  • Message-এর ভেতরের একটা ব্যবসায়িক ID (order ID, payout ID) payment provider-কে call করার বা database-এ লেখার সময় idempotency key হিসেবে দাও।
  • Process হয়ে যাওয়া message ID-র একটা table রাখো (conditional write দিয়ে, যেমন DynamoDB-তে), আর যে ID আগেই আছে সেই message বাদ দাও।
  • কাজটাকেই স্বভাবগতভাবে idempotent বানাও: “order-এর status PAID করে দাও” বারবার করলেও ক্ষতি নেই; “balance-এ ৫০০ টাকা যোগ করো” বারবার করলে বিপদ।

Duplicate যদি একেবারেই মেনে নেওয়া না যায়, আর consumer-কে idempotent বানানোও সম্ভব না হয়, তাহলে হাতিয়ার হলো deduplication সহ FIFO queue, যেটা আছে লেসন ১৩.৩-এ।

#Short polling আর long polling

#Short polling: সাথে সাথে উত্তর

Default অবস্থায় একটা ReceiveMessage call সাথে সাথে উত্তর দেয়, queue খালি থাকলেও। এটাই short polling। দুটো কারণে এটা অপচয়ের:

  • Empty response। টানা loop-এ ঘুরতে থাকা worker বারবার জিজ্ঞেস করে, আর বেশিরভাগ সময় শোনে “কিছু নেই”। প্রতিটা call-ই bill হওয়া request, তাই অলস queue-ও টাকা খায়, আর loop CPU পোড়ায়।
  • False empty response। Short poll SQS-এর server-গুলোর একটা অংশকেই জিজ্ঞেস করে (এলোমেলোভাবে বাছা), আর সেগুলো থেকেই উত্তর দেয়। তোমার message যদি এমন server-এ থাকে যাকে জিজ্ঞেস করা হয়নি, তাহলে queue খালি না হলেও উত্তর খালি আসে। পরের কোনো poll message-টা খুঁজে পায়, তাই কিছু হারায় না, কিন্তু বাড়তি call-এর দাম দিতে হয়, আর অপেক্ষাও বাড়ে।

#Long polling: ২০ সেকেন্ড পর্যন্ত অপেক্ষা

Long polling-এ SQS সাথে সাথে খালি উত্তর দেয় না। একটা message আসার জন্য সে wait time (১ থেকে ২০ সেকেন্ড) পর্যন্ত অপেক্ষা করে, আর সব server-কে জিজ্ঞেস করে। Message এসে গেলে সাথে সাথে ফেরত দেয়; পুরো অপেক্ষার মধ্যে কিছুই না এলে তবেই খালি উত্তর দেয়।

তাই long polling:

  • API call-এর সংখ্যা আর empty response কমায়, ফলে খরচ কমে;
  • false empty response দূর করে;
  • latency কমায়: পরের poll-এর অপেক্ষা না করে message আসামাত্রই ফেরত যায়।

#কীভাবে চালু করবে

  • Queue-তে: ReceiveMessageWaitTimeSeconds ০-এর বেশি করো, সাধারণত ২০ (সর্বোচ্চ)। তখন প্রতিটা receive long-poll করে।
  • একটা call-এ: ReceiveMessage-এ WaitTimeSeconds (০ থেকে ২০) পাঠাও। Call-এর মান queue-এর মানের ওপরে জেতে।
  • SDK-এর HTTP read timeout wait time-এর চেয়ে লম্বা রাখো, নইলে SQS উত্তর দেওয়ার আগেই client হাল ছেড়ে দেবে।

Short polling-এর কাজ যে একেবারে নেই তা না: একটা thread-কে যখন কয়েকটা queue poll করতে হয় আর কোনো একটায় আটকে থাকা চলে না, বা application-এর সাথে সাথে উত্তর দরকার, তখন। প্রায় বাকি সব ক্ষেত্রে long polling-ই ভালো default।

Short polling বনাম long polling

একটা notification worker এমন একটা queue পড়ে, যেটা রাতের বেশিরভাগ সময় খালি থাকে। প্রতিটা receive call একটা bill হওয়া request, খালি হোক বা না হোক। দুই ধরনে কতগুলো call হয় আর নতুন message কত দ্রুত ধরা পড়ে, তুলনা করো।

পরিস্থিতি

Wait time ০ (default): প্রতিটা call সাথে সাথে ফেরে।

Consumercall: ১
SQS queueঅনেক server
Producer
receive (wait ০)
ধাপ 1 / 4
1

Queue attribute ReceiveMessageWaitTimeSeconds ০, মানে default, তাই এটা short polling। Worker message চায়।

#Dead-letter queue

#Poison message-এর সমস্যা

কখনো কখনো message-টাই খারাপ: কোনো field-এর type ভুল, যে customer-এর কথা বলা হয়েছে সে মুছে গেছে, কিংবা code-এ এমন একটা bug, যেটা শুধু এই input-এই ধরা পড়ে। Consumer এটা receive করে, ব্যর্থ হয়, delete করে না; visibility timeout শেষ হয়; পরের consumer receive করে, একইভাবে ব্যর্থ হয়। এই অসীম চক্র (poison message বা poison pill) worker আর API call নষ্ট করে, log ভাসিয়ে দেয়, আর শেষমেশ retention period পেরিয়ে message-টা মুছে যায়, ফলে কেউ খুঁজে দেখার সুযোগও পায় না।

#Redrive policy আর maxReceiveCount

সমাধান হলো একটা dead-letter queue (DLQ): একটা সাধারণ SQS queue, যেটা তুমি নিজে বানাও, তারপর বারবার ব্যর্থ হওয়া message-এর গন্তব্য হিসেবে তার নাম দাও। দুটোকে জোড়া লাগায় source queue-এর ওপর বসানো একটা redrive policy:

  • deadLetterTargetArn: DLQ-এর ARN।
  • maxReceiveCount: কতবার receive হতে দেওয়া হবে (১ থেকে ১,০০০)। একটা message ততবার receive হয়ে যাওয়ার পর আবার ফিরে এলে SQS তাকে আরেকবার না দিয়ে DLQ-তে সরিয়ে দেয়।

maxReceiveCount ৩ হলে message তিনবার সুযোগ পায়; তৃতীয়বার ব্যর্থ হওয়ার পর তাকে সরিয়ে রাখা হয়, পেছনের সুস্থ message-গুলো স্বাভাবিকভাবে চলে, আর তোমার team পরে DLQ-তে পড়ে দেখতে পারে আসলে কী ভুল ছিল।

সংখ্যাটা কীভাবে বাছবে: এত বেশি, যাতে কোনো dependency অল্প সময়ের জন্য বিকল হলে (database failover, throttle হওয়া API) সুস্থ message DLQ-তে না চলে যায়; আবার এত কম, যাতে আসল poison message অনেকগুলো চেষ্টা নষ্ট না করে। ৩ থেকে ১০-এর মধ্যে মান খুবই প্রচলিত।

#নিয়মগুলো

  • DLQ-কে source queue-এর একই account আর Region-এ থাকতে হবে।
  • DLQ-কে হতে হবে একই type-এর: standard queue-এর জন্য standard DLQ, FIFO queue-এর জন্য FIFO DLQ।
  • DLQ-এর ওপর redrive allow policy বলে দেয় কোন source queue এটা ব্যবহার করতে পারবে: সবাই (default), নাম ধরে ১০টা পর্যন্ত queue (byQueue), বা কেউই না।
  • FIFO queue-তে DLQ দিও না, যদি ক্রম ভাঙা আটকে থাকার চেয়েও খারাপ হয় (যেমন এমন একটা edit-এর তালিকা, যেগুলো শুধু ক্রমেই অর্থ রাখে): একটা message সরিয়ে রাখলে বাকিগুলোর ক্রম বদলে যায়।
  • সাম্প্রতিক একটা আচরণ জেনে রাখো: যে standard queue-এর maxReceiveCount ৩-এর বেশি, সেখানে delete না হয়ে ৩ বার receive হওয়া message-কে queue-এর পেছনে সরিয়ে দেওয়া হয়, যাতে queue-এর বয়সের metric সে আর আটকে না রাখে।

#Retention-এর ফাঁদ

Standard queue-তে message DLQ-তে সরালে তার enqueue time নতুন করে শুরু হয় না: বয়স গোনা চলতে থাকে আসল send-এর সময় থেকে। Source queue যদি message ৪ দিন রাখে, message সেখানে ৩ দিন অপেক্ষা করে, আর DLQ-ও ৪ দিন রাখে, তাহলে DLQ থেকে message-টা উধাও হয়ে যায় মাত্র আর ১ দিন পরে। FIFO queue-তে সরানোর সময় timestamp নতুন করে শুরু হয়। যেটাই হোক, ভালো অভ্যাস একটাই: DLQ-এর retention source-এর চেয়ে লম্বা রাখো, সাধারণত সর্বোচ্চ ১৪ দিন।

#নজর রাখো, তারপর redrive

DLQ তখনই কাজে লাগে, যখন কেউ সেটা খেয়াল করে। DLQ-এর ApproximateNumberOfMessagesVisible-এর ওপর (০-এর বেশি হলে) একটা CloudWatch alarm বসাও, আর সেটা on-call team-এর কাছে পাঠাও (monitoring আছে chapter ১৯-এ)।

Bug ঠিক হলে DLQ redrive দিয়ে message-গুলো ফেরত পাঠাও: console-এর “Start DLQ redrive” button, বা StartMessageMoveTask API। এটা message-গুলোকে তাদের source queue-তে (বা একই type-এর অন্য কোনো queue-তে) সরায়, সবচেয়ে পুরোনোটা আগে, চাইলে সেকেন্ডে সর্বোচ্চ ৫০০ message-এর একটা বাঁধা গতিতে, যাতে worker-রা ভেসে না যায়। Redrive করা message আসে নতুন message হিসেবে: নতুন message ID, নতুন receive count, আর নতুন retention period। একটা redrive task সর্বোচ্চ ৩৬ ঘণ্টা চলে, আর redrive-এর সময় SQS কোনো message বাছাই বা বদল করে না।

#SQS-এর বাইরেও dead-letter queue

যেখানেই retry ফুরিয়ে যেতে পারে, সেখানেই ধারণাটা একই (মালিকের note, 18.others):

  • SNS প্রতিটা subscription-এর সাথে একটা DLQ জুড়তে পারে, যেটা একটা SQS queue, সেই message-গুলোর জন্য যেগুলো retry শেষেও পৌঁছানো যায়নি (লেসন ১৩.৪)।
  • Asynchronous-ভাবে চালানো Lambda ব্যর্থ event একটা DLQ-তে (SQS বা SNS) পাঠাতে পারে, যদিও এখন on-failure destination-ই বেশি পছন্দের (লেসন ১৫.১)। Lambda যখন SQS থেকে পড়ে, তখন DLQ বসানো হয় SQS queue-এর ওপর, function-এর ওপর না।
  • Elastic Beanstalk-এর worker environment ঠিক এই ব্যবস্থাই ব্যবহার করে, নিজের max retries সহ (লেসন ৯.৪)।

একটা poison message আর dead-letter queue

একটা খারাপ order (দাম লেখা আছে text হিসেবে) invoice code-কে প্রতিবারই exception দেয়। DLQ না থাকলে এটা অনন্তকাল ঘুরতে থাকে; থাকলে কয়েকবার চেষ্টার পর সরিয়ে রাখা হয়, আর bug ঠিক হলে আবার ফেরত পাঠানো যায়।

পরিস্থিতি

একই message বারবার fail করে।

Order app
orders queuesource queue
Worker
Invoice DB
order #৪৬০০
ধাপ 1 / 4
1

Order #৪৬০০ আসে দাম ৫০০-এর বদলে “five hundred” লেখা নিয়ে। Queue-তে message-এর ভেতরটা কেউ যাচাই করে না; SQS শুধু জমা রাখে।

নিজেকে যাচাই করোপ্রশ্ন

একটা standard source queue message ৪ দিন রাখে, আর তার DLQ-ও ৪ দিন রাখে। একটা message DLQ-তে সরার আগে source queue-তে ২ দিন কাটায়। DLQ থেকে কখন এটা মুছে যাবে?

#Delay queue আর message timer

কখনো কখনো একটা message সাথে সাথে process হওয়া উচিত না। Delay queue প্রতিটা নতুন message পাঠানোর পরে একটা নির্দিষ্ট সময় লুকিয়ে রাখে:

  • Queue attribute DelaySeconds, ০ থেকে ৯০০ সেকেন্ড (১৫ মিনিট), default ০।
  • Delay চলাকালীন message receive করা যায় না; তারপর সে অন্য যেকোনো message-এর মতোই visible।
  • Delay একটা message লুকায় প্রথম receive-এর আগে; visibility timeout লুকায় একটা receive-এর পরে।
  • Standard queue-এর delay বদলালে আগে থেকে অপেক্ষায় থাকা message-এর কিছু হয় না; FIFO queue-তে বদলটা আগের message-এর ওপরও খাটে।

Message timer delay বসায় একটা message-এ (SendMessage-এ DelaySeconds), আর queue-এর delay-কে ছাপিয়ে যায়। Timer চলে শুধু standard queue-তে; FIFO queue শুধু queue-পর্যায়ের delay মানে। ১৫ মিনিটের পরের কোনো কিছুর জন্য বরং EventBridge Scheduler দিয়ে পাঠানোটা schedule করো (chapter ১৯)।

#বড় message: Extended Client Library

একটা SQS message সর্বোচ্চ ১ MiB (১০,৪৮,৫৭৬ byte) বইতে পারে। আগস্ট ২০২৫-এর আগে সীমা ছিল ২৫৬ KiB, মালিকের note, slide আর অনেক practice প্রশ্নে এখনো সেটাই লেখা। এর চেয়ে বড় কিছু অন্য পথে পাঠাতে হয়।

Amazon SQS Extended Client Library (Java আর Python-এর জন্য) কাজটা তোমার হয়ে করে দেয়:

  1. Producer-এর client payload-টা একটা S3 bucket-এ object হিসেবে লেখে।
  2. তারপর একটা ছোট SQS message পাঠায়, যেটায় শুধু একটা pointer: bucket আর object key।
  3. একই library ব্যবহার করা consumer pointer-টা receive করে, আর নিজে থেকেই S3 থেকে payload নিয়ে আসে।
  4. Message delete হলে library চাইলে S3 object-টাও মুছে দিতে পারে; নইলে একটা S3 lifecycle rule দাও (লেসন ১০.৫), যাতে মালিকহারা payload জমে না যায়।

এভাবে ২ GB পর্যন্ত payload চলে। Library ছাড়া একই কৌশল, “data রাখো S3-এ, তার key পাঠাও queue দিয়ে”, S3 event notification আগে থেকেই ঠিক এভাবে কাজ করে (লেসন ১০.৭)।

দেরি করানো আর বড় payload

Producer-এর দুটো কৌশল: পাঠানোর পরে একটা message কিছুক্ষণ লুকিয়ে রাখা, আর SQS-এর জন্য খুব বড় payload S3-এ রেখে পাঠানো।

পরিস্থিতি

প্রতিটা নতুন message প্রথমে queue-এর delay-টুকু লুকিয়ে থাকে।

Producer
SQS queue৬০ s delayed
Consumer
trip #১২ শেষ
ধাপ 1 / 3
1

একটা ride-hailing app trip শেষ হলে “rating চাওয়ার message পাঠাও” queue-তে দেয়, কিন্তু চায় যাত্রী এটা পাক এক মিনিট পরে। Queue-তে DelaySeconds = ৬০ (অনুমতি আছে ০ থেকে ৯০০, মানে ১৫ মিনিট পর্যন্ত)।

#আরও কাজের API call আর সীমা

Call বা settingকী করেজেনে রাখার মতো
SendMessageBatch, DeleteMessageBatch, ChangeMessageVisibilityBatchএক call-এ ১০টা পর্যন্ত messageকম request, কম খরচ; প্রতিটা entry আলাদাভাবে সফল বা ব্যর্থ হতে পারে
MaxNumberOfMessages সহ ReceiveMessageএক receive-এ ১ থেকে ১০টা messageপ্রত্যেক message-এর নিজের receipt handle আর timer
PurgeQueueQueue-এর সব message মুছে দেয়, in-flight গুলোওফেরানো যায় না; ৬০ সেকেন্ড পর্যন্ত লাগে; প্রতি queue-তে ৬০ সেকেন্ডে একবার
GetQueueAttributesSetting আর আনুমানিক সংখ্যা পড়ে (visible, in flight, delayed)সংখ্যাগুলো আনুমানিক, কখনো সঠিক মোট হিসেবে ধরো না
ListDeadLetterSourceQueuesকোন কোন source queue একটা DLQ ব্যবহার করে, তালিকা দেয়DLQ মোছা বা বদলানোর আগে কাজে লাগে
StartMessageMoveTask, ListMessageMoveTasks, CancelMessageMoveTaskDLQ redrive আর তার অবস্থাএকটা task সর্বোচ্চ ৩৬ ঘণ্টা চলে; প্রতি account-এ ১০০টা পর্যন্ত চালু task
In-flight সীমাপ্রতি standard queue-তে প্রায় ১,২০,০০০ in-flight message (FIFO-তে ১,২০,০০০)Short polling তখন OverLimit error পায়; long polling শুধু নতুন কিছু দেয় না। সাধারণত মানে consumer delete করতে ভুলে যাচ্ছে
Message size আর দাম১ byte থেকে ১ MiB; প্রতি ৬৪ KB অংশ একটা request হিসেবে bill হয়মাসে ১০ লাখ request ফ্রি, তারপর প্রতি ১০ লাখে মোটামুটি $০.৪০ (standard) বা $০.৫০ (FIFO); খালি receive-ও গোনা হয়

#নতুন কিছু feature, পাশের নোট হিসেবে

  • ১ MiB message (আগস্ট ২০২৫), আগে ছিল ২৫৬ KiB। পরীক্ষায় এখনো পুরোনো সংখ্যা আসতে পারে।
  • API দিয়ে source-এ বা অন্য কোনো queue-তে DLQ redrive (২০২৩), FIFO queue-তেও।
  • Fair queue (২০২৫): standard queue-তে producer যদি প্রতিটা tenant-এর জন্য একটা MessageGroupId বসায় (standard queue-তে এটা শুধু tenant চেনায়, কোনো ক্রম মানায় না), তাহলে SQS একজন হইচই করা tenant-এর জমে থাকা message দিয়ে বাকি সব tenant-এর message আটকে যেতে দেয় না। Consumer-এ কিছুই বদলাতে হয় না। পরীক্ষার উত্তর হওয়ার মতো পুরোনো এখনো হয়নি, কিন্তু multi-tenant SaaS-এ কাজের।

#কী কী গোলমাল হয়

লক্ষণসম্ভাব্য কারণসমাধান
একই কাজ দুই বা তার বেশিবার হচ্ছে, অথচ কিছুই crash করেনিVisibility timeout কাজের সময়ের চেয়ে কমTimeout স্বাভাবিক কাজের সময়ের ওপরে তোলো; লম্বা কাজে ChangeMessageVisibility দিয়ে heartbeat
Processing “সফল” হলেও message চিরকাল ফিরে আসছেConsumer কখনো DeleteMessage call করে না, বা পুরোনো receipt handle দিয়ে delete করেসফল হলে delete করো, সর্বশেষ receive-এর handle দিয়ে
একটা worker crash করার পর একটা order কয়েক ঘণ্টা আটকে আছেVisibility timeout কয়েক ঘণ্টা রাখাকাজের সময়ের কাছাকাছি রাখো; বিশাল timeout-এর বদলে heartbeat
প্রায় সবসময় খালি থাকা queue-এর SQS bill বেশিটানা loop-এ short polling২০ সেকেন্ড wait সহ long polling
Message অপেক্ষায় থাকা সত্ত্বেও consumer মাঝে মাঝে খালি উত্তর পায়Short polling server-এর একটা অংশই দেখেLong polling
একটা message বারবার fail করে log ভরে ফেলছেPoison message, DLQ নেইDLQ আর যুক্তিসঙ্গত maxReceiveCount সহ redrive policy
অল্প সময়ের বিভ্রাটে সুস্থ message DLQ-তে চলে যাচ্ছেmaxReceiveCount খুব কম (যেমন ১)বাড়াও, যাতে dependency-র ছোট্ট বিকলতায় আবার চেষ্টা হয়
কেউ দেখার আগেই DLQ থেকে message উধাওDLQ-এর retention source-এর সমান বা কমDLQ-এর retention ১৪ দিন, সাথে visible message-এর ওপর alarm
“DLQ-কে FIFO হতে হবে” errorFIFO source queue একটা standard DLQ-কে দেখাচ্ছেএকটা .fifo DLQ বানাও
ভালো timeout থাকলেও মাঝে মাঝে payment বা email দুবার যাচ্ছেStandard queue-এর at-least-once deliveryIdempotent consumer, বা deduplication সহ FIFO
Receive-এ OverLimitপ্রায় ১,২০,০০০ message in flight: receive হয়েছে, delete হয়নিProcess হওয়া message delete করো; crash loop আছে কিনা দেখো
Send করলে খুব বড় হওয়ার errorPayload ১ MiB-এর বেশি (বা queue-এর কম করে রাখা MaximumMessageSize-এর বেশি)S3 সহ Extended Client Library, বা একটা reference পাঠাও

#System design-এ SQS-এর এই খুঁটিনাটি

  • Queue-এর পেছনে worker tier। কাজের সময়ের একটু ওপরে visibility timeout, ২০ সেকেন্ডের long polling, alarm সহ একটা DLQ, আর idempotent worker। এটাই চলতি রেসিপি, আর Elastic Beanstalk-এর worker environment ঠিক এটাই তোমার হয়ে সাজিয়ে দেয় (লেসন ৯.৪)।
  • Backlog দেখে scaling। এই setting-গুলোর সাথে লেসন ৬.৫ আর ১৩.১-এর Auto Scaling “backlog per instance” target মেলাও। যে worker message বেশিক্ষণ ধরে রাখে (কাজের সময়ের চেয়ে অনেক বড় timeout), সে আসল কাজের তুলনায় backlog-কে ছোট দেখায়।
  • Consumer হিসেবে Lambda। Lambda service তোমার হয়ে queue long-poll করে (একটা event source mapping), function সফল হলে message delete করে, আর partial batch response দিয়ে শুধু ব্যর্থগুলো ফেরত দেয়। Queue-এর visibility timeout function timeout-এর অন্তত ছয় গুণ রাখো, আর DLQ বসাও queue-তে (chapter ১৫)।
  • Scheduler ছাড়াই দেরিতে কাজ। Reminder, back-off সহ retry, “১০ মিনিটে টাকা না দিলে order বাতিল”, ১৫ মিনিটের মধ্যে → delay queue আর message timer; এর বেশি → EventBridge Scheduler।
  • বড় document আর media। Payload S3-এ, pointer SQS-এ, Extended Client Library দিয়ে, বা নিজেই S3 key পাঠিয়ে।
  • লম্বা workflow। যা ১২ ঘণ্টা ছাড়িয়ে যেতে পারে, বা যাতে অপেক্ষা আর শাখা সহ অনেক ধাপ, তার জায়গা Step Functions-এ, আর আলাদা আলাদা task-এর জন্য SQS।

#নিজে করে দেখো

নিচের queue URL-গুলো Singapore-এর (ap-southeast-1) উদাহরণ account 111122223333-এর।

# 1. A DLQ with 14-day retention, then a source queue with long polling, a 3-minute visibility
#    timeout and a redrive policy (maxReceiveCount 3)
aws sqs create-queue --queue-name orders-dlq-example \
  --attributes MessageRetentionPeriod=1209600
aws sqs create-queue --queue-name orders-example --attributes '{
  "VisibilityTimeout": "180",
  "ReceiveMessageWaitTimeSeconds": "20",
  "RedrivePolicy": "{\"deadLetterTargetArn\":\"arn:aws:sqs:ap-southeast-1:111122223333:orders-dlq-example\",\"maxReceiveCount\":\"3\"}"
}'
#   "QueueUrl": "https://sqs.ap-southeast-1.amazonaws.com/111122223333/orders-example"

Q=https://sqs.ap-southeast-1.amazonaws.com/111122223333/orders-example

# 2. Send an order, and one with a 60-second message timer
aws sqs send-message --queue-url $Q --message-body '{"order":4521,"amount":500}'
aws sqs send-message --queue-url $Q --message-body '{"order":4522}' --delay-seconds 60

# 3. Receive with long polling and ask for the receive count
aws sqs receive-message --queue-url $Q --wait-time-seconds 20 --max-number-of-messages 10 \
  --message-system-attribute-names ApproximateReceiveCount
#   "Messages": [{ "MessageId": "5fea7756-0ea4-451a-a703-a558b933e274",
#     "ReceiptHandle": "AQEBzbVv...fqNzFw==",
#     "Body": "{\"order\":4521,\"amount\":500}",
#     "Attributes": { "ApproximateReceiveCount": "1" } }]

# 4. Need more time? Extend this receive by 5 minutes (counted from now)
aws sqs change-message-visibility --queue-url $Q \
  --receipt-handle "AQEBzbVv...fqNzFw==" --visibility-timeout 300

# 5. Done: delete it with the latest receipt handle
aws sqs delete-message --queue-url $Q --receipt-handle "AQEBzbVv...fqNzFw=="

# 6. See what is waiting, in flight and delayed
aws sqs get-queue-attributes --queue-url $Q --attribute-names \
  ApproximateNumberOfMessages ApproximateNumberOfMessagesNotVisible ApproximateNumberOfMessagesDelayed
#   "ApproximateNumberOfMessages": "0", "ApproximateNumberOfMessagesNotVisible": "0",
#   "ApproximateNumberOfMessagesDelayed": "1"

# 7. Make a poison message: receive it 3 times without deleting, and let the 180 s pass each time
#    (or set the visibility timeout to 5 s for the test). On the next receive it is in the DLQ:
aws sqs get-queue-attributes --attribute-names ApproximateNumberOfMessages \
  --queue-url https://sqs.ap-southeast-1.amazonaws.com/111122223333/orders-dlq-example
#   "ApproximateNumberOfMessages": "1"

# 8. After the fix, redrive the DLQ back to the source queue at 10 messages per second
aws sqs start-message-move-task \
  --source-arn arn:aws:sqs:ap-southeast-1:111122223333:orders-dlq-example \
  --max-number-of-messages-per-second 10
#   "TaskHandle": "eyJ0YXNrSWQiOiJkYzE..."
aws sqs list-message-move-tasks \
  --source-arn arn:aws:sqs:ap-southeast-1:111122223333:orders-dlq-example
#   "Status": "COMPLETED", "ApproximateNumberOfMessagesMoved": 1

# 9. Clean up (purge cannot be undone)
aws sqs purge-queue --queue-url $Q
aws sqs delete-queue --queue-url $Q
aws sqs delete-queue --queue-url https://sqs.ap-southeast-1.amazonaws.com/111122223333/orders-dlq-example

৮ নম্বর ধাপে --destination-arn না দিলে message-গুলো যে queue থেকে এসেছিল, সেখানেই ফেরত যায়। Console-এ একই কাজগুলো পাবে queue-এর Send and receive messages page-এ (message poll করা, receive count দেখা, delete করা), আর DLQ-এর Start DLQ redrive button-এ।

#সারসংক্ষেপ

মূল কথাগুলো

  • একটা message-এর জীবন sent → (delayed) → visible → in flight → deleted। Receive করলে message কখনো মোছে না; মোছে শুধু সর্বশেষ receipt handle দিয়ে DeleteMessage। অন্য পথগুলো: timeout শেষ হলে আবার visible, বেশিবার receive হলে DLQ-তে, আর retention (default ৪ দিন, সর্বোচ্চ ১৪) ফুরালে মুছে যাওয়া।
  • Visibility timeout: receive হওয়া message-কে সব consumer-এর কাছ থেকে লুকিয়ে রাখে, যাতে একসাথে দুবার process না হয়। Default ৩০ s, ০ s থেকে ১২ ঘণ্টা। বসানো যায় queue-তে, প্রতি receive-এ, বা প্রতি message-এ ChangeMessageVisibility দিয়ে (call থেকে গোনা; প্রথম receive থেকে ১২ ঘণ্টার সীমা; ০ দিলে সাথে সাথে ছেড়ে দেয়)।
  • খুব ছোট → একই কাজ দুবার; খুব লম্বা → crash-এর পর ধীর retry। স্বাভাবিক কাজের সময়ের একটু ওপরে মান রাখো, আর লম্বা কাজে heartbeat। পরীক্ষা: ২ মিনিটের কাজ দুবার process হচ্ছে → visibility timeout বাড়াও / ChangeMessageVisibility।
  • Standard queue অন্তত একবার পৌঁছায়, তাই consumer-কে idempotent হতে হবে (idempotency key, process হওয়া ID-র table)। Exactly-once processing-এর জন্য FIFO (লেসন ১৩.৩)।
  • Short polling (wait ০, default) সাথে সাথে উত্তর দেয়, শুধু কিছু server দেখে: empty আর false empty response, বেশি খরচ। Long polling অপেক্ষা করে ১–২০ s (২০ ভালো), সব server দেখে, message আসামাত্র ফেরত দেয়। চালু করো ReceiveMessageWaitTimeSeconds (queue) বা WaitTimeSeconds (call, এটা জেতে) দিয়ে। পরীক্ষা: খরচ / empty response কমাও → long polling।
  • DLQ: একটা সাধারণ queue, যার নাম source-এর redrive policy-তে থাকে, maxReceiveCount (১–১,০০০) সহ। একই account, Region আর type (FIFO → FIFO DLQ)। তাকে লম্বা retention দাও (১৪ দিন), তার ওপর alarm বসাও, আর fix-এর পর redrive করে ফেরত পাঠাও (নতুন message হিসেবে)। পরীক্ষা: poison pill, অসীম loop, ব্যর্থতা খুঁজে দেখা → DLQ।
  • Delay queue প্রতিটা message-এর জন্য ০–১৫ মিনিট; message timer প্রতি message-এ (শুধু standard); ১৫ মিনিটের পরে → EventBridge Scheduler।
  • সর্বোচ্চ message ১ MiB (২০২৫-এর আগে ২৫৬ KiB)। এর বড় → Extended Client Library: payload S3-এ (২ GB পর্যন্ত), pointer SQS-এ।
  • কাজের call: ১০টার batch, PurgeQueue (সব message, ৬০ s পর্যন্ত, ফেরানো যায় না), প্রায় ১,২০,০০০ in-flight message, প্রতি ৬৪ KB অংশ একটা request হিসেবে bill।