সেশন ০৯

Background Jobs ও Worker Pool - পেছনের কাজ, সময়মতো আর নিরাপদে

এই সেশনের লক্ষ্য: User এর request এর বাইরে চলা কাজগুলো (background job) কীভাবে নির্ভরযোগ্যভাবে চালাতে হয়, সেটা বোঝা। Job এর রকমফের আর cron, worker pool এর মাপ ঠিক করা, concurrency limit, backoff আর jitter দিয়ে retry, timeout আর status, deploy এর সময় graceful shutdown, আর একই cron দুইবার চলা ঠেকানো।

০০ যে প্রশ্নগুলোর উত্তর খুঁজবো

সেশন ০৮ এ আমরা শিখেছি, ধীর কাজ সাথে সাথে না করে queue তে রেখে দেওয়া যায়। পরে worker রা সেটা তুলে নিয়ে শেষ করে। কিন্তু সেখানে worker কে আমরা একটা বাক্স হিসেবেই দেখেছি। ভেতরে কী হয়, সেটা দেখা হয়নি।

এই সেশনে আমরা সেই বাক্সটা খুলবো। কতজন worker রাখবো? কাজ fail করলে কখন আবার চেষ্টা করবো? Deploy এর সময় worker হঠাৎ বন্ধ হলে অর্ধেক করা কাজের কী হবে? আর রাত ২টার কাজ ভুল করে দুইবার চললে কী হবে? প্রতিটা প্রশ্ন আমরা দেখবো একটা payroll startup এর একটা কঠিন সকালের ভেতর দিয়ে।

  • ১ ৫০,০০০ salary slip একটা loop এ বানালে সমস্যা কোথায়? (background job)
  • ২ Job কত রকমের হয়? (one-off, delayed, cron, recurring)
  • ৩ অনেক job একসাথে চালাবো কীভাবে? (worker pool)
  • ৪ Worker pool কত বড় হবে? (CPU-bound বনাম IO-bound)
  • ৫ Worker বাড়ালেই কি কাজ সবসময় দ্রুত হয়? (concurrency limit)
  • ৬ Job fail করলে কখন আবার চেষ্টা করবো? (exponential backoff, jitter)
  • ৭ একটা job আটকে গেলে কে টের পায়? (job timeout, job status)
  • ৮ Deploy এর সময় চলতি job গুলোর কী হয়? (graceful shutdown)
  • ৯ দুই server এ একই cron job চললে কী হয়? (distributed lock)

০১ ৫০,০০০ salary slip একটা loop এ বানালে সমস্যা কোথায়?

নভেম্বরের ১ তারিখ, সকাল ৮টা ৫৫। তানিয়া অফিসে ঢুকে laptop খুলতেই দেখে, support inbox এ ৪০টা নতুন message। প্রায় সবগুলোর কথা এক: “আমার বেতনের slip এখনো আসেনি।” তানিয়ার startup দেশের ৬০টা কোম্পানির payroll চালায়, সব মিলিয়ে ৫০,০০০ কর্মী। নিয়ম হলো, প্রতি মাসের ১ তারিখ সকাল ৯টার আগে প্রত্যেকের email এ তার salary slip এর PDF পৌঁছাবে। তানিয়া script এর log খুললো। রাত ২টায় চালু হওয়া script টা এখন মাত্র ১২,৪৫০ নম্বর slip এ আছে। তার বুকটা ধক করে উঠলো।

Script টা দেখতে খুবই সরল ছিলো। একটা loop সব কর্মীর তালিকা ধরে একজন একজন করে এগোয়। প্রতিজনের জন্য আগে PDF বানায়, তারপর email পাঠায়, তারপর পরের জনে যায়। হিসাবটা করলেই সমস্যা পরিষ্কার হয়:

একটা slip:      PDF render 0.8s + email 1.2s = 2s

এক loop এ সব:   50,000 × 2s = 100,000s ≈ 27.8 ঘণ্টা

হাতে সময়:      রাত 2:00 থেকে সকাল 9:00 = 7 ঘণ্টা = 25,200s
9টার আগে হয়:   25,200 ÷ 2s = 12,600 slip
বাকি থাকে:      50,000 - 12,600 = 37,400 জন কর্মী

এক loop এ পুরো কাজ শেষ করতে প্রায় ২৮ ঘণ্টা লাগে। অথচ হাতে আছে মাত্র ৭ ঘণ্টা। কিন্তু সমস্যা শুধু ধীর হওয়া নয়:

  1. একটাই লাইন: Loop একবারে একটা slip নিয়েই ব্যস্ত থাকে। Machine এর বাকি core গুলো চুপচাপ বসে থাকে। এই loop টাই এখানে bottleneck (সেশন ০১), অর্থাৎ পুরো system এর গতি যে একটা জায়গায় আটকে আছে।
  2. মাঝপথে crash হলে সব গুলিয়ে যায়: রাত ৪টায় script crash করলে কোন কোন slip গেছে, তার কোনো হিসাব থাকে না। আবার শুরু থেকে চালালে প্রথম কয়েক হাজার মানুষ দুইটা করে email পান।
  3. ভুল জায়গায় চলে: Script টা চলছিলো web server এ। রাতে যে HR ম্যানেজাররা dashboard খোলেন, তাদের page ও তখন ধীর হয়ে যায়।

সমাধানের প্রথম ধাপ হলো ভাবনাটা বদলানো। “৫০,০০০ slip এর একটা বিশাল কাজ” না ভেবে ভাবুন “৫০,০০০টা ছোট, আলাদা কাজ”। প্রতিটা slip একটা আলাদা background job।

Background job: এমন একটা কাজ, যেটা কোনো user এর request এর ভেতরে চলে না। Web server এর বাইরে আলাদা একটা process (worker) কাজটা করে। সাধারণত job টা আগে একটা queue তে জমা হয়, তারপর worker সেখান থেকে তুলে নেয়।

Queue, ACK আর idempotency সেশন ০৮ এ দেখেছি। সেখানে প্রশ্ন ছিলো, কাজকে পরে করার জন্য কোথায় রাখবো। এই সেশনের প্রশ্ন হলো, সেই পরে করার কাজগুলো ঠিক সময়ে, ঠিক গতিতে আর নিরাপদে কীভাবে চালাবো।

আসল সমস্যা

একটা বড় কাজ একটা loop এ চালালে তিনটা জিনিস একসাথে হারায়: গতি, crash এর পরে হিসাব, আর web server এর শান্তি। কাজটাকে অনেকগুলো ছোট job এ ভাঙলে প্রতিটা job আলাদাভাবে চালানো, retry করা আর গোনা যায়।

০২ Job কত রকমের হয়?

তানিয়ার system এ শুধু মাসের শুরুর slip ই নেই। আরো কয়েক রকম কাজ পেছনে চলে। কোন job কখন চলবে, সেটা দেখে job কে চার ভাগে ভাগ করা যায়:

