Distributed Job Scheduler: claiming, lease ও retry
Distributed Job Scheduler
এই পাঠের সূচিপত্র
এই পাঠে যা শিখবেন
- Due job claiming ও execution আলাদা করা
- SKIP LOCKED, leases ও fencing token বোঝা
- Retry, timezone ও idempotent side effect design
সমস্যা দিয়ে শুরু
প্রতি মাসে invoice পাঠানোর scheduler তিন server-এ চলছে। একই invoice তিনবার গেল। Leader lock যোগ করলেও old process pause হয়ে ফিরে এসে নতুন leader-এর সঙ্গে কাজ করছে। Requirements: durable scheduled jobs, due-time execution, worker claim, retry ও audit। Real-time deadline guarantee নয়, acceptable scheduling lag নির্ধারণ করুন। Cron expression, timezone ও daylight-saving behavior স্পষ্ট না হলে business schedule-ও ভুল হয়।
সহজ করে বুঝি
Scheduler due work খুঁজে ready queue বা job table থেকে claim করে। PostgreSQL FOR UPDATE SKIP LOCKED multiple claimer-কে locked row এড়িয়ে আলাদা কাজ নিতে দেয়; এটি general consistent read নয়, queue-like access pattern। Claim lease expiry crashed worker-এর কাজ পুনরায় নিতে দেয়। Monotonic fencing token downstream stale owner reject করতে সাহায্য করে, তবে downstream token enforce না করলে শুধু number সংরক্ষণে লাভ নেই।
পরিচিত একটি উপমা
গুদামে কাজের card clerk নিজের নামে অল্প সময়ের জন্য নেন। Clerk হারিয়ে গেলে card ফেরত line-এ আসে। পুরোনো clerk পরে এসে কাজ করতে চাইলে নতুন assignment number দেখে গুদাম বুঝবে তার authority stale। কিন্তু gatekeeper number না দেখলে পুরোনো card দিয়েই কাজ হয়ে যাবে।
নকশাটি দেখুন
Diagram লোড হচ্ছে…
Diagram-এর text flow দেখুন
flowchart TD C[Cron or schedule definitions] --> D[(Due job table)] A[Claimer A] --> L[Short transaction claim] B[Claimer B] --> L L --> D L --> W[Execute with lease token] W --> E[Idempotent effect and fenced state] W --> R[Complete or schedule retry] X[Expired lease recovery] --> D
Schedule definitions থেকে durable due instance তৈরি হয়। Claim transaction row lock নিয়ে lease owner, expiry ও token update করে দ্রুত commit করে। Execution lock ধরে নয়, lease context নিয়ে চলে। Completion conditional owner/token দিয়ে হয়। Expired lease অন্য worker নিতে পারে, তাই duplicate overlap expected; business effect idempotent এবং stale mutation fenced হওয়া দরকার।
PHP ও Laravel-এ প্রয়োগ
Assumption: প্রতি মিনিটে ৬ হাজার due job মানে average ১০০ claim/second, actual job duration concurrency নির্ধারণ করে। jobs table-এ due_at,status,lease_until,owner,token এবং unique schedule occurrence key। নিচে Laravel query fragment PostgreSQL transaction-এ small batch claim পড়ে; একই transaction-এ owner, lease ও incremented token save করে execution context ফেরানো হয়েছে। DB server time, indexes ও isolation verify করুন।
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
$workerId = (string) Str::uuid();
$claims = DB::transaction(function () use ($workerId) {
$jobs = DB::table('scheduled_jobs')
->where('status', 'ready')
->where('due_at', '<=', now())
->orderBy('due_at')->orderBy('id')
->limit(20)->lock('FOR UPDATE SKIP LOCKED')->get();
return $jobs->map(function ($job) use ($workerId) {
$token = $job->token + 1;
DB::table('scheduled_jobs')->where('id', $job->id)->update([
'status' => 'leased',
'owner' => $workerId,
'lease_until' => now()->addMinute(),
'token' => $token,
]);
return ['id' => $job->id, 'owner' => $workerId, 'token' => $token];
})->all();
});সুবিধা, সীমাবদ্ধতা ও trade-off
Database claim operationally simple এবং transactional, কিন্তু polling ও hot due index pressure বাড়ায়। Broker execution buffering ভালো দিতে পারে, schedule source এবং dispatch gap তখনও থাকে। Long lease duplicate overlap কমায়, recovery ধীর করে। Short lease দ্রুত recovery দেয়, heartbeat ও fencing accuracy বেশি গুরুত্বপূর্ণ। Exactly-once external email/payment promise করবেন না।
সাধারণ ভুল
- Execution চলাকালে database row lock ধরে রাখা।
- Lease expiry মানেই old worker মরে গেছে ধরা।
- Token downstream check ছাড়া fencing implemented দাবি করা।
আরও একটু গভীরে
Local completion-ও fenced হতে হবে: UPDATE scheduled_jobs SET status='completed' WHERE id=? AND owner=? AND token=? AND status='leased' চালিয়ে affected row এক কি না দেখুন। Zero মানে ownership বদলেছে, তাই পুরোনো worker result authoritative নয়। এটি শুধু local row রক্ষা করে; email বা external database write-এর downstream নিজে token/idempotency যাচাই না করলে external effect fenced নয়।
Lease renewal conditional owner/token-এ করুন। Retry exponential backoff, jitter, maximum attempts ও dead-letter review রাখুন। Laravel Scheduler-এর onOneServer shared atomic cache lock দিয়ে duplicate trigger কমাতে পারে, কিন্তু arbitrary long-running side effect-এ fencing substitute নয়। Schedule occurrence unique key DST duplicate বা repeated tick সামলায়। Lag, expired leases এবং stuck jobs metric দিন; operator replay audited এবং idempotency-preserving হবে।
মনে রাখুন
Scheduler reliable হয় durable due instances, short atomic claims এবং duplicate-safe fenced execution দিয়ে; lease একা exactly-once নয়।
নিজে চেষ্টা করুন
Worker lease নেওয়ার পরে pause হয়, অন্য worker reclaim করে—এই scenario লিখুন। Old worker completion reject এবং external effect dedupe কীভাবে হবে বলুন। SKIP LOCKED claim batch-এর concurrency integration test design করুন।
নিজের বোঝা যাচাই করুন
1. SKIP LOCKED কেন?
অন্য transaction locked job এড়িয়ে আলাদা কাজ claim করতে দেয়। Queue pattern-এ উপযোগী, consistent full-table read নয়।
2. Lease expiry কি process kill?
না। Old process pause বা network partition-এর পরে ফিরে আসতে পারে। Stale authority reject করতে হবে।
3. Fencing token কী করে?
Monotonic ownership version downstream enforce করলে পুরোনো owner-এর mutation reject হয়। শুধু log field হলে protection নেই।
4. Execution transaction-এ?
দীর্ঘ job-এ lock ধরে contention বাড়ে। Short claim transaction এবং conditional completion রাখুন।
5. onOneServer যথেষ্ট?
Duplicate scheduled trigger কমাতে সহায়ক; external side effect, lease overlap ও retry-এর correctness আলাদা design লাগে।
আরও পড়ুন
এই পাঠের কুইজ
এই পাঠ থেকে ২টি প্রশ্ন। উত্তর দিয়ে ব্যাখ্যা দেখুন; অধ্যায়ের কুইজ আলাদাভাবে দিতে হবে।
এই পাঠের ২টি প্রশ্নের উত্তর দিন। পাস করতে দুটিই সঠিক হতে হবে।
জমা দেওয়ার পর সঠিক উত্তর ও ব্যাখ্যা দেখবেন। ফলাফল এই ব্রাউজারে সংরক্ষিত থাকবে। অনুশীলনের ফল অধ্যায় সম্পন্নের হিসাবে যোগ হয় না।
অগ্রগতি এই ব্রাউজারে সংরক্ষিত থাকে। শুধু পাঠ খুললে সম্পন্ন হয় না।