labZero:~$ cd ./notes / note-queue-job-idempoten...
Job نجح — ثم نُفّذ مرة ثانية. من المسؤول؟
مهمة إنشاء فاتورة نُفّذت مرتين بعد failure جزئي — Job نجح في الكتابة وفشل في تحديث الحالة، فظنّ الطابور أنه فشل.
في نظام فوترة تلقائية، Job يُنشئ فاتورة ويرسل بريداً ويُحدّث invoice_status. في أحد الأيام وجدت فاتورتين لنفس الطلب وبريدين للعميل نفسه. راجعت logs: الـ Job نجح في إنشاء الفاتورة ثم فشل قبل تحديث الحالة بخطأ شبكة في إرسال البريد. الطابور اعتبره فاشلاً وأعاده. الإعادة أنشأت فاتورة ثانية.
الجوهر: المشكلة في غياب idempotency — الـ Job لم يكن آمناً للتنفيذ مرتين. كل job يعدّل بيانات يجب أن يسأل: «ماذا يحدث لو نُفّذت مرتين؟» الحل في هذه الحالة مفتاح تفرد على مستوى البيانات:
Invoice::firstOrCreate(
['order_id' => $this->orderId, 'period' => $this->period],
['amount' => $this->amount, 'status' => 'pending']
);
بهذا الشكل، التنفيذ الثاني يجد الفاتورة موجودة ولا يُنشئ نسخة. للبريد أضفت فحصاً: if ($invoice->wasRecentlyCreated) قبل الإرسال — فقط الفاتورة الجديدة تُرسل بريداً.
المبدأ الذي أطبقه الآن: كل Job يكتب بيانات يُصمَّم كأنه سيُنفَّذ مرتين. الفشل الجزئي في الطوابير ليس استثناءً نادراً — هو حالة طبيعية في أي نظام distributed. idempotency ليست feature إضافية، بل شرط لأي عملية تمرّ عبر queue في الإنتاج.
لديك مشكلة مشابهة؟ باب التعاون من هنا ‹