One-off HR চাইলো, এখনই একবার slip #48213 Delayed এখন ঠিক হলো, চলবে পরে ৩০ মিনিট অপেক্ষা reminder email Scheduled (cron) প্রতি মাসের ১ তারিখ, ২:০০ ১ নভেম্বর, ২:০০ ১ ডিসেম্বর, ২:০০ Recurring প্রতি ১৫ মিনিট পর পর check check check check check check সময়
একই worker চার রকম job চালায়। পার্থক্য শুধু কখন চলবে, সেখানে: one-off এখনই একবার, delayed একটা নির্দিষ্ট অপেক্ষার পরে, cron ক্যালেন্ডারের নির্দিষ্ট সময়ে, আর recurring একটা নির্দিষ্ট বিরতিতে বারবার।

Cron: ঘড়ি দেখে কাজ চালানো

Cron হলো Unix এর একটা পুরোনো আর খুব জনপ্রিয় scheduler, মানে এমন একটা program যেটা ঘড়ি দেখে নির্দিষ্ট সময়ে কাজ চালু করে। কখন চলবে, সেটা পাঁচটা ঘরে লেখা হয়। এই লেখাটাকে বলে cron expression। তানিয়ার payroll job এর cron expression হলো 0 2 1 * *:

মিনিটঘণ্টামাসের দিনমাসসপ্তাহের দিন
021**
০ মিনিটেরাত ২টায়১ তারিখেপ্রতি মাসেসপ্তাহের যেকোনো দিন

* মানে “যেকোনো”। তাই পুরো লাইনটার মানে: প্রতি মাসের ১ তারিখ রাত ২টা ০ মিনিটে কাজটা চলবে। প্রতি ১৫ মিনিটের recurring job টা লেখা যায় */15 * * * * দিয়ে।

খেয়াল করুন, cron নিজে PDF বানায় না। সে শুধু ঠিক সময়ে ঘুম ভেঙে ৫০,০০০টা job queue তে রেখে দেয়। আসল কাজ করে worker রা। এই ভাগটা মনে রাখুন, সেশনের শেষে এটা খুব কাজে লাগবে।

চার রকম job, একই worker

চার রকম job এর পার্থক্য শুধু কখন চলবে, সেখানে। কীভাবে চলবে, সেটা সবার জন্য একই। সবাই queue তে যায়, আর একই worker রা সবাইকে চালায়।

০৩ অনেক job একসাথে চালাবো কীভাবে?

এখন queue তে ৫০,০০০টা job। একজন worker একটা একটা করে চালালে আবার সেই ২৮ ঘণ্টা। তাই তানিয়া একসাথে অনেক worker চালানোর সিদ্ধান্ত নিলো। এই worker দের দলকে বলে worker pool।

Worker pool: নির্দিষ্ট সংখ্যক worker এর একটা দল, যারা একই queue থেকে job নেয়। প্রত্যেকে নিজের হাতের job শেষ করে ACK পাঠায়, তারপর queue থেকে পরের job নেয়। Worker এর সংখ্যা আগে থেকেই ঠিক করা থাকে।

WORKER POOL Cron ১ তারিখ, রাত ২:০০ ৫০,০০০ job বানায় QUEUE ৫০,০০০ job অপেক্ষায় Worker ১ একটা slip বানায়, পাঠায় Worker ২ একটা slip বানায়, পাঠায় . . . Worker ২৮ একটা slip বানায়, পাঠায় ১ worker: ~২৮ ঘণ্টা · ২৮ worker: ~১ ঘণ্টা যে worker খালি হয়, সে queue থেকে পরের job টা তুলে নেয়
Cron রাত ২টায় প্রতিটা কর্মীর জন্য একটা করে job queue তে রাখে। Worker pool এর ২৮টা worker একসাথে কাজ করে। যে worker হাতের slip শেষ করে, সে queue থেকে পরের job নেয়। তাই ২৮ ঘণ্টার কাজ প্রায় ১ ঘণ্টায় নামে।

চায়ের দোকানের কথা ভাবুন। দোকানে চারজন কর্মী। Customer এলে কেউ নতুন লোক ডেকে আনে না। যে কর্মী হাতের কাপ শেষ করেছেন, তিনিই পরের অর্ডার নেন। কর্মীর সংখ্যা ঠিক থাকে, অর্ডার যত খুশি আসুক। Worker pool ঠিক এভাবেই কাজ করে।

প্রশ্ন আসতে পারে, প্রতিটা job এর জন্য একটা করে নতুন process খুললেই তো হয়। ৫০,০০০ job মানে ৫০,০০০ process? প্রতিটা process কিছু memory নেয়। এত process একসাথে খুললে machine এর memory শেষ হয়ে যায়, আর machine টা crash করে। Pool এ worker এর সংখ্যা বাঁধা থাকে, তাই কাজ যত বাড়ুক, machine এর ওপর চাপ একটা সীমার ভেতরে থাকে।

এবার হিসাব। একটা slip এ ২ সেকেন্ড লাগে। ৫০,০০০ slip মানে মোট ১,০০,০০০ সেকেন্ডের কাজ। এটা এক ঘণ্টায় (৩,৬০০ সেকেন্ড) শেষ করতে চাইলে একসাথে লাগবে ১,০০,০০০ ÷ ৩,৬০০ ≈ ২৮ জন worker।

Worker pool এর মূল কথা

Queue বলে দেয় কী কী কাজ বাকি। Pool বলে দেয় একসাথে কতজন কাজ করবে। দুটো আলাদা রাখলে কাজের পরিমাণ যত বাড়ুক, machine এর ওপর চাপ নিয়ন্ত্রণে থাকে।

০৪ Worker pool কত বড় হবে?

২৮ সংখ্যাটা শুনে তানিয়ার টিমের একজন বললো, “তাহলে একটা ৮ core এর machine এ ২৮টা worker চালিয়ে দিই।” চালানোর পর দেখা গেলো, গতি প্রায় বাড়েইনি। কেন?

উত্তর লুকিয়ে আছে slip এর দুই অংশের চরিত্রে। একটা slip এর ২ সেকেন্ডের মধ্যে দুটো আলাদা রকম কাজ হয়:

একটা core এর সময় দুই রকম কাজে কীভাবে খরচ হয়, দেখুন:

CPU-BOUND: PDF RENDER Core ১ PDF ১ PDF ২ PDF ৩ core এক মুহূর্তও খালি থাকে না; এক PDF শেষ হলে তবেই পরেরটা IO-BOUND: EMAIL পাঠানো Email ১ network এর উত্তরের অপেক্ষা Email ২ Email ৩ Email ৪ Core ১ core প্রায় পুরো সময় খালি CPU এর কাজ মাত্র ২০ms, বাকি ১,১৮০ms শুধু অপেক্ষা; তাই এক core এ অনেক email একসাথে চলে 0 800ms 1600ms 2400ms
একই core, দুই রকম কাজ। PDF render এ core পুরো 800ms ব্যস্ত থাকে, তাই core এর চেয়ে বেশি worker চালালে তারা শুধু লাইনে দাঁড়ায়। Email পাঠানোর সময় core কাজ করে মাত্র ২০ms, বাকি সময় network এর উত্তরের অপেক্ষা। সেই ফাঁকা সময়ে অন্য email এর কাজ চলে, তাই এক core এ অনেক email worker রাখা যায়।

