নির্ভরযোগ্য নোটিফিকেশনের জন্য ট্রিগার বনাম ব্যাকগ্রাউন্ড ওয়ার্কার
কখন ট্রিগার আর কখন ব্যাকগ্রাউন্ড ওয়ার্কার নোটিফিকেশনের জন্য নিরাপদ—রিট্রাই, ট্রানজ্যাকশন সীমা, এবং ডুপ্লিকেট প্রতিরোধ সম্পর্কে ব্যবহারিক নির্দেশনা।

কেন বাস্তব অ্যাপে নোটিফিকেশন ডেলিভারি ভাঙে
নোটিফিকেশনগুলো মনে হয় সহজ: ইউজার কিছু করে, তারপর একটা ইমেইল বা SMS যায়। অধিকাংশ বাস্তব ব্যর্থতা টাইমিং ও ডুপ্লিকেশনের কারণে ঘটে। মেসেজগুলো সত্যিই ডেটা সংরক্ষিত হওয়ার আগেই পাঠানো হয়, অথবা আংশিক ব্যর্থতার পরে দুইবার পাঠানো হয়।
“নোটিফিকেশন” অনেক কিছু হতে পারে: ইমেইল রসিদ, SMS এক-বারের কোড, পুশ এলার্ট, ইন-অ্যাপ মেসেজ, Slack বা Telegram পিং, অথবা অন্য সিস্টেমে ওয়েবহুক। সমস্যাটা সবখানেই একটাই: আপনি ডাটাবেস পরিবর্তনকে আপনার অ্যাপের বাইরে কিছুয়ের সঙ্গে কোঅর্ডিনেট করতে চাচ্ছেন।
বাইরের দুনিয়া অগোছালো। প্রোভাইডার ধীর হতে পারে, টাইমআউট দেবে, অথবা রিকোয়েস্ট গ্রহণ করতে পারে অথচ আপনার অ্যাপ সাফল্য রেসপন্স না পেতে পারে। আপনার নিজের অ্যাপ রিকোয়েস্ট মাঝপথে ক্র্যাশ বা রিস্টার্ট করতে পারে। এমনকি “সফল” দেখানো সেন্ডগুলোও পুনরায় চালানো হতে পারে ইনফ্রাস্ট্রাকচার রিট্রাই, ওয়ার্কার রিস্টার্ট বা ইউজার আবার বোতাম চেপে দেওয়ার কারণে।
নোটিফিকেশন ডেলিভারি ভাঙার সাধারণ কারণগুলোর মধ্যে নেটওয়ার্ক টাইমআউট, প্রোভাইডার আউটেজ বা রেট লিমিট, ভুল মুহূর্তে অ্যাপ রিস্টার্ট, ইউনিক গার্ড ছাড়া একই সেন্ড লজিক রি‑রান করা, এবং যেখানে ডাটাবেস লেখানো ও এক্সটার্নাল সেন্ড একসাথে মিশে আছে এমন ডিজাইন থাকে।
যখন মানুষ “নির্ভরযোগ্য নোটিফিকেশন” বলে, তারা সাধারণত দুই সম্পর্কে একটিকে বোঝায়:
- ঠিক একবার ডেলিভারি করা, অথবা
- কমপক্ষে ডুপ্লিকেট কখনো না হওয়া (ডুপ্লিকেট অনেকসময় ডিলে থেকে খারাপ)।
দ্রুততা ও পুরোপুরি নিরাপত্তা একসাথে পাওয়া কঠিন, তাই আপনি দ্রুততা, নিরাপত্তা এবং জটিলতার মধ্যে ট্রেড‑অফ বেছে নেন।
এজন্য ট্রিগার বনাম ব্যাকগ্রাউন্ড ওয়ার্কার পছন্দ কেবল আর্কিটেকচার বিতর্ক নয়। এটি সিদ্ধান্ত নেয় কবে সেন্ড করতে দেওয়া হবে, ব্যর্থতা কিভাবে রিট্রাই হবে, এবং কিভাবে আপনি কিছু ভুল হলে ডুপ্লিকেট ইমেইল বা SMS প্রতিরোধ করবেন।
ট্রিগার ও ব্যাকগ্রাউন্ড ওয়ার্কার: কী বোঝায়
ট্রিগার বনাম ব্যাকগ্রাউন্ড ওয়ার্কার তুলনা করলে মূলত আপনি যাচাই করছেন নোটিফিকেশন লজিক কোথায় চলছে এবং সেটি ওই অ্যাকশনটির সাথে কতটুকু আঁটসাঁটভাবে যুক্ত।
ট্রিগার মানে “X ঘটলে এখন করুন।” অনেক অ্যাপেই এর মানে হল ইউজার অ্যাকশনের পরে একই ওয়েব রিকোয়েস্টের ভিতর ইমেইল বা SMS পাঠানো। ট্রিগার ডাটাবেস স্তরেও থাকতে পারে: রো ইনসার্ট বা আপডেট হলে ডাটাবেস ট্রিগার অটোম্যাটিক চালায়। দুটোই তাৎক্ষণিক মনে হয়, তবে তারা যে জিনিসটি চালিয়েছে তার টাইমিং ও সীমাবদ্ধতা গ্রহণ করে।
ব্যাকগ্রাউন্ড ওয়ার্কার মানে “শিগগিরই কিন্তু ফরগ্রাউন্ডে নয়।” এটি একটি আলাদাভাবে চলা প্রসেস যা কিউ থেকে জব নিয়ে নেয় এবং সম্পন্ন করার চেষ্টা করে। আপনার প্রধান অ্যাপ কী করা হবে তা রেকর্ড করে দ্রুত ফিরে আসে, আর ওয়ার্কার ধীর, ত্রুটিপূর্ণ অংশগুলো—ইমেইল বা SMS প্রোভাইডারে কল করা—হ্যান্ডল করে।
“জব” হলো ওয়ার্কার যে কাজটি প্রক্রিয়া করে তার ইউনিট। সাধারণত এতে থাকে কোনকে নোটিফাই করতে, কোন টেমপ্লেট, ডেটা কী, বর্তমান স্ট্যাটাস (queued, processing, sent, failed), কতবার চেষ্টা হয়েছে, এবং কখনও কখনও শিডিউলড সময়।
একটি সাধারণ নোটিফিকেশন ফ্লো দেখতে এমন: আপনি মেসেজের বিবরণ প্রস্তুত করেন, জব এনকিউ করেন, প্রোভাইডারের মাধ্যমে পাঠান, ফলাফল রেকর্ড করেন, তারপর সিদ্ধান্ত নেন যে রিট্রাই করতে হবে, বন্ধ করতে হবে, না কাউকে সতর্ক করতে হবে।
ট্রানজ্যাকশন সীমানা: কখন সত্যিই পাঠানো নিরাপদ
ট্রানজ্যাকশন সীমানা হল “আমরা এটা সেভ করার চেষ্টা করেছি” এবং “এটি সত্যিই সেভ হয়েছে” এর মাঝে রেখাটি। ডাটাবেস কমিট হওয়া পর্যন্ত পরিবর্তন রোলব্যাক হতে পারে। এটা গুরুত্বপূর্ণ কারণ নোটিফিকেশনকে পিছনে ফেরা কঠিন।
কমিটের আগে ইমেইল বা SMS পাঠালে আপনি কারো কাছে এমন বার্তা পাঠাতে পারেন যা আসলে হয়নি। গ্রাহক পেতে পারেন “আপনার পাসওয়ার্ড পরিবর্তিত হয়েছে” বা “আপনার অর্ডার কনফার্ম হয়েছে”—তারপর লেখাটি একটি কনস্ট্রেন্ট এরর বা টাইমআউটের কারণে ব্যর্থ হয়ে যেতে পারে। এখনই ব্যবহারকারী বিভ্রান্ত, এবং সাপোর্টকে বিশ্লেষণ করতে হবে।
ডাটাবেস ট্রিগার থেকে পাঠানো আকর্ষণীয় মনে হতে পারে কারণ এটি ডেটা চেঞ্জের সঙ্গে অটোম্যাটিক চালায়। কিন্তু কিপার হল ট্রিগার একই ট্রানজ্যাকশনের ভিতরে চলে। যদি ট্রানজ্যাকশন রোলব্যাক করে, আপনি হয়তো ইতোমধ্যে ইমেইল বা SMS প্রোভাইডারকে কল করে ফেলেছেন।
ডাটাবেস ট্রিগারগুলো পর্যবেক্ষণ, টেস্ট ও নিরাপদভাবে রিট্রাই করা কঠিন হয়ে পড়ে। আর যখন তারা ধীর এক্সটার্নাল কল করে, তখন তারা লকগুলো দীর্ঘক্ষণ ধরে রাখতে পারে ও ডাটাবেস ইস্যুগুলো নির্ণয় করা কঠিন করে তোলে।
একটি নিরাপদ পন্থা হল আউটবক্স ধারণা: নোটিফাই করার ইচ্ছেটা ডাটাবেসে রেকর্ড করুন, কমিট করুন, তারপর পাঠান।
আপনি ব্যবসায়িক পরিবর্তন করেন এবং একই ট্রানজ্যাকশনে একটি আউটবক্স সারি ইনসার্ট করেন যা মেসেজ বর্ণনা করে (কে, কি, কোন চ্যানেল, প্লাস একটি ইউনিক কী)। কমিটের পরে একটি ব্যাকগ্রাউন্ড ওয়ার্কার পেন্ডিং আউটবক্স সারি পড়ে, মেসেজ পাঠায়, তারপর সেগুলোকে sent হিসেবে মার্ক করে।
কম প্রভাবশালী, তথ্যবহুল মেসেজগুলোর জন্য তাত্ক্ষণিক পাঠানো ঠিক থাকতে পারে—যেমন “আমরা আপনার অনুরোধ প্রক্রিয়াকরণ করছি।” কিন্তু যে কিছু চূড়ান্ত রাজ্যের সাথে মিলে যেতে হবে, তার জন্য কমিটের পরে অপেক্ষা করুন।
রিট্রাই ও ত্রুটি হ্যান্ডলিং: কোথায় কোন পদ্ধতি জিতে
রিট্রাই সাধারণত সিদ্ধান্তের নির্ধারক।
ট্রিগার: দ্রুত, কিন্তু ব্যর্থতায় ভঙ্গুর
অধিকাংশ ট্রিগার‑ভিত্তিক ডিজাইনে ভাল রিট্রাই কাহিনী থাকে না।
যদি ট্রিগার ইমেইল/SMS প্রোভাইডারকে কল করে এবং কল ব্যর্থ হয়, আপনি সাধারণত দুই খারাপ বিকল্পের মধ্যে পড়েন:
- ট্রানজ্যাকশন ব্যর্থ করা (এবং মূল আপডেট ব্লক করা), অথবা
- এররকে নীরবে ছুড়ে ফেলা (এবং নোটিফিকেশন হারানো)।
যখন নির্ভরযোগ্যতা জরুরি, তখন দুটোই গ্রহণযোগ্য নয়।
ট্রিগারের ভিতরে লুপ বা বিলম্ব চেষ্টা করলে ট্রানজ্যাকশনগুলি আরো দীর্ঘক্ষণ খোলা রেখে দিতে পারে, লক সময় বাড়ায় এবং ডাটাবেস ধীর করে। আর যদি ডাটাবেস বা অ্যাপ সেন্ট্রে ক্র্যাশ করে, আপনি প্রায়শই বলতে পারবেন না প্রোভাইডার রিকোয়েস্ট পেয়েছে কি না।
ব্যাকগ্রাউন্ড ওয়ার্কার: রিট্রাইয়ের জন্য ডিজাইনকৃত
ওয়ার্কার পাঠানোকে আলাদা কাজ হিসেবে বিবেচনা করে যার নিজস্ব স্টেট থাকে। এতে কেবল প্রয়োজনমত রিট্রাই করা সহজ হয়।
বাস্তবিকভাবে, সাধারণ নিয়ম হল অস্থায়ী ব্যর্থতাগুলো (টাইমআউট, নেটওয়ার্ক সমস্যা, সার্ভার এরর, রেট লিমিট) রিট্রাই করা; স্থায়ী সমস্যাগুলো (অবৈধ ফোন নম্বর, ভুল ইমেইল, আনসাবস্ক্রাইব করা) সাধারণত রিট্রাই করা হয় না। “অজানা” এররগুলোর জন্য চেষ্টা সীমা রাখুন এবং স্টেট দৃশ্যমান রাখুন।
ব্যাকঅফই রিট্রাইগুলোকে আরো খারাপ না করে রাখে। একটি ছোট অপেক্ষা দিয়ে শুরু করুন, তারপর প্রতিবার বাড়ান (উদাহরণ: 10s, 30s, 2m, 10m), এবং নির্দিষ্ট সংখ্যক প্রচেষ্টার পরে বন্ধ করুন।
ডিপ্লয় ও রিস্টার্ট সহ্য করার জন্য, প্রতিটি জবের সাথে রিট্রাই স্টেট সংরক্ষণ করুন: অ্যাটেম্পট কাউন্ট, পরবর্তী অ্যাটেম্পটের সময়, লাস্ট এরর (সংক্ষিপ্ত ও পড়ার যোগ্য), লাস্ট অ্যাটেম্পট টাইম, এবং স্পষ্ট স্ট্যাটাস যেমন pending, sending, sent, failed।
যদি আপনার অ্যাপ মাঝপথে ক্র্যাশ করে, একটি ওয়ার্কার আটকে থাকা জবগুলো পুনরায় চেক করতে পারে (উদাহরণ: স্ট্যাটাস = sending কিন্তু পুরোনো টাইমস্ট্যাম্প) এবং সেগুলো নিরাপদভাবে আবার রিট্রাই করতে পারে। এটাই যেখানে অইডেমপোটেন্সি অপরিহার্য যাতে রিট্রাই ডাবল-সেন্ড না করে।
ডুপ্লিকেট ইমেইল ও SMS প্রতিরোধ: অইডেমপোটেন্সি
অইডেমপোটেন্সি মানে একই “সেন্ড নোটিফিকেশন” অ্যাকশন অনেকবার চালালেও ব্যবহারকারী তা একবারই পায়।
ক্লাসিক ডুপ্লিকেট কেসটি হল টাইমআউট: আপনার অ্যাপ প্রোভাইডারকে কল করে, রিকোয়েস্ট টাইমআউট করে, এবং আপনার কোড পুনরায় চেষ্টা করে। প্রথম অনুরোধটি হয়ত কার্যকরভাবে সফল হয়েছিল, তাই রিট্রাই ডুপ্লিকেট তৈরি করে।
প্রায়োগিক সমাধান হলো প্রতিটি মেসেজকে একটি স্থিতিশীল কী দেওয়া এবং সেই কী-কে একমাত্র সত্য হিসেবে ব্যবহার করা। ভালো কীগুলো ব্যাখ্যা করে মেসেজটি কী বোঝায়, কখন পাঠানো চেষ্টা করা হয়েছে তা নয়।
সাধারণ পদ্ধতিগুলির মধ্যে রয়েছে:
- আপনি যখন সিদ্ধান্ত নেন “এই মেসেজ থাকা উচিত” তখন একটি সৃষ্ট
notification_idজেনারেট করা, বা - একটি ব্যবসায়িক-উৎপন্ন কী যেমন
order_id + template + recipient(শুধু যদি তা সত্যিই অনন্যতা নির্ধারণ করে)।
তারপর একটি সেন্ড লেজার রাখুন (প্রায়শই আউটবক্স টেবিলই) এবং সব রিট্রাইতে তা দেখে পাঠান। স্টেটগুলো সিম্পল ও দৃশ্যমান রাখুন: created (নির্ধারিত), queued (প্রস্তুত), sent (নিশ্চিতভাবে পাঠানো), failed (নিশ্চিত ব্যর্থ), canceled (প্রয়োজন নেই)। সমালোচনামূলক নিয়ম হল একই অইডেমপোটেন্সি কী-র জন্য একটিই সক্রিয় রেকর্ড অনুমোদন করা।
প্রোভাইডার-সাইড অইডেমপোটেন্সি সাহায্য করতে পারে যেখানে তা সাপোর্ট করে, কিন্তু তা আপনার নিজস্ব লেজার বদলায় না। আপনাকে এখনও আপনার রিট্রাই, ডিপ্লয়মেন্ট এবং ওয়ার্কার রিস্টার্ট হ্যান্ডল করতে হবে।
“অজানা” আউটকামকেও প্রথম-শ্রেণির হিসাবে বিবেচনা করুন। যদি অনুরোধ টাইমআউট করে, অবিলম্বে আবার পাঠাবেন না। সেটিকে কনফার্মেশন-পেন্ডিং হিসেবে চিহ্নিত করুন এবং সম্ভব হলে প্রোভাইডারের ডেলিভারি স্ট্যাটাস চেক করে নিরাপদভাবে রিট্রাই করুন। যদি নিশ্চিত করা না যায়, দেরি করুন এবং এভর্ট করে দ্বিগুণ পাঠানোর বদলে সতর্ক করুন।
একটি নিরাপদ ডিফল্ট প্যাটার্ন: আউটবক্স + ব্যাকগ্রাউন্ড ওয়ার্কার (ধাপে ধাপে)
নিরাপদ ডিফল্ট চাইলে আউটবক্স প্যাটার্ন প্লাস ওয়ার্কার খুব কার্যকর। এটি আপনার ব্যবসায়িক ট্রানজ্যাকশনের বাইরে সেন্ডিং রাখে, সাথে সাথে নিশ্চিত করে যে নোটিফাই করার ইচ্ছেটা সংরক্ষিত আছে।
ফ্লো
“নোটিফিকেশন পাঠান” কে একটি ডাটা হিসেবে সংরক্ষণ করুন, অ্যাকশন হিসেবে নয়।
আপনি ব্যবসায়িক পরিবর্তন সংরক্ষণ করবেন (উদাহরণ: অর্ডার স্ট্যাটাস আপডেট)। একই ডাটাবেস ট্রানজ্যাকশনে আপনি একটি আউটবক্স রেকর্ড ইনসার্ট করবেন — রিসিপিয়েন্ট, চ্যানেল (email/SMS), টেমপ্লেট, পে-লোড, এবং একটি অইডেমপোটেন্সি কী। ট্রানজ্যাকশন কমিট হলে হার্ড করে শুধু তখনই কিছু পাঠাতে দিন।
একটি ব্যাকগ্রাউন্ড ওয়ার্কার নিয়মিত পেন্ডিং আউটবক্স সারি তুলে নেয়, পাঠায়, এবং ফলাফল রেকর্ড করে।
দু’টি ওয়ার্কার একই সারি ধরবে না তা নিশ্চিত করতে একটি সিম্পল ক্লেইমিং স্টেপ যোগ করুন। এটি processing-এ স্ট্যাটাস পরিবর্তন করা বা একটি লকড টাইমস্ট্যাম্প হতে পারে।
ডুপ্লিকেট ব্লক করা ও ব্যর্থতা হ্যান্ডলিং
ডুপ্লিকেট প্রায়ই তখন ঘটে যখন সেন্ড সফল হয়েছে কিন্তু আপনার অ্যাপ “sent” মার্ক করার আগে ক্র্যাশ করে। এটাকে সমাধান করতে “mark sent” লেখাটি পুনরাবৃত্তি করার জন্য সেফ করুন।
ইউনিকনেস নিয়ম ব্যবহার করুন (উদাহরণ: idempotency কী এবং চ্যানেলে ইউনিক কনস্ট্রেন্ট)। রিট্রাই স্পষ্ট নিয়ম অনুসরণ করে: সীমিত চেষ্টা, বাড়তে থাকা ডিলে, এবং শুধুমাত্র রিট্রাইযোগ্য ত্রুটির জন্য। শেষ রিট্রাইয়ের পরে জবটিকে ডেড-লেটার স্টেটে পাঠান (যেমন failed_permanent) যাতে কেউ পর্যালোচনা করে ম্যানুয়ালি পুনরায় প্রসেস করতে পারে।
মনিটরিং সহজ রাখতে পারেন: pending, processing, sent, retrying, failed_permanent-এর গণনা, এবং সবচেয়ে পুরোনো pending টাইমস্ট্যাম্প।
কনক্রিট উদাহরণ: যখন একটি অর্ডার “Packed” থেকে “Shipped” হয়, আপনি অর্ডার রো আপডেট করেন এবং একটি আউটবক্স রো তৈরি করেন idempotency কী order-4815-shipped সহ। যদিও ওয়ার্কার পাঠানোর মাঝপথে ক্র্যাশ করে, পুনরায় চালালে ডুপ্লিকেট হবে না কারণ “sent” লেখাটিকে ঐ ইউনিক কী দ্বারা সুরক্ষিত রাখা আছে।
কখন ব্যাকগ্রাউন্ড ওয়ার্কারই ভালো
ডাটাবেস ট্রিগার ডেটা চেঞ্জের মুহূর্তে প্রতিক্রিয়া জানাতে ভাল। কিন্তু যদি কাজটি হয় “বাহিরের নোইজি বাস্তব দুনিয়ায় নির্ভরযোগ্যভাবে নোটিফাই করা”, তাহলে ব্যাকগ্রাউন্ড ওয়ার্কার সাধারণত আপনাকে বেশি নিয়ন্ত্রণ দেয়।
ওয়ার্কার উপযুক্ত যখন আপনি টাইম-ভিত্তিক সেন্ড চান (রিমাইন্ডার, ডাইজেস্ট), উচ্চ ভলিউম ও রেট লিমিট মোকাবেলা, প্রোভাইডার ভ্যারিয়েবিলিটি সহ্য করার ক্ষমতা (429 লিমিট, ধীর রেসপন্স), মাল্টি-স্টেপ ওয়ার্কফ্লো (পাঠান, ডেলিভারি অপেক্ষা, তারপর ফলোআপ), বা এমন ক্রস-সিস্টেম ইভেন্ট যা রিকনসিলিয়েশন প্রয়োজন।
সহজ উদাহরণ: আপনি কারো চার্জ নেবেন, তারপর SMS রিসিপ্ট পাঠাবেন, তারপর ইমেইলে ইনভয়েস পাঠাবেন। যদি SMS একটি গেটওয়ে ইস্যুর কারণে ব্যর্থ হয়, আপনি চান যে অর্ডারটি পেইড অবস্থায় থেকে পরবর্তীতে নিরাপদে রিট্রাই করা যাবে। ট্রিগারে এই লজিক রাখলে “ডাটা সঠিক” এবং “তৎক্ষণাৎ থার্ড-পার্টি উপলব্ধ” মিশে যেতে পারে—যা ঝুঁকিপূর্ণ।
ব্যাকগ্রাউন্ড ওয়ার্কার অপারেশনাল কন্ট্রোল ও সহজ করে। আপনি একটি ইনসিডেন্টের সময় কিউ পজ করতে পারেন, ব্যর্থতা ইনস্পেক্ট করতে পারেন, এবং ডিলে সহ রিট্রাই চালাতে পারেন।
ডুপ্লিকেট বা মিস হওয়া মেসেজের সাধারণ ভুলগুলো
সবচেয়ে দ্রুত অবিশ্বাস্য নোটিফিকেশন পেতে চাওয়া হচ্ছে যেখানে সারাদিন “ঠিক সেখানে পাঠান” এবং রিট্রাইগুলো আশা করা হয় সব ঠিক করে দেবে। ট্রিগার হন বা ওয়ার্কার—ব্যর্থতা ও স্টেট সম্পর্কে ছোটখাটো বিবরণই নির্ধারণ করে ব্যবহারকারী একবার, দুইবার, বা একটাও না পাবে।
একটি সাধারণ ফাঁদ হল ডাটাবেস ট্রিগার থেকে পাঠানো এবং ধরে নেওয়া এটি ব্যর্থ হতেই পারবে না। ট্রিগার ট্রানজ্যাকশনের ভিতরে চলে, তাই কোনো ধীর প্রোভাইডার কল লেখাটাকে আটকে দিতে পারে, টাইমআউট সৃষ্টি করতে পারে, বা টেবিল লক দীর্ঘায়িত করতে পারে। খারাপ দিকটি হলো যদি প্রথম কলটি সফল হয় তারপর ট্রানজ্যাকশন রোলব্যাক হয়, পরে রিস্টার্টে আপনি আবার পাঠালে ডুপ্লিকেট হবে।
ঘাটতি যা বারবার দেখা যায়:
- সবকিছু একইভাবে রিট্রাই করা, স্থায়ী ত্রুটিগুলোসহ (খারাপ ইমেইল, ব্লক করা নম্বর)।
- “queued” এবং “sent” আলাদা না করা, ফলে ক্র্যাশের পরে কী রিট্রাই করা নিরাপদ তা বোঝা যায় না।
- ডেডুপ কীগুলোর জন্য টাইমস্ট্যম্প ব্যবহার করা, ফলে রিট্রাই স্বাভাবিকভাবেই ইউনিকনেস উপেক্ষা করে।
- প্রোভাইডার কলগুলো ইউজার রিকোয়েস্ট পথেই করা (চেকআউট বা ফর্ম সাবমিট গেটওয়েটার সাপেক্ষে অপেক্ষা করা উচিত নয়)।
- প্রোভাইডার টাইমআউটকে “ডেলিভারহীন” হিসেবে ধরা, যখন অনেক ক্ষেত্রেই তা আসলে “অজানা”।
সহজ উদাহরণ: আপনি একটি SMS পাঠান, প্রোভাইডার টাইমআউট করে, এবং আপনি আবার চেষ্টা করেন। প্রথম অনুরোধটি যদি আসলে সফল হয়ে থাকে, ব্যবহারকারী দুই কোড পায়। সমাধান হলো একটি স্থির অইডেমপোটেন্সি কী (যেমন notification_id) রেকর্ড করা, পাঠানোর আগে মেসেজকে queued হিসেবে চিহ্নিত করা, এবং স্পষ্ট সফলতার পরে এটিকে sent হিসেবে মার্ক করা।
রিলিজের আগে দ্রুত চেকলিস্ট
অধিকাংশ নোটিফিকেশন বাগ সরঞ্জাম নয়—এগুলো টাইমিং, রিট্রাই, ও অনুপস্থিত রেকর্ড নিয়ে।
নিশ্চিত করুন আপনি শুধুমাত্র ডাটাবেস লেখাটি নিরাপদে কমিট হওয়ার পরে পাঠান। যদি আপনি লেখার সময় পাঠান এবং পরে সেটি রোলব্যাক করে, ব্যবহারকারী এমন কিছু সম্পর্কে বার্তা পাবে যা কখনই ঘটেনি।
পরবর্তী, প্রতিটি নোটিফিকেশনকে অনন্যভাবে শনাক্ত করুন। প্রতিটি মেসেজে একটি স্থির অইডেমপোটেন্সি কী দিন (উদাহরণ order_id + event_type + channel) এবং স্টোরেজে তা এনফোর্স করুন যাতে রিট্রাই দ্বিতীয় “নিউ” নোটিফিকেশন তৈরি করতে না পারে।
রিলিজের আগে এই বেসিকগুলো চেক করুন:
- পাঠানো কমিটের পরে হয়, লেখার সময় নয়।
- প্রতিটি নোটিফিকেশনের একটি ইউনিক অইডেমপোটেন্সি কী আছে এবং ডুপ্লিকেট অস্বীকার করা হয়।
- রিট্রাই সেফ: সিস্টেম একই জব আবার চালালেও সর্বোচ্চ একবারই পাঠাবে।
- প্রতিটি চেষ্টা রেকর্ড করা হয় (স্ট্যাটাস, last_error, টাইমস্ট্যাম্প)।
- প্রচেষ্টা লিমিট রয়েছে, এবং আটকে থাকা আইটেমগুলোর স্পষ্ট পর্যালোচনার জায়গা আছে।
রিস্টার্ট আচরণ অনুমান করে পরীক্ষা করুন। ওয়ার্কারকে মাঝপথে হত্যা করে পুনরায় চালান এবং যাচাই করুন কিছুই ডাবল-সেন্ড হয়নি। একই পরীক্ষা ডাটাবেস লোড থাকা অবস্থায়ও করুন।
একটি সহজ দৃশ্য যাচাই করার জন্য: একজন ব্যবহারকারী ফোন নম্বর পরিবর্তন করে, তারপর আপনি SMS ভেরিফিকেশন পাঠান। যদি SMS প্রোভাইডার টাইমআউট করে, আপনার অ্যাপ রিট্রাই করে। ভাল অইডেমপোটেন্সি কী ও চেষ্টা লগ থাকলে, আপনি একচেটিয়া পাঠান অথবা পরে নিরাপদে আবার চেষ্টা করবেন—কিন্তু স্প্যাম করবেন না।
উদাহরণ দৃশ্য: অর্ডার আপডেট ডাবল-সেন্ড ছাড়া
একটি স্টোর দুই ধরনের মেসেজ পাঠায়: (1) পেমেন্টের পরে অর্ডার কনফার্মেশন ইমেইল, এবং (2) প্যাকেজ আউট ফর ডেলিভারি ও ডেলিভারড হলে SMS আপডেট।
যখন আপনি খুব আগেই পাঠান (উদাহরণ ডাটাবেস ট্রিগারে), তখন এরকম ভুল হয়: পেমেন্ট ধাপ একটি orders রো লিখে, ট্রিগার গ্রাহককে ইমেইল করে, এবং পরে পেমেন্ট ক্যাপচার এক সেকেন্ড পরে ব্যর্থ হয়। এখন আপনার কাছে এমন একটি “Thanks for your order” ইমেইল আছে যা বাস্তবে কখনই স্টিক করে নি।
এবার বিপরীত সমস্যা ভাবুন: ডেলিভারি স্ট্যাটাস “Out for delivery” হয়, আপনি SMS প্রোভাইডারকে কল করেন, প্রোভাইডার টাইমআউট করে। আপনি জানেন না এটি পাঠানো হয়েছে কি না। আপনি যদি অবিলম্বে রিট্রাই করেন, দুটো SMS হওয়ার ঝুঁকি আছে। না করলে, আপনি হয়ত কিছুই পাঠাননি।
একটি নিরাপদ ফ্লো আউটবক্স রেকর্ড ও ব্যাকগ্রাউন্ড ওয়ার্কার ব্যবহার করে দেয়। অ্যাপ অর্ডার বা স্ট্যাটাস পরিবর্তন কমিট করে, একই ট্রানজ্যাকশনে একটি আউটবক্স রো লিখে—যেমন “send template X to user Y, channel SMS, idempotency key Z।” শুধু কমিটের পরে ওয়ার্কার মেসেজ ডেলিভার করবে।
সহজ টাইমলাইন এমন:
- পেমেন্ট সফল হয়, ট্রানজ্যাকশন কমিট হয়, কনফার্মেশন ইমেইলের জন্য আউটবক্স রো সংরক্ষিত হয়।
- ওয়ার্কার ইমেইল পাঠায় এবং প্রোভাইডার মেসেজ আইডি সহ আউটবক্সকে sent হিসেবে মার্ক করে।
- ডেলিভারি স্ট্যাটাস পরিবর্তিত হয়, ট্রানজ্যাকশন কমিট হয়, SMS আপডেটের আউটবক্স রো সংরক্ষিত হয়।
- প্রোভাইডার টাইমআউট করে, ওয়ার্কার আউটবক্সকে retryable হিসেবে চিহ্নিত করে এবং একই অইডেমপোটেন্সি কী ব্যবহার করে পরে আবার চেষ্টা করে।
রিট্রাইতে, আউটবক্স রো একক সত্যের উৎস হিসেবে কাজ করে। আপনি নতুন “সেন্ড” অনুরোধ তৈরি করছেন না—আপনি শুরু করাটাকে শেষ করছেন।
সাপোর্টের জন্য এটা আরো পরিষ্কার। তারা দেখতে পারে কোন মেসেজগুলো failed এ আটকা আছে, শেষ এরর (টাইমআউট, খারাপ ফোন নম্বর, ব্লক করা ইমেইল), কতবার চেষ্টা হয়েছে, এবং নিরাপদে রিট্রাই করা যাবে কি না।
পরবর্তী ধাপ: একটি প্যাটার্ন নির্বাচন করুন এবং পরিষ্কারভাবে বাস্তবায়ন করুন
একটি ডিফল্ট প্যাটার্ন বেছে নিন এবং তা লিখে রাখুন। অসঙ্গত আচরণ সাধারণত ট্রিগার ও ওয়ার্কার মিশিয়ে ব্যবহার করলে আসে।
ছোট থেকে শুরু করুন: একটি আউটবক্স টেবিল এবং একটি ওয়ার্কার লুপ বানান। প্রথম লক্ষ্য দ্রুততা নয়—ঠিকতা: আপনি যা পাঠাবেন তা স্টোর করুন, কমিটের পরে পাঠান, এবং প্রোভাইডার কনফার্ম করলে শুধুমাত্র তখনই sent মার্ক করুন।
সরল রোলআউট প্ল্যান:
- ইভেন্টগুলো নির্ধারণ করুন (
order_paid,ticket_assigned) ও কোন চ্যানেলগুলো ব্যবহার হবে। - একটি আউটবক্স টেবিল যোগ করুন: event_id, recipient, payload, status, attempts, next_retry_at, sent_at।
- একটি ওয়ার্কার বানান যা পেন্ডিং সারি পোল করে, পাঠায়, এবং এক জায়গায় স্ট্যাটাস আপডেট করে।
- প্রতিটি মেসেজের জন্য ইউনিক কী দিয়ে অইডেমপোটেন্সি যোগ করুন এবং “যদি ইতিমধ্যে পাঠানো থাকে তাহলে কিছু না করা” নীতি লাগান।
- ত্রুটিগুলোকে retryable (টাইমআউট, 5xx, রেট লিমিট) বনাম not retryable (খারাপ নম্বর, ব্লক ইমেইল) ভাগ করুন।
আপনি ভলিউম বাড়ানোর আগে বেসিক ভিজিবিলিটি যোগ করুন। pending কনট, ফেইলিউর রেট, এবং সবচেয়ে পুরোনো pending মেসেজের আয়ু ট্র্যাক করুন। যদি সবচেয়ে পুরোনো pending ক্রমশ বাড়ে, সম্ভবত ওয়ার্কার আটকে আছে, প্রোভাইডারে আউটেজ, বা লজিকে বাগ আছে।
আপনি যদি AppMaster (appmaster.io) এ নির্মাণ করে থাকেন, এই প্যাটার্নটি পরিষ্কারভাবে মানায়: Data Designer-এ আউটবক্স মডেল করুন, ব্যবসায়িক পরিবর্তন ও আউটবক্স সারি একই ট্রানজ্যাকশনে লিখুন, তারপর আলাদা ব্যাকগ্রাউন্ড প্রসেসে send-and-retry লজিক চালান। এই বিভাজনই নোটিফিকেশন ডেলিভারি নির্ভরযোগ্য রাখে এমনকি প্রোভাইডার বা ডিপ্লয়মেন্ট সমস্যা থাকলেও।
প্রশ্নোত্তর
ব্যাকগ্রাউন্ড ওয়ার্কার সাধারণত নিরাপদ ডিফল্ট কারণ পাঠানো ধীর এবং ত্রুটিপূর্ণ হতে পারে, আর ওয়ার্কারগুলো রিট্রাই এবং দৃশ্যমানতার জন্য ডিজাইন করা। ট্রিগার দ্রুত হতে পারে, কিন্তু এগুলো যে ট্রানজ্যাকশন বা রিকোয়েস্ট চালায় তার সঙ্গে শক্তভাবে জড়িত থাকে, ফলে ব্যর্থতা ও ডুপ্লিকেট হ্যান্ডল করা কষ্টসাধ্য হয়।
এটি ঝুঁকিপূর্ণ কারণ ডাটাবেস লেখাটি এখনও রোলব্যাক হতে পারে। ফলে আপনি এমন কিছু সম্পর্কে নোটিফাই করতে পারেন যা বাস্তবে কখনই কমিট হয়নি—এমনকি সেটি অপ্রত্যাহার্য ইমেইল বা SMS হলে সেটি ফেরানো সম্ভব নয়।
ডাটাবেস ট্রিগার একই ট্রানজ্যাকশনের ভিতরে চলে। যদি ট্রিগার একটি ইমেইল/SMS প্রোভাইডারকে কল করে এবং পরে ট্রানজ্যাকশন ব্যর্থ হয়, আপনি এমন বাস্তব বার্তা পাঠিয়েছেন যা ডাটাতে স্থায়ী হয়নি, অথবা ধীর এক্সটার্নাল কল ট্রানজ্যাকশনকে আটকিয়ে দিতে পারে।
আউটবক্স প্যাটার্নটি সহজভাবে বলে: পাঠানোর ইচ্ছেটা ডাটাবেসে একটি সারি হিসেবে সংরক্ষণ করা। এটি ব্যবসায়িক পরিবর্তনের সঙ্গে একই ট্রানজ্যাকশনে লিখুন। কমিট হওয়ার পরে একটি ওয়ার্কার পেন্ডিং আউটবক্স সারি পড়ে, বার্তাটি পাঠায় এবং সেটিকে sent হিসেবে মার্ক করে—এতে টাইমিং ও রিট্রাই নিরাপদ হয়।
প্রোভাইডার টাইমআউট হলে বাস্তব ফলাফল প্রায়শই “অজানা” থাকে, সরাসরি “অসফল” হিসেবে চিহ্নিত করা ঠিক নয়। ভালো সিস্টেমটি চেষ্টা রেকর্ড করে, বিলম্ব করে এবং একই মেসেজ পরিচয় ব্যবহার করে নিরাপদভাবে রিট্রাই করে, বদলে অবিলম্বে আবার পাঠিয়ে ডুপ্লিকেট হওয়ার ঝুঁকি নেয় না।
অইডেমপোটেন্সি ব্যবহার করুন: প্রতিটি নোটিফিকেশনকে এমন একটি স্থির কী দিন যা বার্তার অর্থ প্রকাশ করে (কখন পাঠানোর চেষ্টা করা হয়েছে তা নয়)। ওই কী একটি লেজারে (অফবক্স টেবিল) রাখুন এবং প্রতিটি কী-র জন্য একটাই সক্রিয় রেকর্ড সুনিশ্চিত করুন—এতে রিট্রাই একই বার্তাটাকেই শেষ করবে, নতুন বার্তা তৈরি করবে না।
টাইমআউট, সার্ভার 5xx, বা রেট লিমিটের মতো অস্থায়ী ত্রুটি রিট্রাই করা উচিত (উপযুক্ত অপেক্ষা দিয়ে)। খারাপ ঠিকানা, ব্লক করা নম্বর, অথবা হার্ড বাউন্সের মতো স্থায়ী ত্রুটি রিট্রাই করবেন না—এগুলোকে ব্যর্থ হিসেবে চিহ্নিত করে দৃশ্যমান করা উচিত যাতে কেউ ডাটা ঠিক করে।
ব্যাকগ্রাউন্ড ওয়ার্কার স্টক বা স্টাক থাকা জবগুলো স্ক্যান করতে পারে (উদাহরণ: sending স্ট্যাটাস কিন্তু পুরোনো টাইমস্ট্যাম্প) এবং সেগুলোকে আবার retryable করে পেছনে পাঠাতে পারে। এটি কেবল তখনই নিরাপদ যখন প্রতিটি জবে অ্যাটেম্পট-কাউন্ট, টাইমস্ট্যাম্প, লাস্ট-এরর ইত্যাদি রেকর্ড করা থাকে এবং অইডেমপোটেন্সি ডাবল-সেন্ড প্রতিরোধ করে।
এটার মানে আপনি ‘সেফলি রিট্রাই করা যাবে’ কি না বলতে পারবেন। স্পষ্ট স্ট্যাটাস রাখুন: pending, processing, sent, failed—অ্যাটেম্পট কাউন্ট এবং লাস্ট এররও রাখুন। এতে সাপোর্ট ও ডিবাগ করা সহজ হয় এবং সিস্টেম অনুমানের উপর নির্ভর না করে পুনরুদ্ধার করতে পারে।
Data Designer-এ আউটবক্স টেবিল মডেল করুন, ব্যবসায়িক আপডেট ও আউটবক্স সারি একই ট্রানজ্যাকশনে লিখুন, তারপর আলাদা ব্যাকগ্রাউন্ড প্রসেসে send-and-retry লজিক চালান। প্রতিটি মেসেজে একটি অইডেমপোটেন্সি কী রাখুন এবং অ্যাটেম্পট রেকর্ড করুন, যাতে ডিপ্লয়, রিট্রাই ও ওয়ার্কার রিস্টার্ট ডুপ্লিকেট তৈরি না করে।


