Partial Failure: কিছু component সচল, কিছু অনিশ্চিত
Partial Failures and Network Partitions
এই পাঠের সূচিপত্র
এই পাঠে যা শিখবেন
- Partial failure এবং অনিশ্চিত ফল আলাদা করুন
- Timeout-এর পরে order-এর সত্য খুঁজুন
- নিরাপদ retry ও reconciliation পরিকল্পনা করুন
সমস্যা দিয়ে শুরু
একটি দোকানের Laravel API payment provider-এ টাকা কাটার request পাঠাল। Provider টাকা কাটল, কিন্তু উত্তর ফেরার পথে connection ভাঙল। Browser failure দেখালেও ব্যাংকের হিসাবে charge আছে। সমস্ত server বন্ধ হয়নি; শুধু যোগাযোগের একটি অংশ ব্যর্থ হয়েছে। এই অস্পষ্ট অবস্থায় নতুন payment বানালে একই ক্রেতার টাকা আবার কাটতে পারে।
সহজ করে বুঝি
Distributed system-এ অন্য process-এর কাজ ও নিজের দেখা response এক বিষয় নয়। Timeout বলে নির্ধারিত সময়ে উত্তর আসেনি; কাজ হয়নি বলে না। Network partition-এ কিছু node পরস্পরের সঙ্গে কথা বলতে পারে না, অথচ প্রত্যেকে সচল থাকে। তাই success, definite rejection এবং unknown outcome আলাদা state হওয়া দরকার।
পরিচিত একটি উপমা
কুরিয়ারে টাকা পাঠিয়ে প্রাপকের ফোন বন্ধ পাওয়ার মতো। ফোন না ধরলে টাকা পৌঁছায়নি বলা যায় না। Tracking number দিয়ে পরে খোঁজ নিতে হয়। Software-এ সেই tracking number হলো স্থায়ী operation identifier; মানুষের অনুমানের বদলে provider-এর authoritative status দেখতে হয়।
নকশাটি দেখুন
Diagram লোড হচ্ছে…
Diagram-এর text flow দেখুন
flowchart LR A[Laravel Payment] -->|Same operation key| B[Provider] B --> C[Charge committed] C -.->|Response lost| A A -->|Reconcile by key| B
প্রথম arrow-তে operation key request-এর পরিচয় বহন করে। Provider নিজের database-এ charge স্থায়ী করে। ভাঙা response arrow-এর কারণে Laravel শুধু ফল জানে না। শেষ arrow নতুন charge নয়; একই পরিচয়ের status query। UI-তে pending দেখানো এই সময়ের সৎ আচরণ।
PHP ও Laravel-এ প্রয়োগ
Payment controller-এর fragment; validated amount, authenticated customer এবং আগে তৈরি করা pending payment আছে ধরে নিন। Provider-এর documented idempotency ও lookup contract ছাড়া এই pattern নিরাপদ নয়। ConnectionException হলে retry না করে reconciliation queue-তে পাঠানো হচ্ছে।
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
try {
$response = Http::baseUrl(config('services.payments.url'))
->withToken(config('services.payments.token'))
->withHeaders(['Idempotency-Key' => $payment->operation_key])
->connectTimeout(1)->timeout(3)
->post('/charges', ['amount_minor' => $payment->amount_minor]);
$response->throw();
return response()->json(['state' => 'received'], 202);
} catch (ConnectionException $exception) {
Log::warning('payment.outcome_unknown', ['payment_id' => $payment->id]);
return response()->json(['state' => 'pending_review'], 202);
}এটি PHP 8.3+ ও Laravel 12-এর শিক্ষামূলক fragment; সম্পূর্ণ application নয়। সংশ্লিষ্ট route-এ authentication ও resource authorization প্রয়োগ করতে হবে। নিচের অনুশীলনে failure ইচ্ছা করে তৈরি করুন; শুধু সফল response দেখে correctness ধরে নেবেন না।
সুবিধা, সীমাবদ্ধতা ও trade-off
Pending state ব্যবহারকারীকে অপেক্ষা করায় এবং reconciliation worker চালাতে হয়। বিনিময়ে একটি সাময়িক network সমস্যাকে মিথ্যা rejection বানাতে হয় না। Balance বা booking-এর মতো irreversible কাজের ক্ষেত্রে সঠিক ফলের অপেক্ষা সাধারণত দ্রুত ভুল ফলের চেয়ে ভালো। Catalog image ব্যর্থ হলে placeholder যথেষ্ট হতে পারে।
সাধারণ ভুল
- Timeout-কে provider rollback-এর প্রমাণ মনে করা।
- Retry-তে operation key বদলে নতুন charge সৃষ্টি করা।
- Pending payment-এর বয়স, সংখ্যা ও reconciliation failure না মাপা।
আরও একটু গভীরে
Failure detector বাস্তবে সন্দেহ জানায়; ধীর process আর মৃত process সব সময় আলাদা করতে পারে না। অতএব retry budget, deadline এবং operation state machine একসঙ্গে design করুন। HTTP 500 পেলেও provider-এর side effect আগে হয়ে থাকতে পারে।
পরীক্ষায় response আসার আগে connection কেটে দিন। এরপর provider ledger, local payment row এবং user-visible state মিলিয়ে দেখুন। প্রত্যাশা হলো একটি operation key-তে সর্বোচ্চ একটি charge এবং pending record শেষে confirmed অথবা rejected হবে। Worker বারবার ব্যর্থ হলে মানুষের investigation queue দরকার।
মনে রাখুন
- অনিশ্চিত ফলকে আলাদা state দিন।
- একই operation-এর পরিচয় স্থায়ী রাখুন।
- সত্যের মালিকের সঙ্গে reconciliation করুন।
নিজে চেষ্টা করুন
দুটি request timeline লিখুন: provider request পাওয়ার আগে connection ভাঙা এবং commit-এর পরে response হারানো। কোন ক্ষেত্রে সরাসরি retry নিরাপদ তা provider contract দিয়ে ব্যাখ্যা করুন।
নিজের বোঝা যাচাই করুন
1. Timeout মানেই failure?
না; এটি অপেক্ষার সীমা। Provider ইতিমধ্যে commit করলে local caller ফল না জেনেও side effect ঘটে যায়।
2. Pending কেন দরকার?
Unknown outcome-কে success বা rejection বলে ভুল না করতে। পরে authoritative status দিয়ে state বদলানো যায়।
3. সব service বন্ধ হওয়া দরকার?
না। একটি network link বা dependency ব্যর্থ হলেও অন্য component স্বাভাবিক কাজ করতে পারে।
4. নতুন key-তে retry কেন বিপদ?
Provider সেটিকে নতুন operation ভাবতে পারে। Deduplication পুরোনো পরিচয়ের সঙ্গে সম্পর্কিত।
5. কী monitor করবেন?
Pending age ও reconciliation error; HTTP error rate একা financial correctness দেখায় না।
আরও পড়ুন
এই পাঠের কুইজ
এই পাঠ থেকে ২টি প্রশ্ন। উত্তর দিয়ে ব্যাখ্যা দেখুন; অধ্যায়ের কুইজ আলাদাভাবে দিতে হবে।
এই পাঠের ২টি প্রশ্নের উত্তর দিন। পাস করতে দুটিই সঠিক হতে হবে।
জমা দেওয়ার পর সঠিক উত্তর ও ব্যাখ্যা দেখবেন। ফলাফল এই ব্রাউজারে সংরক্ষিত থাকবে। অনুশীলনের ফল অধ্যায় সম্পন্নের হিসাবে যোগ হয় না।
অগ্রগতি এই ব্রাউজারে সংরক্ষিত থাকে। শুধু পাঠ খুললে সম্পন্ন হয় না।