CPU-bound কাজে core সবসময় ব্যস্ত। ৮ core এ ২৮টা PDF worker চালালে একসাথে চলতে পারে ৮টাই। বাকি ২০টা পালার জন্য অপেক্ষা করে। উল্টো এদের মধ্যে বারবার পালা বদলাতে CPU এর কিছু সময় নষ্ট হয়, আর memory ও বেশি লাগে।

IO-bound কাজে core বেশিরভাগ সময় খালি। একটা email যখন উত্তরের অপেক্ষায়, core তখন আরেকটা email এর কাজ করতে পারে। তাই এখানে core এর চেয়ে অনেক বেশি worker রাখা যায়। হিসাবটা এরকম:

CPU-bound (PDF render, 0.8s পুরোটাই CPU)
  worker সংখ্যা ≈ core সংখ্যা
  8 core এর machine: 8 worker
  1 worker: 1 ÷ 0.8s = 1.25 slip/s
  8 worker: 10 slip/s;  3টা machine: 30 slip/s
  50,000 ÷ 30 ≈ 1,667s ≈ 28 মিনিট

IO-bound (email, 20ms CPU + 1,180ms অপেক্ষা)
  worker সংখ্যা ≈ core × (1 + অপেক্ষা ÷ CPU সময়)
              = 8 × (1 + 1180 ÷ 20) = 8 × 60 = 480
  কিন্তু আসল সীমা ঠিক করে email provider (পরের অংশ দেখুন)

এই হিসাব থেকে তানিয়া একটা গুরুত্বপূর্ণ সিদ্ধান্ত নিলো। একটা slip এর দুই অংশ একই worker এ রাখলে কোনো মাপই ঠিক হয় না। ৮টা worker রাখলে email এর সময় core খালি পড়ে থাকে। ৪৮০টা রাখলে PDF এর সময় সবাই core এর জন্য ঠেলাঠেলি করে। তাই সে কাজটা দুই ভাগ করলো। একটা queue (pdf-render) শুধু PDF এর জন্য, যার worker সংখ্যা core এর সমান। আরেকটা queue (send-email) শুধু email এর জন্য, যেখানে worker অনেক বেশি। PDF worker কাজ শেষ করে একটা email job পরের queue তে রেখে দেয়।

খরচটাও দেখার মতো। Cloud এ একটা ৮ core এর machine ভাড়া ঘণ্টায় মোটামুটি ৪০ টাকা। তিনটা machine আধা ঘণ্টা চালালে মাসে খরচ ৬০ টাকার মতো। কাজ শেষ হলে machine গুলো বন্ধ করে দেওয়া যায়।

মাপ ঠিক করার নিয়ম

CPU-bound কাজে worker রাখুন core এর সমান। IO-bound কাজে core এর অনেক গুণ রাখা যায়, কারণ বেশিরভাগ সময় worker শুধু অপেক্ষা করে। একই job এ দুই রকম কাজ থাকলে job টা ভেঙে দুটো queue তে দিন।

০৫ Worker বাড়ালেই কি কাজ সবসময় দ্রুত হয়?

হিসাব বলছিলো, email এর জন্য ৪৮০ জন worker রাখা যায়। তানিয়া সাবধানে ১০০ দিয়ে শুরু করলো। পাঁচ মিনিটের মাথায় log ভরে গেলো একই error এ: 429 Too Many Requests।

তানিয়ার email provider এর একটা নিয়ম আছে। একটা account থেকে সেকেন্ডে ৪০টার বেশি email নেবে না। ১০০ জন worker প্রত্যেকে ১.২ সেকেন্ডে একটা করে পাঠালে সেকেন্ডে যায় প্রায় ৮৩টা। সীমার দ্বিগুণ। Provider বাড়তি request গুলো ফিরিয়ে দিচ্ছিলো।

এখানে তানিয়ার machine এ কোনো সমস্যা নেই। Bottleneck সরে গেছে অন্য পক্ষের কাছে। এর সমাধান হলো concurrency limit।

Concurrency limit: একটা নির্দিষ্ট ধরনের job একসাথে সর্বোচ্চ কয়টা চলতে পারবে, তার সীমা। Pool এ যত worker ই থাকুক, এর বেশি job একসাথে চলবে না। বাকিরা পালার জন্য অপেক্ষা করবে।

LIMIT ছাড়া ~৮৩ request/s ১০০ worker সবাই একসাথে পাঠায় Provider: 429 Too Many Requests LIMIT সহ ~৩৭/s ১০০ worker বাকিরা পালার অপেক্ষায় Limit: ৪৫ একসাথে এর বেশি নয় Provider: 200 OK সীমা ৪০/s এর ভেতরে
Email provider সেকেন্ডে ৪০টার বেশি request নেয় না। ১০০ worker একসাথে পাঠালে সেকেন্ডে প্রায় ৮৩টা request যায়, আর provider 429 ফেরত দেয়। Concurrency limit ৪৫ রাখলে একসাথে ৪৫টার বেশি email চলে না। প্রতিটা email 1.2s নেয়, তাই হার দাঁড়ায় সেকেন্ডে প্রায় ৩৭টা, সীমার ভেতরে।

সীমা কত হবে, সেটা হিসাব করে বের করা যায়। একটা email এ ১.২ সেকেন্ড লাগে। তাই একসাথে ৪৮টা email চললে সেকেন্ডে ঠিক ৪০টা যায় (৪০ কে ১.২ দিয়ে গুণ করে ৪৮)। একটু জায়গা রেখে তানিয়া limit দিলো ৪৫। এতে সেকেন্ডে প্রায় ৩৭টা email যায়। ৫০,০০০ email পাঠাতে লাগে ২৩ মিনিটের মতো।

Database নিয়েও একই সাবধানতা দরকার। তানিয়ার database একসাথে ২০০টা connection নিতে পারে। এর মধ্যে ১২০টা web server এর লাগে। Worker রা প্রত্যেকে একটা করে connection খুললে web server এর জন্য connection বাকি থাকে না, আর HR dashboard খুলতেই পারে না। তাই worker দের database connection এর জন্যও আলাদা একটা সীমা রাখা হলো: সব worker মিলিয়ে সর্বোচ্চ ৬০টা।

সবচেয়ে ধীর অংশই গতি ঠিক করে

Worker বাড়ালে কাজ দ্রুত হয়, যতক্ষণ না অন্য কোনো জিনিস সীমায় পৌঁছায়। সেটা email provider, database বা অন্য কোনো API হতে পারে। প্রতিটা ভাগ করা জিনিসের সামনে একটা concurrency limit বসান, যাতে আপনার worker রা নিজেরাই অন্য system কে চাপে না ফেলে।

০৬ Job fail করলে কখন আবার চেষ্টা করবো?

