ক্রন ঝামেলা ছাড়া ব্যাকগ্রাউন্ড জব শিডিউলিং: প্যাটার্ন
ওয়ার্কফ্লো ও একটি jobs table ব্যবহার করে রিমাইন্ডার, দৈনিক সামারি এবং ক্লিনআপ নির্ভরযোগ্যভাবে শিডিউল করার প্যাটার্ন জানুন—ক্রন-সংক্রান্ত ঝামেলা কমিয়ে।

কেন প্রথমে cron সহজ লাগে, তারপর সমস্যা দেখা দেয়
প্রথম দিন cron চমৎকার: একটি লাইন লেখো, সময় বেছে নাও, আর ভুলে যাও। এক সার্ভার এবং এক টাস্কের জন্য এটা প্রায়ই কাজ করে।
কিন্তু যখন আপনি রিয়েল প্রোডাক্ট আচরণের জন্য শিডিউলিং নির্ভর করেন—রিমাইন্ডার, দৈনিক সামারি, ক্লিনআপ বা সিঙ্ক জব—তখন সমস্যা দেখা দেয়। বেশিরভাগ “মিসড রান” গল্প cron-এর ব্যর্থতা নয়। সেটা চারপাশের সবকিছু: সার্ভার রিবুট, ডিপ্লয় যা crontab ওভাররাইট করেছে, দীর্ঘ রান করা একটি জব, বা ক্লক/টাইমজোন মিল না হওয়া। এবং একাধিক অ্যাপ ইনস্ট্যান্স চালালে বিপরীত সমস্যা হতে পারে: ডুপ্লিকেট, কারণ দুই মেশিনই একই টাস্ক চালানোর চেষ্টা করে।
টেস্ট করাও দুর্বল একটি জায়গা। একটি cron লাইন আপনাকে "আগামীকাল সকাল ৯টায় কী হবে" পুনরাবৃত্ত টেস্টে সহজ উপায় দেয় না। ফলে শিডিউলিং হাতিল কাজ, প্রোডাকশন সারপ্রাইজ এবং লগ খোঁজায় পরিণত হয়।
কোন পদ্ধতি বেছে নেওয়ার আগে, স্পষ্ট হন আপনি কী শিডিউল করছেন। বেশিরভাগ ব্যাকগ্রাউন্ড কাজ কয়েকটি বালতির মধ্যে পড়ে:
- রিমাইন্ডার (নির্দিষ্ট সময়ে একবার পাঠাতে হবে)
- দৈনিক সামারি (ডেটা অ্যাগ্রিগেট করে পাঠানো)
- ক্লিনআপ টাস্ক (মুছা, আর্কাইভ, মেয়াদ শেষ করা)
- পিরিয়ডিক সিঙ্ক (আপডেট টানানো বা পাঠানো)
কখনো কখনো আপনি শিডিউলিং পুরোপুরি এড়িয়ে যেতে পারেন। কিছু যদি ইভেন্ট ঘটার সঙ্গে সঙ্গেই হওয়া যায় (যেমন ইউজার সাইন আপ, পেমেন্ট সফল), তাহলে ইভেন্ট-চালিত কাজ সাধারণত সময়-চালিত কাজের থেকে সহজ এবং বেশি নির্ভরযোগ্য।
যখন আপনাকে সময়-চাহিদা দরকার, নির্ভরযোগ্যতা মূলত দর্শন ও নিয়ন্ত্রণের উপর নির্ভর করে। আপনি এমন একটা জায়গা চান যেখানে কি চলবে, কখন চলবে, এবং zuletzt কী ব্যর্থ হয়েছে তা রেকর্ড থাকে, সাথে নিরাপদভাবে রিট্রাই করার উপায় যাতে ডুপ্লিকেট না হয়।
মৌলিক প্যাটার্ন: scheduler, jobs table, worker
cron-সংক্রান্ত ঝামেলা এড়াতে সহজ উপায় হলো দায়িত্বগুলো ভাগ করা:
- একটি scheduler সিদ্ধান্ত নেয় কী চলবে এবং কখন।
- একটি worker কাজটি করে।
এই ভূমিকা আলাদা রাখলে দুইভাবে সুবিধা হয়। টাইমিং বদলাতে ব্যবসায়িক লজিক স্পর্শ করতে হয় না, এবং ব্যবসায়িক লজিক বদলাতে শেডিউল ভাঙে না।
একটি jobs table সত্যের উৎস হয়ে ওঠে। সার্ভারের প্রক্রিয়ার মধ্যে বা crontab লাইনের ভিতরে স্টেট লুকানোর বদলে, প্রতিটি কাজ একটি সারি: কী করতে হবে, কার জন্য, কখন চালাতে হবে, এবং শেষবার কী হয়েছে। যখন কিছু ভুল হয়, আপনি এটা পরীক্ষা করতে পারেন, পুনরায় চালাতে পারেন, বা বাতিল করতে পারেন অনুমান না করেই।
একটি টিপিকাল ফ্লো দেখতে এমন হবে:
- scheduler কার্জ্যযোগ্য জবগুলো স্ক্যান করে (উদাহরণস্বরূপ,
run_at <= nowএবংstatus = queued)। - এটি একটি জব ক্লেইম করে যাতে কেবল একটাই worker তা নেয়।
- worker জবের বিস্তারিত পড়ে এবং কাজ সম্পন্ন করে।
- worker একই সারিতে ফলাফল রেকর্ড করে।
কী ধারণাটা হলো কাজকে ম্যাজিক না করে পুনরায় চালানো যোগ্য করা। যদি worker মাঝপথে ক্র্যাশ করে, জব সারিটি এখনও আপনাকে বলে কি হয়েছে এবং পরবর্তী পদক্ষেপ কী।
এমন একটি jobs table ডিজাইন করা যা কাজে লাগে
একটি jobs table দ্রুত দুইটি প্রশ্নের উত্তর দিতে পারা উচিত: পরবর্তী কি চালাতে হবে, এবং শেষবার কী হয়েছে।
পরিচয়, সময় এবং অগ্রগতির জন্য ছোট কিছু ফিল্ড দিয়ে শুরু করুন:
- id, type: একটি ইউনিক আইডি এবং
send_reminderবাdaily_summaryমত ছোট টাইপ। - payload: worker-কে দরকারি ভ্যালিডেটেড JSON (উদাহরণ:
user_id, পুরো user অবজেক্ট নয়)। - run_at: কখন কাজ চালানোর উপযোগী হবে।
- status:
queued,running,succeeded,failed,canceled। - attempts: প্রতিটি ট্রাইতে বাড়বে।
তারপর কয়েকটি অপারেশনাল কলাম যোগ করুন যা concurrency নিরাপদ করে এবং ইনসিডেন্ট হ্যান্ডল করা সহজ করে। locked_at, locked_by, এবং locked_until একটি worker-কে জব ক্লেইম করতে দেয় যাতে ডুপ্লিকেট না হয়। last_error হওয়া উচিত একটি সংক্ষিপ্ত বার্তা (ঐচ্ছিকভাবে একটি এরর কোড), পুরো স্ট্যাকট্রেস নয় যা সারি ফুলিয়ে দেয়।
সবশেষে, সাপোর্ট ও রিপোর্টিং-এ সাহায্য করার জন্য টাইমস্ট্যাম্প রাখুন: created_at, updated_at, এবং finished_at। এগুলো আপনাকে দ্রুত উত্তর দিতে দেয়: “আজ কয়টি রিমাইন্ডার ব্যর্থ হলো?”—লগ খুঁটিয়ে না দেখে।
ইন্ডেক্স গুরুত্বপূর্ণ কারণ সিস্টেম প্রায়ই প্রশ্ন করে “পরবর্তী কী?” সাধারণত উপকার করে এমন দুটি ইন্ডেক্স:
(status, run_at)— দ্রুত প্রাপ্য জব ফেচ করার জন্য(type, status)— কোনও জব পরিবারের অবস্থা পরিদর্শন বা পজ করার জন্য
payload-এ, ছোট ও ফোকাস করা JSON পছন্দ করুন এবং জব ইনসার্টের আগে ভ্যালিডেট করুন। আইডেন্টিফায়ার ও প্যারামিটার সংরক্ষণ করুন, ব্যবসায়িক ডেটার স্ন্যাপশট নয়। payload-এর আকারকে একটি API কনট্রাক্ট হিসেবে বিবেচনা করুন যাতে পুরনো queued জবগুলো আপনার অ্যাপ পরিবর্তনের পরে ও ঠিকভাবে চলতে পারে।
জব লাইফসাইকল: স্ট্যাটাস, লকিং, এবং আইডেম্পোটেন্সি
প্রত্যেক জব একটি ছোট, পূর্বানুমেয় লাইফসাইকেল মেনে চললে জব রানার নির্ভরযোগ্য থাকে। এই লাইফসাইকেলই আপনার সেফটি নেট যখন দুই worker একসাথে শুরু করে, সার্ভার রিস্টার্ট হয় মাঝপথে, বা রিট্রাই করতে হবে ডুপ্লিকেট ছাড়াই।
একটি সাধারণ স্টেট মেশিন প্রায়ই যথেষ্ট:
- queued:
run_at-এর পরে চালানোর জন্য প্রস্তুত - running: worker দ্বারা ক্লেইম করা হয়েছে
- succeeded: শেষ হয়েছে ও পুনরায় চালানো উচিত নয়
- failed: ত্রুটির সঙ্গে শেষ হয়েছে এবং মনিটরিং দরকার
- canceled: ইচ্ছাকৃতভাবে থামানো হয়েছে (উদাহরণ: ইউজার অপ্ট-আউট করেছে)
ডবল কাজ ছাড়া জব ক্লেইম করা
ডুপ্লিকেট এড়াতে, জব ক্লেইম অ্যাটমিক হওয়া দরকার। সাধারণ পদ্ধতি হলো সময়সীমা সহ লক (লিজ): worker একটি আপডেটে status=running সেট করে এবং locked_by ও locked_until লেখে এটি ক্লেইম করে। worker ক্র্যাশ করলে লক এক্সপায়ার করে এবং অন্য worker তা পুনরায় ক্লেইম করতে পারে।
একটি বাস্তবসম্মত ক্লেইমিং নিয়ম:
- কেবল সেই queued জব ক্লেইম করুন যাদের
run_at <= now - একসাথে
status,locked_by, এবংlocked_untilসেট করুন একই আপডেটে locked_until < nowহলে রিক্লেইম করুন- লিজটা সংক্ষিপ্ত রাখুন এবং কাজ দীর্ঘ হলে এক্সটেন্ড করুন
আইডেম্পোটেন্সি (অভ্যাস যা আপনাকে বাঁচায়)
আইডেম্পোটেন্সি মানে: একই জব দুইবার চললেও ফলাফল সঠিক থাকে।
সহজতম টুল হলো একটি ইউনিক কী। উদাহরণস্বরূপ, দৈনিক সামারির জন্য আপনি দিনে একবার প্রতিটি ইউজারের জন্য একটি কী বাধ্যতামূলক করতে পারেন: summary:user123:2026-01-25। যদি ডুপ্লিকেট ইনসার্ট ঘটে, এটি একই জব নির্দেশ করবে নতুন একটি জব তৈরি করার চেয়ে।
সফলতা মার্ক করুন ঠিক তখন যখন সাইড-ইফেক্ট সত্যিই সম্পন্ন হয়েছে (ইমেইল পাঠানো, রেকর্ড আপডেট করা)। রিট্রাই করলে, রিট্রাই পথ যেন দ্বিতীয় ইমেইল বা ডুপ্লিকেট লিখে না ফেলে।
নাটক ছাড়াই রিট্রাই ও ব্যর্থতা হ্যান্ডলিং
রিট্রাই হল সেই অংশ যেখানে জব সিস্টেম নির্ভরযোগ্য হয়ে ওঠে বা শব্দ জমে যায়। লক্ষ্য সহজ: যেখানে ত্রুটি সাময়িক হওয়ার সম্ভবনা বেশি সেখানে রিট্রাই করুন, যেখানে নয় সেখানে বন্ধ করুন।
একটি ডিফল্ট রিট্রাই পলিসিতে সাধারণত থাকে:
- সর্বোচ্চ চেষ্টা (উদাহরণ: মোট 5 বার)
- বিলম্ব কৌশল (ফিক্সড ডিলে বা এক্সপোনেনশিয়াল ব্যাকঅফ)
- থামানোর শর্ত (যেমন “অবৈধ ইনপুট” টাইপ এরর হলে রিট্রাই করবেন না)
- জিটার (রিট্রাই স্পাইক এড়াতে ছোট র্যান্ডম অফসেট)
নতুন কোনো স্ট্যাটাস আবিষ্কার করার বদলে, প্রায়ই queued পুনঃব্যবহার করা যায়: run_at-কে পরবর্তী চেষ্টা সময়ে সেট করুন এবং জবটিকে আবার কিউতে রাখুন। এতে স্টেট মেশিন ছোট থাকে।
যখন একটি জব আংশিক অগ্রগতি করতে পারে, সেটাকে স্বাভাবিক হিসেবে করুন। একটি checkpoint সংরক্ষণ করুন যাতে রিট্রাই নিরাপদে চলতে পারে—অথবা job payload-এ (যেমন last_processed_id) অথবা একটি সম্পর্কিত টেবিলে।
উদাহরণ: একটি দৈনিক সামারি জব ৫০০ জন ইউজারের জন্য মেসেজ জেনারেট করে। যদি এটি ইউজার ৩২০-এ ব্যর্থ হয়, শেষ সফল ইউজার আইডি সংরক্ষণ করুন এবং ৩২১ থেকে পুনরায় শুরু করুন। যদি আপনি প্রতি-ইউজার summary_sent রেকর্ডও সংরক্ষণ করেন, একটি rerun সহজেই ইতিমধ্যে সম্পন্ন ইউজারগুলো স্কিপ করতে পারে।
এমন লগিং যা সত্যিই সাহায্য করে
দুই-তিন মিনিটে ডিবাগ করার জন্য যথেষ্ট লগ রাখুন:
- job id, type, এবং attempt নম্বর
- প্রধান ইনপুট (user/team id, ডেটা রেঞ্জ)
- সময় (started_at, finished_at, next run time)
- সংক্ষিপ্ত এরর সমারি (স্ট্যাকট্রেস থাকলে আলাদাভাবে সরবরাহ করুন)
- সাইড-ইফেক্ট কাউন্ট (ইমেইল পাঠানো, রো আপডেট)
ধাপে ধাপে: একটি সাধারণ scheduler লুপ তৈরি করা
একটি scheduler লুপ হলো একটি ছোট প্রক্রিয়া যা নির্দিষ্ট রিদমে জেগে উঠে, প্রাপ্য কাজ খুঁজে পায়, এবং তা হ্যান্ডঅফ করে। লক্ষ্য হলো বিরক্তিকর নির্ভরযোগ্যতা, নিখুঁত টাইমিং নয়। অনেক অ্যাপের জন্য "প্রতি মিনিট জেগে ওঠা" যথেষ্ট।
ওয়ার্ক-টাইপ এবং ডাটাবেস লোডের উপর ভিত্তি করে আপনার ওয়েক-আপ ফ্রিকোয়েন্সি বেছে নিন। যদি রিমাইন্ডার নিকট-রিয়েল-টাইম হওয়া জরুরি হয়, প্রতি ৩০–৬০ সেকেন্ডে চালান। যদি দৈনিক সামারি একটু ড্রিফট করতে পারে, প্রতি ৫ মিনিট চালালেও চলবে এবং কম ব্যয় হবে।
একটি সাধারণ লুপ:
- জেগে ওঠুন এবং কারেন্ট টাইম নিন (UTC ব্যবহার করুন)।
status = 'queued'এবংrun_at <= nowযেখানে প্রাপ্য জব সিলেক্ট করুন।- নিরাপদে জব ক্লেইম করুন যাতে কেবল এক worker নেয়।
- প্রতিটি ক্লেইম করা জব worker-কে হস্তান্তর করুন।
- পরের টিকে পর্যন্ত ঘুমান।
ক্লেইম স্টেপই যেখানে অনেক সিস্টেম ভেঙে। আপনি চাইবেন একটি জব running হিসাবে মার্ক করা (এবং locked_by, locked_until সংরক্ষণ করা) একই ট্রানজ্যাকশনে যা তা সিলেক্ট করে। অনেক ডাটাবেস “skip locked” রিড সাপোর্ট করে যাতে একাধিক scheduler একসাথে চললেও তারা পরস্পরের উপর চাপ না ফেলে।
-- concept example
BEGIN;
SELECT id FROM jobs
WHERE status='queued' AND run_at <= NOW()
ORDER BY run_at
LIMIT 100
FOR UPDATE SKIP LOCKED;
UPDATE jobs
SET status='running', locked_until=NOW() + INTERVAL '5 minutes'
WHERE id IN (...);
COMMIT;
ব্যাচ সাইজ ছোট রাখুন (প্রায় 50 থেকে 200)। বড় ব্যাচ ডাটাবেসকে ধীর করতে পারে এবং ক্র্যাশ হলে ব্যথা বাড়ায়।
যদি scheduler মাঝপথে ক্র্যাশ করে, লিজ আপনাকে সেভ করে। running-এর মধ্যে আটকে থাকা জবগুলো locked_until-এর পরে আবার প্রাপ্য হবে। আপনার worker-কে আইডেম্পোটেন্ট রাখা দরকার যাতে রিক্লেইম হওয়া জব ডুপ্লিকেট ইমেইল বা ডাবল চার্জ তৈরি না করে।
রিমাইন্ডার, দৈনিক সামারি, এবং ক্লিনআপের প্যাটার্ন
বেশিরভাগ দল একই তিন ধরনের ব্যাকগ্রাউন্ড কাজ তৈরি করে: সময়মত পাঠানো মেসেজ, শিডিউলভিত্তিক রিপোর্ট, এবং স্টোরেজ/পারফরম্যান্স ভালো রাখার ক্লিনআপ। একই jobs table ও worker লুপ সবগুলো পরিচালনা করতে পারে।
রিমাইন্ডার
রিমাইন্ডারের জন্য, জব সারিতে পাঠানোর জন্য প্রয়োজনীয় সবকিছু রাখুন: কার জন্য, কোন চ্যানেল (ইমেইল, SMS, Telegram, ইন-অ্যাপ), কোন টেমপ্লেট, এবং ঠিক কখন পাঠাতে হবে। worker-কে জব চালাতে হলে আশেপাশ থেকে অতিরিক্ত কিছু খুঁজতে না হওয়া উচিত।
বহু রিমাইন্ডার একসাথে প্রাপ্য হলে রেট-লিমিটিং যোগ করুন। প্রতি মিনিটে প্রতি চ্যানেলে মেসেজ সীমাবদ্ধ করে অতিরিক্ত জবগুলো পরের রান পর্যন্ত অপেক্ষা করাতে দিন।
দৈনিক সামারি
দৈনিক সামারি ব্যর্থ হয় যখন সময় উইন্ডো অমসৃণ থাকে। একটি স্থায়ী কাটঅফ টাইম বেছে নিন (উদাহরণ: ব্যবহারকারীর লোকাল টাইমে 08:00) এবং উইন্ডো স্পষ্টভাবে সংজ্ঞায়িত করুন (উদাহরণ: “গতকাল 08:00 থেকে আজ 08:00”)। রিরান করলে একই ফলাফল পেতে কাটঅফ ও ব্যবহারকারীর টাইমজোন জবের সাথে সংরক্ষণ করুন।
প্রতিটি সামারি ছোট রাখুন। যদি হাজারো রেকর্ড প্রসেস করতে হয়, টুকরো করে বিভক্ত করুন (প্রতিটি টিম, অ্যাকাউন্ট বা আইডি রেঞ্জ অনুযায়ী) এবং ফলো-আপ জব এঞ্জয় করুন।
ক্লিনআপ টাস্ক
ক্লিনআপ নিরাপদ হয় যখন আপনি “ডিলিট” আলাদা রাখেন “আর্কাইভ”-থেকে। স্থির করুন কি স্থায়ীভাবে মুছবেন (অস্থায়ী টোকেন, মেয়াদোত্তীর্ণ সেশন) এবং কি আর্কাইভ করবেন (অডিট লগ, ইনভয়েস)। ক্লিনআপগুলো পূর্বানুমেয় ব্যাচে চালান যাতে দীর্ঘ লক বা হঠাৎ লোড স্পাইক না দেখা দেয়।
সময় ও টাইমজোন: বাগের গোপন উৎস
অনেক ব্যর্থতা সময় বাগ থেকে আসে: একটি রিমাইন্ডার এক ঘণ্টা আগে যায়, একটি দৈনিক সামারি রবিবার স্কিপ করে, বা ক্লিনআপ দুবার চলে।
একটি ভালো ডিফল্ট হলো শিডিউল টাইমস্ট্যাম্প UTC-তে সংরক্ষণ করা এবং ব্যবহারকারীর টাইমজোন আলাদা করে রাখা। আপনার run_at একটি UTC মুহূর্ত হওয়া উচিত। যখন ইউজার বলে “আমার টাইমে সকাল 9:00”, তখন শিডিউল করার সময় সেটা UTC-তে রূপান্তর করুন।
ডেলাইট সেভিং টাইম হল যেখানে সাদামাটা সেটআপ ভেঙে যায়। “প্রতিদিন সকাল 9:00” মানে না “প্রতি 24 ঘন্টা”। DST-এ সকাল 9:00 ভিন্ন UTC-তে মানচিত্রিত হয়, এবং কিছু লোকাল সময় উপস্থিতই থাকে না (spring forward) বা দুইবার ঘটে (fall back)। নিরাপদ পদ্ধতি হলো প্রতিবার পরবর্তী লোকাল কেরেন্স হিসাব করা, তারপর তা UTC-তে রূপান্তর করা।
দৈনিক সামারির জন্য, “একটা দিন” কী বোঝায় তা কোড লেখার আগে নির্ধারণ করুন। ক্যালেন্ডার দিন (ব্যবহারকারীর টাইমজোনে মধ্যরাত থেকে মধ্যরাত) মানব প্রত্যাশার সাথে মেলে। "গত 24 ঘন্টা" সহজ হলেও ধীরে ধীরে ড্রিফট করে এবং ব্যবহারকারীকে অবাক করতে পারে।
দেরি করে আসা ডেটা অনিবার্য: একটি ইভেন্ট রিট্রাই-এর পরে আসে, অথবা একটি নোট মধ্যরাতের কয়েক মিনিট পরে যোগ করা হয়। সিদ্ধান্ত নিন দেরিতে আসা ইভেন্টগুলো "গতকাল"-এর মধ্যে পড়বে (একটি গ্রেস পিরিয়ড দিয়ে) না "আজ"-এর এবং সেই নিয়ম কনসিস্টেন্ট রাখুন।
একটি বাস্তবসম্মত বাফার মিস এড়াতে পারে:
- 2 থেকে 5 মিনিট আগের পর্যন্ত প্রাপ্য জবও স্ক্যান করুন
- জবকে আইডেম্পোটেন্ট রাখুন যাতে রিরান নিরাপদ হয়
- কভার করা সময়রেঞ্জ payload-এ সংরক্ষণ করুন যাতে সামারি কনসিস্টেন্ট থাকে
মিসড বা ডুপ্লিকেট রান ঘটায় এমন সাধারণ ভুল
অধিকাংশ ব্যথা কয়েকটি পূর্বানুমেয় অনুমানের ফলে আসে।
সবচেয়ে বড়টি হলো “ঠিক একবার” এক্সিকিউশন আশা করা। বাস্তব সিস্টেমে worker রিস্টার্ট হয়, নেটওয়ার্ক কল টাইমআউট হয়, এবং লক হারিয়ে যেতে পারে। সাধারণত আপনি “at least once” ডেলিভারি পান, যার মানে ডুপ্লিকেট স্বাভাবিক এবং আপনার কোডকে তা সহনীয় করতে হবে।
আরেকটি ভুল হলো এফেক্টগুলো প্রথমে করা (ইমেইল পাঠানো, কার্ড চার্জ করা) बिना ডেডুপে চেক। একটি সাধারণ গার্ড অনেক সময় সমস্যা সমাধান করে: একটি sent_at টাইমস্ট্যাম্প, (user_id, reminder_type, date) মত ইউনিক কী, বা একটি ডেডুপে টোকেন।
দৃশ্যমানতা-সংক্রান্ত ফাঁকও বড় ব্যাপার। যদি আপনি বলতে না পারেন “কি আটকে আছে, কখন থেকে, এবং কেন”, আপনি অনুমানে চলে যাবেন। ন্যূনতম ডেটা যা কাছাকাছি রাখা উচিত: status, attempt count, next scheduled time, last error, এবং worker id।
সবচেয়ে ঘন ঘন দেখা ভুলগুলো:
- জবগুলোকে ঠিক একবার চালানো হবে বলে ডিজাইন করা, পরে ডুপ্লিকেটে বিস্মিত হওয়া
- সাইড-ইফেক্ট লেখার সময় ডেডুপে পরীক্ষা না করা
- একটি বিশাল জব লেখা যা সবকিছু করার চেষ্টা করে এবং মাঝপথে টাইমআউট হয়
- অনির্দিষ্টকালের জন্য রিট্রাই করা
- কিউ দৃশ্যমানতা এড়িয়ে চলা (ব্যাকলগ, ব্যর্থতা, দীর্ঘ-চলমান আইটেম সম্পর্কে পরিষ্কার দৃষ্টি না থাকা)
একটি স্পষ্ট উদাহরণ: একটি দৈনিক সামারি জব ৫০,০০০ ইউজার লুপ করে এবং ২০,০০০-এ টাইমআউট হয়। রিট্রাই করলে এটি আবার শুরু হয় এবং প্রথম ২০,০০০ ইউজারকে পুনরায় সামারি পাঠায় যদি আপনি ইউজার-স্তরে সম্পন্নতা ট্র্যাক না করেন বা প্রতিইউজার জবে ভাগ না করেন।
নির্ভরযোগ্য জব সিস্টেমের দ্রুত চেকলিস্ট
একটি জব রানার “শেষ” তখনই যখন আপনি রাত ২টায়ও এটাকে বিশ্বাস করতে পারেন।
নিশ্চিত করুন আপনার কাছে আছে:
- কিউ দৃশ্যমানতা: queued বনাম running বনাম failed-এর গোনা, এবং সবচেয়ে পুরোনো queued জব।
- ডিফল্টভাবে আইডেম্পোটেন্সি: প্রতিটি জব দুইবার চলতে পারে ধরে নিন; ইউনিক কী বা “ইতিমধ্যেই প্রসেসড” মার্কার ব্যবহার করুন।
- প্রতি জব টাইপ রিট্রাই পলিসি: রিট্রাই, ব্যাকঅফ, এবং স্পষ্ট থামার শর্ত।
- সামঞ্জস্যপূর্ণ সময় সঞ্চয়:
run_atUTC-তে রাখুন; কনভার্ট শুধুমাত্র ইনপুট ও ডিসপ্লের সময়ে করুন। - রিকভারেবল লক: ক্র্যাশে জবগুলো অনন্তকাল চালু থাকবে না এমন লিজ।
এছাড়া ব্যাচ সাইজ (একবারে কতগুলো জব ক্লেইম করবেন) এবং ওয়ার্কার কনকারেন্সি (একসাথে কতগুলো চলবে) সীমাবদ্ধ করে দিন। সীমা না থাকলে একটি স্পাইক ডাটাবেসকে ওভারলোড করতে পারে বা অন্য কাজগুলোকে ক্ষুধায় ফেলতে পারে।
বাস্তবসম্মত উদাহরণ: একটি ছোট দলের রিমাইন্ডার ও সামারি
একটি ছোট SaaS টুলে ৩০টি কাস্টমার অ্যাকাউন্ট আছে। প্রতিটি অ্যাকাউন্ট দুইটি জিনিস চায়: ওপেন টাস্কগুলোর জন্য সকাল ৯টায় রিমাইন্ডার এবং সন্ধ্যা ৬টায় দৈনিক সামারি যা আজ কী পরিবর্তন হলো তা বলে। তারা সাপ্তাহিক ক্লিনআপও চায় যাতে ডাটাবেস পুরোনো লগ ও মেয়াদোত্তীর্ণ টোকেন দিয়ে ভরে না ওঠে।
তারা jobs table এবং একটি worker ব্যবহার করে যা প্রাপ্য জব পোল করে। নতুন কাস্টমার সাইন আপ করলে ব্যাকএন্ড প্রথম রিমাইন্ডার ও সামারির রানগুলো শিডিউল করে ব্যবহারকারীর টাইমজোন অনুযায়ী।
জবগুলো কয়েক সাধারণ মুহূর্তে তৈরি হয়: সাইনআপে (রিকারিং শিডিউল তৈরি), নির্দিষ্ট ইভেন্টে (ওয়ান-অফ নোটিফিকেশন এনকিউ করা), শেডিউলার টিকে এ (আসন্ন রান ইনসার্ট করা), এবং রক্ষণাবেক্ষণ দিনে (ক্লিনআপ এনকিউ করা)।
একটি মঙ্গলবার, ইমেইল প্রোভাইডারে সাময়িক আউটেজ হয় ৮:৫৯ AM-এ। worker রিমাইন্ডার পাঠাতে চেষ্টা করে, টাইমআউট পায়, এবং ব্যাকঅফ ব্যবহার করে সেই জবগুলো রিস্কেজুল করে (run_at-কে ২ মিনিট, তারপর ১০, তারপর ৩০)। প্রতি রিমাইন্ডার জবের একটা আইডেম্পোটেন্সি কী আছে যেমন account_id + date + job_type, তাই প্রোভাইডার মাঝপথে পুনরুদ্ধার করলে রিট্রাই ডুপ্লিকেট তৈরি করে না।
ক্লিনআপ সাপ্তাহিকভাবে ছোট ব্যাচে চলে, তাই তা অন্য কাজ ব্লক করে না। মিলিয়ন রো একবারে ডিলিট করার পরিবর্তে, প্রতি রান Nটি রো মুছে এবং নিজেই পুনরায় শিডিউল করে যতদিন কাজ শেষ না হয়।
যখন কোনো কাস্টমার অভিযোগ করে “আমি আমার সামারি পাইনি”, টিম সেই অ্যাকাউন্ট আর দিনের জন্য jobs table চেক করে: জব স্ট্যাটাস, অ্যাটেম্পট কাউন্ট, কারেন্ট লক ফিল্ড, এবং প্রোভাইডার থেকে ফিরে আসা শেষ এরর। এতে “এটি পাঠানোর কথা ছিল” থেকে পরিণত হয় “এখানে ঠিক কী ঘটেছে”।
পরবর্তী ধাপ: বাস্তবায়ন, পর্যবেক্ষণ, তারপর স্কেল করা
একটি জব টাইপ বেছে নিন এবং শুরু থেকে শেষ পর্যন্ত সেটা তৈরি করুন আগে অন্যগুলো যোগ করার। একটি একক রিমাইন্ডার জব ভাল সূচনাকারী কারণ এটা সবকিছু স্পর্শ করে: শিডিউলিং, প্রাপ্য কাজ ক্লেইম করা, মেসেজ পাঠানো, ও আউটকাম রেকর্ড করা।
একটি এমন সংস্করণ দিয়ে শুরু করুন যা আপনি বিশ্বাস করতে পারেন:
- jobs table তৈরি করুন এবং একটি worker যে একটি জব টাইপ প্রসেস করে সেটি রাখুন
- একটি scheduler লুপ যোগ করুন যা প্রাপ্য জব ক্লেইম করে এবং চালায়
- জব চালাতে যথেষ্ট payload সংরক্ষণ করুন যাতে অতিরিক্ত অনুমান না করা লাগে
- প্রত্যেক চেষ্টা ও ফলাফল লগ করুন যাতে “এটি চলেছে কি?” ১০ সেকেন্ডে জানা যায়
- ব্যর্থ জবের জন্য ম্যানুয়াল rerun পথ রাখুন যাতে রিকভারি ডিপ্লয় ছাড়াই করা যায়
একবার এটা চললে, মানুষের জন্য এটি পর্যবেক্ষণযোগ্য করুন। এমন একটি বেসিক অ্যাডমিন ভিউ দ্রুতই ফল দেয়: স্ট্যাটাস দিয়ে জব সার্চ, সময় দিয়ে ফিল্টার, payload ইন্সপেক্ট, আটকে থাকা জব বাতিল করা, নির্দিষ্ট জব আইডি পুনরায় চালানো।
যদি আপনি ভিজ্যুয়াল ব্যাকএন্ড লজিকে এই ধরনের scheduler এবং worker ফ্লো বানাতে চান, AppMaster (appmaster.io) PostgreSQL-এ jobs table মডেল করতে পারে এবং claim-process-update লুপকে Business Process হিসেবে বাস্তবায়িত করতে পারে, একই সময়ে বাস্তব সোর্স কোড জেনারেট করে ডেপ্লয়মেন্টের জন্য।