রাত ২টা ১৪। Email provider এর নিজের system এ একটা গোলমাল হলো। তিন মিনিট ধরে সব request এ তারা ফেরত দিলো 503 Service Unavailable। এর মধ্যে তানিয়ার ৩,০০০টা email job fail করলো।

সেশন ০৮ থেকে আমরা জানি, ACK না হওয়া job আবার ফিরে আসে। কিন্তু কখন ফিরবে? সাথে সাথে retry করলে কী হয়, ভাবুন। ৩,০০০ job এক সেকেন্ডের মধ্যে আবার provider এর দরজায় ধাক্কা দেয়। Provider তখনো ঠিক হয়নি, তাই আবার fail। আবার সাথে সাথে retry। যে system টা সুস্থ হওয়ার চেষ্টা করছে, তার ওপর এই চাপ সুস্থ হওয়াটাকেই পিছিয়ে দেয়।

Exponential backoff: প্রতিবার একটু বেশি অপেক্ষা

এর প্রথম সমাধান হলো প্রতিবার fail করার পর আগের চেয়ে দ্বিগুণ সময় অপেক্ষা করা। প্রথমবার ২ সেকেন্ড, তারপর ৪, তারপর ৮, ১৬, ৩২। এই নিয়মের নাম exponential backoff। ছোটখাটো সমস্যা হলে প্রথম দুই এক চেষ্টাতেই কাজ হয়ে যায়। বড় সমস্যা হলে অপেক্ষা লম্বা হতে থাকে, আর অন্য পক্ষ দম ফেলার সময় পায়। অপেক্ষা যেন অসীম না হয়, তাই একটা ছাদ (cap) থাকে, যেমন ৫ মিনিট। আর মোট চেষ্টারও একটা সীমা থাকে, যেমন ৬ বার। ৬ বার fail হলে job টা failed হয়, আর সেশন ০৮ এর Dead Letter Queue (DLQ) তে যায়।

Jitter: সবাই যেন একসাথে না ফেরে

শুধু backoff এ একটা সমস্যা থেকে যায়। ৩,০০০টা job তো একই মুহূর্তে fail করেছিলো। তাই সবাই একসাথে ২ সেকেন্ড অপেক্ষা করে, একসাথে ফেরে। তারপর একসাথে ৪ সেকেন্ড অপেক্ষা করে, আবার একসাথে ফেরে। চাপ কমেনি, শুধু ঢেউয়ের মতো দফায় দফায় আসছে।

এর সমাধান jitter। প্রতিটা job অপেক্ষার সীমার ভেতরে একটা random সময় বেছে নেয়। কেউ ০.৩ সেকেন্ড পরে ফেরে, কেউ ১.৭ সেকেন্ড পরে। ঢেউটা ভেঙে ছড়িয়ে পড়ে:

JITTER ছাড়া: সবাই একসাথে provider যতটা সামলাতে পারে ৩,০০০ job একসাথে ২s ৬s ১৪s ৩০s ৬২s JITTER সহ: ছড়িয়ে পড়ে provider যতটা সামলাতে পারে ২s ৬s ১৪s ৩০s ৬২s সময় (fail হওয়ার পর থেকে)
৩,০০০ job একই মুহূর্তে fail করলো। Jitter ছাড়া সবাই ঠিক ২, ৬, ১৪ সেকেন্ডে একসাথে retry করে, আর প্রতিবার provider এর সীমা ছাড়িয়ে যায়। Jitter দিলে প্রত্যেকে অপেক্ষার সীমার ভেতরে একটা random সময় বেছে নেয়। Retry গুলো সময় জুড়ে ছড়িয়ে পড়ে, আর provider সুস্থ হওয়ার সুযোগ পায়।

Office ছুটির পর লিফটের কথা ভাবুন। সবাই ঠিক ৫টায় বের হলে লিফটের সামনে বিশাল ভিড় হয়। কেউ ৫টা ২ মিনিটে, কেউ ৫টা ৭ মিনিটে বের হলে লিফট সবাইকে আরামে নামাতে পারে। Jitter ঠিক এই কাজটাই করে। হিসাবটা লেখা হয় এভাবে:

wait = random(0, min(cap, base × 2^attempt))     # full jitter
base = 1s,  cap = 300s,  max attempt = 6

attempt   jitter ছাড়া   jitter সহ (এই সীমার যেকোনো মান)
1         2s             0 - 2s
2         4s             0 - 4s
3         8s             0 - 8s
4         16s            0 - 16s
5         32s            0 - 32s
6         64s            0 - 64s
এরপর      status = failed, job যায় DLQ তে

কোন error এ retry করবেন, কোনটায় নয়

সব error retry করার মতো নয়। Provider এর 503 বা network timeout সাময়িক সমস্যা, একটু পরে ঠিক হয়ে যায়। এদের বলে transient error। এগুলোতে retry করুন। কিন্তু কোনো কর্মীর email address ভুল হলে ছয়বার চেষ্টা করেও লাভ নেই। এমন স্থায়ী error এ job টা সরাসরি failed করে দিন, যাতে HR address টা ঠিক করতে পারেন।

Retry এর আরেকটা ঝুঁকি আছে। Email আসলে চলে গিয়েছিলো, কিন্তু provider এর উত্তর আসার আগেই connection কেটে গেলো। Worker ভাবলো fail হয়েছে, আর আবার পাঠালো। কর্মী দুইটা slip পেলেন। তাই প্রতিটা job এর একটা idempotency key থাকে (সেশন ০৮), যেমন slip:2026-11:emp-48213। Worker পাঠানোর আগে দেখে নেয়, এই key এর email আগে গেছে কিনা। একটা user request এর ভেতরে অন্য service কে call করার সময় retry কীভাবে সামলাতে হয়, সেটা সেশন ২১ এ দেখবো।

ভালো retry এর চারটা অংশ

প্রতিবার অপেক্ষা দ্বিগুণ করুন (exponential backoff)। অপেক্ষায় একটু random মেশান (jitter)। মোট চেষ্টা আর সর্বোচ্চ অপেক্ষার সীমা রাখুন, তারপর DLQ। আর শুধু সাময়িক error এ retry করুন, তাও idempotency key সহ।

০৭ একটা job আটকে গেলে কে টের পায়?

রাত ২টা ১৮। একটা কোম্পানির slip এর সাথে ৪০০ পাতার attendance report জোড়া ছিলো। PDF বানাতে গিয়ে library টা কোথাও আটকে গেলো। Error দিলো না, crash ও করলো না। শুধু চলতেই থাকলো।

Crash হলে সমস্যা ছিলো না, সেশন ০৮ এর ACK আর visibility timeout সেটা সামলে নিতো। কিন্তু আটকে থাকা worker তো চালু আছে। Queue ভাবছে কাজ চলছে। Worker টা অন্য কোনো job ও নিচ্ছে না। একই রকম আরো কয়েকটা slip এলে একে একে সব worker আটকে যায়:

TIMEOUT ছাড়া Worker ১ আটকে আছে Worker ২ Worker ৩ Worker ৪ চারজনই আটকে গেলো, অথচ queue তে হাজার হাজার job অপেক্ষায় TIMEOUT ৩০s সহ Worker ১ ৩০s, kill Worker ২ ৩০s, kill Worker ৩ ৩০s, kill Worker ৪ ৩০s, kill ৩০ সেকেন্ডে job kill হয়, retry তে যায়; worker নতুন job নেয় সময়
ছোট বাক্সগুলো সাধারণ slip, প্রতিটা এক সেকেন্ডের কম। মাঝে মাঝে একটা PDF আটকে যায়। Timeout না থাকলে আটকে যাওয়া job worker টাকে চিরদিনের জন্য দখল করে রাখে, আর একে একে পুরো pool থেমে যায়। ৩০ সেকেন্ডের timeout থাকলে job টা kill হয়ে retry তে যায়, আর worker আবার কাজে ফেরে।

Job timeout

সমাধান হলো প্রতিটা job এর জন্য একটা সময়সীমা বেঁধে দেওয়া। এই সময় পার হলে worker নিজেই job টা থামিয়ে দেয়, failed হিসেবে লেখে, আর retry তে পাঠায়। এর নাম job timeout।

Timeout কত হবে? তানিয়া গত মাসের হিসাব দেখলো। সাধারণ একটা PDF এ লাগে ০.৮ সেকেন্ড। সবচেয়ে ধীর ১%ও ৩ সেকেন্ডের মধ্যে শেষ হয়। তাই সে timeout দিলো ৩০ সেকেন্ড। স্বাভাবিক কোনো কাজ এতে কাটা পড়বে না, কিন্তু আটকে যাওয়া কাজ বেশিক্ষণ worker দখল করে রাখতে পারবে না।

একটা নিয়ম মনে রাখুন: job timeout সবসময় queue এর visibility timeout এর চেয়ে ছোট হবে। তানিয়ার queue তে visibility timeout ৬০ সেকেন্ড। উল্টো হলে কী হতো? ধরুন visibility timeout ২০ সেকেন্ড, আর job ২৫ সেকেন্ড ধরে চলছে। ২০ সেকেন্ডে queue ভাববে worker crash করেছে, আর job টা আরেকজনকে দেবে। তখন একই slip দুইজন worker একসাথে বানাবে।

Job status: প্রতিটা job এখন কোথায়

সকালে HR জানতে চাইবেন, “আমাদের কোম্পানির সবার slip কি গেছে?” Queue এই প্রশ্নের উত্তর দিতে পারে না। Queue শুধু জানে কোন job এখনো বাকি। যে job শেষ হয়ে গেছে, সেটা queue থেকে মুছে গেছে।

তাই তানিয়া database এ একটা jobs table রাখলো। প্রতিটা job এর একটা row, আর তাতে তার job status:

idtypestatusattemptslast_error
slip:2026-11:emp-48213send-emaildone১
slip:2026-11:emp-48214send-emailretrying৩503 from provider
slip:2026-11:emp-51007pdf-renderfailed৬timeout after 30s

Status বদলায় একটা নির্দিষ্ট পথে:

worker নিলো সফল ৬ বার fail বা স্থায়ী error সাময়িক error attempt < ৬ backoff শেষে queued ১২,৪০৫ running ৪৫ done ৩৭,৪৩০ failed ৮ এরপর DLQ (সেশন ০৮) retrying ১১২
প্রতিটা job এর একটা status থাকে, আর সেটা এই পথেই বদলায়। বাক্সের সংখ্যাগুলো রাত ২:২০ এর একটা মুহূর্তের ছবি। সব মিলিয়ে সবসময় ৫০,০০০ হয়, তাই HR এক নজরেই বুঝতে পারে কতগুলো বাকি আর কোনগুলো আটকে আছে।

এই table থেকে HR এর dashboard এ এক লাইনে দেখানো যায়: “৪৯,৯৯২টা পাঠানো হয়েছে, ৮টা আটকে আছে।” আরেকটা লাভ আছে। কোনো job ১০ মিনিট ধরে running থাকলে বোঝা যায় কিছু একটা গোলমাল। একটা ছোট recurring job প্রতি ৫ মিনিটে এমন job খুঁজে বের করে আবার queue তে দেয়।

আটকে যাওয়া crash এর চেয়েও বিপজ্জনক

Crash করা worker কে queue নিজেই ধরে ফেলে। আটকে থাকা worker কে কেউ ধরে না, যদি না আপনি timeout দেন। আর status table না থাকলে কোন job কোথায় আছে, সেটা কেউ বলতে পারে না।

০৮ Deploy এর সময় চলতি job গুলোর কী হয়?

রাত ২টা ২০। তানিয়ার এক সহকর্মী timeout এর fix টা merge করলেন। নিয়ম অনুযায়ী নতুন code সাথে সাথে deploy হতে শুরু করলো। পুরোনো worker গুলো বন্ধ হবে, নতুন worker চালু হবে। কিন্তু পুরোনো worker দের হাতে তখন অর্ধেক বানানো slip।

তানিয়ার worker গুলো চলে Kubernetes এ। Kubernetes হলো এমন একটা system, যেটা অনেক machine জুড়ে application চালায়, আর deploy এর সময় পুরোনোগুলো সরিয়ে নতুনগুলো বসায়। একটা worker বন্ধ করার আগে Kubernetes তাকে একটা সংকেত পাঠায়, যার নাম SIGTERM। মানে, “এবার গুছিয়ে বন্ধ হওয়ার সময়।” তারপর সে ৩০ সেকেন্ড অপেক্ষা করে। এই সময়টাকে বলে grace period। ৩০ সেকেন্ডেও worker বন্ধ না হলে Kubernetes পাঠায় SIGKILL। এতে process টা সাথে সাথে থেমে যায়, কিছু গোছানোর সুযোগ থাকে না।

তানিয়ার পুরোনো worker গুলো SIGTERM এর কোনো খেয়ালই করতো না। ফলে প্রতিবার deploy এ যা চলছিলো, সব মাঝপথে কাটা পড়তো। কোনো slip এর email চলে গেছে কিন্তু status এ done লেখা হয়নি। কোনো job ৬০ সেকেন্ডের visibility timeout শেষ না হওয়া পর্যন্ত কারো হাতেই নেই।

Graceful shutdown

ভালো worker SIGTERM পেলে ভদ্রভাবে বিদায় নেয়। এই আচরণকে বলে graceful shutdown:

১ নতুন version deploy শুরু Kubernetes worker কে SIGTERM পাঠায় t = 0s ২ queue থেকে নতুন job নেওয়া বন্ধ হাতে ৩টা slip এর কাজ চলছে ৩ হাতের কাজ শেষ করে ACK পাঠায় status: done t ≈ 2s ৪ লম্বা কোনো job থাকলে ফেরত দেয় queue তে আবার রাখে, অন্য worker নেবে ৫ worker নিজেই বন্ধ হয় কোনো কাজ মাঝপথে কাটা পড়েনি t ≈ 3s grace period ৩০s পার হলে: SIGKILL তখন যা চলছিলো, সব মাঝপথে থেমে যায় t = 30s
Deploy এর সময় Kubernetes প্রথমে একটা ভদ্র সংকেত পাঠায় (SIGTERM): এবার গুছিয়ে বন্ধ হওয়ার সময়। ভালো worker তখন নতুন job নেওয়া থামায়, হাতের কাজ শেষ করে, আর নিজেই বন্ধ হয়। Grace period এর ৩০ সেকেন্ড পার হলে Kubernetes জোর করে process থামিয়ে দেয় (SIGKILL)। তাই কোনো job এর এর চেয়ে বেশি সময় লাগলে সেটা queue তে ফেরত দিতে হয়।
  1. নতুন job নেওয়া বন্ধ: Queue থেকে আর কিছু তোলে না।
  2. হাতের কাজ শেষ: যে slip গুলো চলছিলো, সেগুলো শেষ করে ACK পাঠায় আর status এ done লেখে।
  3. লম্বা কাজ ফেরত: কোনো job grace period এর মধ্যে শেষ হওয়ার মতো না হলে worker সেটা নিজেই queue তে ফেরত দেয়। Visibility timeout এর জন্য অপেক্ষা না করে আরেকজন worker সাথে সাথে সেটা তুলে নিতে পারে।
  4. নিজে বন্ধ হওয়া: সব গুছিয়ে worker নিজেই exit করে। SIGKILL এর দরকারই পড়ে না।

এখানে আগের অংশের timeout আবার কাজে লাগে। তানিয়ার job timeout ৩০ সেকেন্ড, grace period ও ৩০ সেকেন্ড। কোনো job ই timeout এর বেশি চলতে পারে না, তাই হাতের কাজ শেষ করার জন্য grace period যথেষ্ট। Job যত ছোট, deploy তত নিরাপদ। এটাও প্রতিটা slip কে আলাদা job বানানোর একটা বড় লাভ।

Deploy কোনো দুর্ঘটনা নয়

Worker প্রতি সপ্তাহে কয়েকবার deploy হয়। তাই worker থামানো একটা নিয়মিত ঘটনা, দুর্ঘটনা নয়। SIGTERM পেলে নতুন job নেওয়া থামান, হাতের কাজ শেষ করুন, আর না পারলে job টা ফেরত দিন।

০৯ দুই server এ একই cron job চললে কী হয়?

নভেম্বরের মাঝামাঝি তানিয়ার টিম আরেকটা পরিবর্তন করলো। এতদিন cron চলতো একটাই server এ। সেটা down হলে কোনো slip ই যেতো না, অর্থাৎ ঐ server টা ছিলো একটা SPOF (single point of failure, সেশন ০১)। তাই তারা application টা দুইটা server এ চালু করলো (সেশন ০২ এর redundancy)।

পরদিন সকালে একজন HR ম্যানেজার জানালেন, “ব্যাংকের status email প্রতিবার দুইটা করে আসছে।” তানিয়া log দেখে বুঝলো ঘটনাটা। প্রতি ১৫ মিনিটের recurring job টা এখন দুই server এই চলছে। দুই server এ একই code, তাই একই cron schedule। ঠিক তখনই তার মনে পড়লো, ডিসেম্বরের ১ তারিখ রাত ২টায় payroll job টাও দুই জায়গায় চলবে:

LOCK ছাড়া Server A cron: রাত ২:০০ Server B cron: রাত ২:০০ Queue: ১,০০,০০০ job প্রত্যেকে slip পায় দুইবার DISTRIBUTED LOCK সহ OK: lock পেলো nil: skip করলো Server A SET ... NX Server B SET ... NX শুধু A job বানায় Redis lock lock:payroll:2026-11 Queue: ৫০,০০০ job
দুটো server এ একই code, তাই একই cron দুই জায়গাতেই চলে। Lock ছাড়া দুজনেই ৫০,০০০ করে job বানায়। Lock থাকলে দুজনেই Redis এর কাছে একই key চায়, কিন্তু পায় শুধু একজন। যে পায় না, সে চুপচাপ সরে যায়।

দুইটা server এ ৫০,০০০ করে মোট ১,০০,০০০ job। প্রত্যেক কর্মী দুইটা slip পাবেন। ১ তারিখের আগে এটা ঠিক করতেই হবে।

Distributed lock

একটা machine এর ভেতরে দুইটা thread যেন একই কাজ একসাথে না করে, তার জন্য lock ব্যবহার হয়। কিন্তু এখানে দুইটা আলাদা machine। তাদের lock রাখতে হবে এমন জায়গায়, যেটা দুজনেই দেখতে পায়। এমন lock কে বলে distributed lock।

Distributed lock: এমন একটা lock, যেটা অনেকগুলো machine এর মধ্যে ভাগ করা একটা জায়গায় (যেমন Redis) থাকে। একসাথে শুধু একটা machine এটা ধরে রাখতে পারে। যে পায়, সে কাজটা চালায়। বাকিরা কাজটা বাদ দেয়।

তানিয়া Redis ব্যবহার করলো (সেশন ০৪ এ cache হিসেবে দেখা সেই Redis)। রাত ২টায় দুই server ই Redis এর কাছে একই key চায়:

# রাত 2:00, server A আর server B দুজনেই এই command পাঠায়
SET lock:payroll:2026-11 server-A NX EX 900

server A পায়:  OK     # lock তার, সে 50,000 job বানাবে
server B পায়:  nil    # আগেই কেউ নিয়েছে, তাই skip

NX      key আগে থেকে না থাকলে তবেই set হয়
EX 900  900s (15 মিনিট) পরে key নিজে থেকেই মুছে যায়

# দ্বিতীয় পাহারা: database এ একটা unique row
INSERT INTO payroll_runs (company_id, month) VALUES (17, '2026-11');
# একই company_id আর month আবার ঢোকাতে গেলে database error দেয়

NX এর কারণে দুজনের মধ্যে শুধু প্রথমজন key টা পায়। Redis একটা একটা করে command চালায়, তাই দুজনে একসাথে চাইলেও একজনই জেতে।

EX 900 অংশটা খুব জরুরি। ধরুন server A lock নিয়েই crash করলো। Lock এর মেয়াদ না থাকলে সেটা চিরদিন আটকে থাকবে, আর পরের মাসে কোনো server ই job চালাতে পারবে না। মেয়াদ থাকলে ১৫ মিনিট পরে lock নিজে থেকেই খুলে যায়। মেয়াদটা কাজের সময়ের চেয়ে বেশি রাখতে হয়। এখানে cron এর কাজ শুধু ৫০,০০০ job queue তে রাখা, যাতে এক মিনিটের মতো লাগে। ১৫ মিনিট তাই যথেষ্ট।

Lock এর পাশে দ্বিতীয় পাহারা

Distributed lock পুরোপুরি নিখুঁত নয়। ধরুন server A lock নিলো, তারপর কোনো কারণে ২০ মিনিট থেমে রইলো। ততক্ষণে lock এর মেয়াদ শেষ, server B ও lock পেয়ে গেছে। এখন দুজনেই ভাবছে lock তার। এমন বিরল ঘটনা ঠেকাতে তানিয়া database এ একটা payroll_runs table রাখলো। Table এ company_id আর month মিলে unique। প্রতিটা কোম্পানির প্রতি মাসের run একবারই লেখা যায়। দ্বিতীয়বার লিখতে গেলে database নিজেই error দেয়। Lock সাধারণ দিনে দুইবার চলা ঠেকায়, আর unique row ঠেকায় বিরল দিনেরটা।

আরো দুইটা পথ আছে, দুটোরই নিজের downside আছে। একটা হলো cron শুধু একটা আলাদা scheduler server এ চালানো। কিন্তু তাহলে সেই server আবার SPOF হয়ে যায়। আরেকটা হলো server গুলো নিজেদের মধ্যে একজন leader বেছে নেবে, আর শুধু leader cron চালাবে। Leader কীভাবে বাছাই হয়, সেটা সেশন ১৬ এর বিষয়।

সব মিলিয়ে: ডিসেম্বরের ১ তারিখ

Cron ২:০০ + distributed lock ৫০,০০০ job বানায় একটাই server চালায় QUEUE: pdf-render status: queued PDF worker: ৩ machine × ৮ CPU-bound, ~৩০ slip/s timeout ৩০s status: running QUEUE: send-email PDF তৈরি, পাঠানোর অপেক্ষায় Email worker: limit ৪৫ IO-bound, ~৩৭ email/s retry: backoff + jitter ৬ বার fail হলে DLQ সব slip পৌঁছালো রাত ২:৩১ এ status: done, সকাল ৯টার অনেক আগে deploy হলে graceful shutdown
ডিসেম্বরের ১ তারিখে তানিয়ার নতুন system। কাজটা দুই ভাগে ভাগ হয়েছে, কারণ PDF বানানো CPU-bound আর email পাঠানো IO-bound। প্রতিটা ভাগের নিজের queue আর নিজের মাপের worker pool আছে। ২৮ ঘণ্টার কাজ এখন প্রায় আধা ঘণ্টায় শেষ হয়।

রাত ২টা ৩১। Dashboard এ শেষ সংখ্যাটা ভেসে উঠলো: ৫০,০০০টা done, ০টা failed। নভেম্বরের failed তালিকা দেখে HR আগেই ভুল email address গুলো ঠিক করে রেখেছিলেন। Email provider একবার আধা মিনিটের জন্য 503 দিয়েছিলো। Backoff আর jitter চুপচাপ সেটা সামলে নিয়েছে। মাঝে একবার deploy ও হয়েছে, কোনো slip হারায়নি। তানিয়া laptop বন্ধ করে ঘুমাতে গেলো। সকাল ৯টায় support inbox এ একটাও নতুন message ছিলো না।

সবচেয়ে গুরুত্বপূর্ণ শিক্ষা

Background job চালানো মানে শুধু একটা queue আর কিছু worker নয়। কাজটাকে ছোট job এ ভাঙুন। কাজের চরিত্র দেখে pool এর মাপ ঠিক করুন, আর অন্যের সীমা মেনে concurrency limit দিন। Fail করলে backoff আর jitter দিয়ে retry করুন, আটকে গেলে timeout দিন, আর প্রতিটা job এর status রাখুন। Deploy এ ভদ্রভাবে বন্ধ হোন, আর একাধিক server থাকলে lock দিয়ে নিশ্চিত করুন যে cron একবারই চলে।

১০ মগজে প্রেশার দিন

আগে নিজে ভাবুন। তারপর উত্তর খুলে দেখুন: বেশিরভাগ মানুষ কীভাবে ভাবে, সেই চিন্তায় কী বাদ পড়ে, আর আসল কথাটা কোথায়।

প্রশ্ন ১: ৫০,০০০ slip আরো তাড়াতাড়ি পাঠাতে ৫০০টা worker চালালেই তো হয়?
উত্তর দেখুন আগে নিজে ভাবুন

সবাই যেভাবে ভাবে

“Worker যত বেশি, কাজ তত দ্রুত। ২৮ জনে এক ঘণ্টা লাগলে ৫০০ জনে কয়েক মিনিটেই শেষ হবে।”

এই হিসাবে একটা ধরে নেওয়া কথা লুকিয়ে আছে: worker রা একে অপরের পথ আটকায় না।

যে জায়গাটা যোগ করা দরকার

Worker বাড়ালে গতি বাড়ে, যতক্ষণ না কোনো ভাগ করা জিনিস সীমায় পৌঁছায়। PDF এর মতো CPU-bound কাজে সীমা হলো core এর সংখ্যা। ৮ core এ ৫০০ worker চালালেও একসাথে চলে ৮টাই। Email এর মতো IO-bound কাজে সীমা ঠিক করে অন্য পক্ষ। তানিয়ার provider সেকেন্ডে ৪০টার বেশি নেয় না, তাই ৫০০ worker শুধু 429 error বাড়াবে। আর প্রতিটা worker একটা database connection নিলে web server এর connection ফুরিয়ে যাবে।

তাই প্রথম প্রশ্ন হওয়া উচিত, “এই কাজে সবচেয়ে ধীর ভাগ করা জিনিস কোনটা?” সেটার মাপেই pool আর concurrency limit ঠিক হয়।

সঠিক ভাষায়

Worker সংখ্যা ঠিক করে কাজের চরিত্র (CPU-bound না IO-bound) আর সবচেয়ে সংকীর্ণ ভাগ করা জিনিস। এর বেশি worker শুধু লাইনে দাঁড়ায়, বা অন্য system কে চাপে ফেলে।

প্রশ্ন ২: Email provider ৫ মিনিটের জন্য down। আপনার retry এর নিয়ম কী হবে?
উত্তর দেখুন আগে নিজে ভাবুন

সবাই যেভাবে ভাবে

“Fail করলে ১০ সেকেন্ড পর পর ৩ বার retry করবো। তারপরও না হলে বাদ দেবো।”

নিয়মটা শুনতে যুক্তিসঙ্গত। কিন্তু ৫ মিনিটের outage এ এটা দুই দিক থেকেই ভুল করে।

যে জায়গাটা যোগ করা দরকার

প্রথমত, ৩ বার × ১০ সেকেন্ড মানে ৩০ সেকেন্ডেই সব চেষ্টা শেষ। Provider ফিরবে ৫ মিনিট পরে, ততক্ষণে হাজার হাজার job বাদ পড়ে গেছে। Exponential backoff এ অপেক্ষা ২, ৪, ৮, ১৬, ৩২, ৬৪ সেকেন্ড করে বাড়ে, তাই ৬ বারের চেষ্টা প্রায় দুই মিনিট জুড়ে ছড়ায়। চেষ্টা আরেকটু বাড়ালে বা cap বড় রাখলে ৫ মিনিটের outage ও পার হয়ে যায়।

দ্বিতীয়ত, হাজার হাজার job একই মুহূর্তে fail করেছে। একই নির্দিষ্ট বিরতিতে retry করলে সবাই একসাথে ফেরে। Provider যেই উঠে দাঁড়ায়, প্রথম ঢেউটাই তাকে আবার overloaded করে ফেলে। তাই অপেক্ষায় jitter মেশাতে হয়।

এর সাথে বাকি তিনটা অংশ: শুধু 503 আর timeout এর মতো সাময়িক error এ retry, সব চেষ্টা শেষে DLQ, আর প্রতিটা email এর idempotency key, যাতে retry তে কেউ দুইটা slip না পান।

মাথায় গেঁথে নিন

Retry এর নিয়ম মানে শুধু “কতবার” নয়। কতক্ষণ পর (backoff), সবাই একসাথে কিনা (jitter), কোন error এ (transient), আর শেষে কোথায় (DLQ), চারটাই বলতে হয়।

প্রশ্ন ৩: Interview এ জিজ্ঞেস করা হলো: রাত ২টার একটা cron job, আর application চলছে তিনটা server এ। কী সমস্যা হতে পারে, আর কীভাবে ঠিক করবেন?
উত্তর দেখুন আগে নিজে ভাবুন

সবাই যেভাবে ভাবে

“সমস্যা হলো cron তিনবার চলবে। সমাধান সহজ: দুইটা server এ cron বন্ধ রেখে শুধু একটায় চালু রাখবো।”

সমস্যাটা ঠিক ধরা হয়েছে। কিন্তু সমাধানটা নতুন একটা সমস্যা তৈরি করে।

যে জায়গাটা যোগ করা দরকার

শুধু একটা server এ cron রাখলে সেই server একটা SPOF। মাসের ১ তারিখ রাতে ঐ server down থাকলে কোনো slip ই যাবে না, আর কেউ টেরও পাবে না। তিনটা server রাখার উদ্দেশ্যটাই তখন হারিয়ে যায়।

ভালো উত্তর হলো, তিনটা server এই cron চালু থাকুক, কিন্তু কাজ শুরুর আগে সবাই একটা distributed lock চাইবে। যে পাবে, শুধু সে চালাবে। Lock এর একটা মেয়াদ (TTL) থাকবে, যাতে lock নেওয়া server crash করলেও পরের বার কাজ আটকে না যায়। আর lock পুরোপুরি নিখুঁত নয় বলে কাজটা নিজেও idempotent হবে: database এ (company, month) এর একটা unique row, যাতে দ্বিতীয় run শুরুতেই থেমে যায়।

Interview এ যা বলবেন

“একটাই server এ cron” মানে SPOF। সব server এ cron, সাথে মেয়াদওয়ালা distributed lock, আর কাজের ভেতরে একটা unique key: এই তিনটা মিলে cron একবারই চলে, একটা server down থাকলেও।

১১ যে টার্মগুলো শিখলাম

টার্মমানে
Background jobUser এর request এর বাইরে, আলাদা worker এ চলা একটা কাজ। সাধারণত queue থেকে আসে।
One-off jobএকবারই চলে, আর যত দ্রুত সম্ভব।
Delayed jobএখন queue তে যায়, কিন্তু একটা নির্দিষ্ট সময় পরে চলে।
Scheduled jobক্যালেন্ডারের নির্দিষ্ট সময়ে চলে, যেমন প্রতি মাসের ১ তারিখ রাত ২টা।
Recurring jobএকটা নির্দিষ্ট বিরতিতে বারবার চলে, যেমন প্রতি ১৫ মিনিটে।
Cronঘড়ি দেখে নির্দিষ্ট সময়ে কাজ চালু করার পুরোনো আর জনপ্রিয় scheduler।
Cron expressionপাঁচ ঘরের একটা লেখা (মিনিট, ঘণ্টা, দিন, মাস, সপ্তাহের দিন), যেটা বলে কাজ কখন চলবে। যেমন 0 2 1 * *।
Worker poolনির্দিষ্ট সংখ্যক worker এর দল, যারা একই queue থেকে একটার পর একটা job নেয়।
CPU-boundযে কাজের পুরো সময় CPU হিসাব করে। Worker রাখুন core এর সমান।
IO-boundযে কাজের বেশিরভাগ সময় network, disk বা database এর উত্তরের অপেক্ষায় যায়। Core এর অনেক গুণ worker রাখা যায়।
Concurrency limitএকটা ধরনের job একসাথে সর্বোচ্চ কয়টা চলতে পারবে, তার সীমা।
Transient errorসাময়িক error, একটু পরে নিজে থেকেই ঠিক হয়ে যায় (যেমন 503, timeout)।
Exponential backoffপ্রতিবার fail করার পর আগের চেয়ে দ্বিগুণ সময় অপেক্ষা করে retry করা।
JitterRetry এর অপেক্ষায় একটু random সময় মেশানো, যাতে সব job একসাথে না ফেরে।
Job timeoutএকটা job সর্বোচ্চ যতক্ষণ চলতে পারে। সময় পার হলে job থামিয়ে retry তে পাঠানো হয়।
Job statusএকটা job এখন কোন অবস্থায় আছে: queued, running, retrying, done বা failed।
SIGTERMProcess কে গুছিয়ে বন্ধ হতে বলার সংকেত।
SIGKILLProcess কে সাথে সাথে থামিয়ে দেওয়ার সংকেত। এতে গোছানোর কোনো সুযোগ থাকে না।
Grace periodSIGTERM আর SIGKILL এর মাঝের সময়। Kubernetes এ সাধারণত ৩০ সেকেন্ড।
Graceful shutdownবন্ধ হওয়ার আগে নতুন কাজ নেওয়া থামানো, হাতের কাজ শেষ করা, আর বাকিটা ফেরত দেওয়া।
Distributed lockঅনেক machine এর মধ্যে ভাগ করা একটা lock, যেটা একসাথে শুধু একজন ধরে রাখতে পারে।
Lock TTLLock এর মেয়াদ। এই সময় পরে lock নিজে থেকেই খুলে যায়, যাতে crash করা machine চিরদিন lock আটকে না রাখে।

১২ শর্ট সামারি

  1. বড় কাজকে অনেকগুলো ছোট background job এ ভাঙুন। Cron শুধু ঠিক সময়ে job গুলো queue তে রাখে, আর একটা worker pool সেগুলো একসাথে চালায়।
  2. Pool এর মাপ ঠিক করে কাজের চরিত্র। CPU-bound কাজে worker থাকে core এর সমান, IO-bound কাজে অনেক বেশি। আর বাইরের system এর সীমা মানতে প্রতিটা ধরনের job এ একটা concurrency limit দিতে হয়।
  3. Fail করা job retry করুন exponential backoff আর jitter দিয়ে, শুধু সাময়িক error এ, আর idempotency key সহ। আটকে যাওয়া job থামান timeout দিয়ে, আর প্রতিটা job এর status database এ রাখুন।
  4. Deploy এর সময় worker SIGTERM পেলে graceful shutdown করবে। একাধিক server এ cron চললে distributed lock আর একটা unique key মিলে নিশ্চিত করে যে কাজটা একবারই চলে।

১৩ মনে রাখুন