تشريح حادثة توقف متجر إلكتروني ليلة الجمعة: 5 أخطاء بسيطة في إعداد السيرفر كبّدت المتجر آلاف الدولارات
قصة تقنية بأسلوب Postmortem تشرح كيف اجتمعت RAM غير الكافية وQuery بطيئة وإعداد Workers خاطئ وBackup في وقت الذروة وغياب Monitoring لتوقف متجر أثناء أهم ساعات البيع.
الساعة 7:42 مساءً ليلة الجمعة.
الإعلانات تعمل، الطلبات تدخل، وفريق المتجر يراقب المبيعات ترتفع أسرع من المعتاد.
كان يفترض أن تكون هذه أفضل ليلة في الأسبوع.
بعد أقل من ساعة أصبحت صفحة الدفع تحتاج ثماني ثوانٍ للتحميل.
بعدها بدأت بعض الطلبات تفشل.
ثم ظهرت أول:
502 Bad Gateway
وبحلول الساعة 9 تقريبًا، أصبح جزء كبير من العملاء غير قادر على إكمال الشراء.
المشكلة لم تكن هجومًا، ولم تنقطع الكهرباء عن Datacenter، ولم يتعطل Domain.
المتجر سقط بسبب خمس قرارات صغيرة في إعداد Server كان كل واحد منها يبدو «مقبولًا» عندما كان عدد الزوار منخفضًا.
هذه حادثة افتراضية مركبة وليست قصة متجر بعينه. الأرقام والأوقات المستخدمة للتوضيح، لكن الأخطاء التقنية نفسها من المشاكل الشائعة جدًا في متاجر وتطبيقات الإنتاج.
7:42 مساءً — كل شيء يبدو طبيعيًا
المتجر يعمل على VPS بالمواصفات التالية:
4 vCPU
4 GB RAM
80 GB SSD
Ubuntu
Nginx
PHP-FPM
WooCommerce
MariaDB
Redis
في الأيام العادية كان الاستخدام قريبًا من:
RAM:
2.6 - 3.1 GB
CPU:
20% - 45%
ولهذا كان الانطباع:
«السيرفر مرتاحة.»
لكن هذه كانت أول غلطة في طريقة التفكير.
المتجر تم قياسه في يوم عادي، بينما الليلة القادمة لم تكن يومًا عاديًا.
8:03 مساءً — أول شكوى: «الموقع صار ثقيل»
الحملة الإعلانية بدأت تجلب عددًا أكبر من الزوار.
لا يوجد انهيار حتى الآن.
فقط Response Time ترتفع تدريجيًا:
Normal:
350ms
Now:
1.4s
Then:
2.7s
في هذه المرحلة كان يمكن إنقاذ الليلة كلها لو وصلت تنبيه مبكر.
لكن لم يكن هناك Alert.
لا يوجد تنبيه عندما:
Available RAM < 15%
CPU > 85%
Database Connections مرتفعة
Response Time تتجاوز حدًا معينًا
المؤشر الوحيد كان العميل نفسه.
وهنا يظهر الخطأ الأول.
الخطأ الأول: Server اختيرت على Average وليس Peak
وجود 4GB RAM لم يكن المشكلة وحده.
المشكلة أن الاستخدام الطبيعي كان أصلًا:
3GB تقريبًا من أصل 4GB
أي أن المتجر لم يكن يملك Headroom حقيقية.
عند زيادة الزوار زاد عدد PHP Processes، وارتفعت Database Connections، وبدأ Redis يحتفظ ببيانات أكثر.
أصبح الاستهلاك:
OS + services 700 MB
MariaDB 1200 MB
Redis 300 MB
PHP-FPM 2200 MB
Other 250 MB
------------------------
Total 4650 MB
لكن Server لا تملك 4.65GB.
تملك 4GB فقط.
من هنا بدأ الضغط الحقيقي.
المشكلة ليست أن 4GB «سيئة»
4GB قد تكون ممتازة لمتجر آخر.
المشكلة أن هذه Server كانت تحتاج في Peak إلى أكثر من ذلك.
المواصفات يجب أن تبنى على:
Peak المتوقع
+
Headroom
وليس:
متوسط يوم هادئ
8:11 مساءً — RAM وصلت للحد
بدأ النظام باستخدام Swap.
وهذا خفف الانهيار الفوري، لكنه أدخل Server في مشكلة جديدة.
Database التي تحتاج وصولًا سريعًا للذاكرة بدأت تنتظر عمليات تخزين أبطأ.
Response Time ارتفعت أكثر.
والزائر الذي كان يحتاج ثانية للوصول إلى Checkout أصبح ينتظر عدة ثوانٍ.
بعض الزوار ضغطوا Refresh.
وهذا أرسل Requests إضافية إلى Server المتعبة أصلًا.
ما بدأ كنقص بسيط في الموارد أصبح Feedback Loop.
Server بطيئة
↓
Users ينتظرون
↓
Refresh / Retry
↓
Requests أكثر
↓
Server أبطأ
8:16 مساءً — Database أصبحت مركز الحادثة
المراقبة اليدوية أظهرت أن MariaDB أصبحت من أكبر مستهلكي CPU.
في البداية كان الاعتقاد:
«نحتاج CPU أكثر.»
لكن Query Log حكت قصة مختلفة.
صفحة داخل المتجر كانت تشغل Query شبيهة بـ:
SELECT ...
FROM order_items
WHERE product_id = ?
ORDER BY created_at DESC;
Table أصبحت كبيرة مع نمو المتجر.
والاستعلام الذي كان سريعًا عندما كانت البيانات قليلة أصبح مكلفًا جدًا بعد أشهر من الطلبات.
المشكلة؟
Index المناسبة غير موجودة.
الخطأ الثاني: Database نمت، لكن تصميم Queries بقي كما كان يوم الإطلاق
عندما كان الجدول يحتوي 10,000 Row لم تكن المشكلة واضحة.
عندما أصبح يحتوي مئات الآلاف من Rows، بدأ الفرق يظهر.
ومع عشرات المستخدمين في الوقت نفسه، أصبحت نفس Query تتكرر باستمرار.
بدل أن تحصل Database بسرعة على Rows المطلوبة، كان عليها فحص كمية أكبر من البيانات.
هنا زيادة CPU يمكن أن تساعد قليلًا، لكنها لا تعالج السبب.
الحل الحقيقي كان مراجعة:
EXPLAIN
Indexes
Slow Query Log
Query frequency
Database schema
وإضافة Index مناسبة بعد اختبارها.
أحيانًا ترقية CPU تخفي المشكلة لفترة، لكن إذا كان التطبيق يقرأ بيانات بطريقة غير فعالة، سيعود نفس الاختناق عندما ينمو الحمل مرة أخرى.
8:22 مساءً — الفريق يحاول «تسريع» الموقع
ظهر اقتراح سريع:
«زد عدد PHP Workers.»
كان PHP-FPM مضبوطًا على عدد محدود من Workers.
فتم رفع العدد حتى يستطيع الموقع تنفيذ Requests أكثر بالتزامن.
لعدة دقائق بدا أن الحل نجح.
ثم أصبحت Server أسوأ.
الخطأ الثالث: زيادة Workers بدون حساب RAM لكل Worker
إذا كانت PHP Process الواحدة تستهلك مثلًا:
120 MB
فإن:
10 Workers
≈ 1.2 GB
لكن:
25 Workers
≈ 3 GB
وهذا فقط PHP.
ما زالت لديك:
MariaDB
Redis
Nginx
OS
Monitoring
Cron Jobs
رفع `pm.max_children` لم يخلق RAM جديدة.
هو فقط سمح لـPHP باستهلاكها بسرعة أكبر.
بعد دقائق أصبح الضغط على الذاكرة أقوى من قبل.
كيف تحسب Workers بصورة أفضل؟
ابدأ بقياس متوسط وPeak استهلاك Process الحقيقية في تطبيقك.
ثم استخدم ميزانية واضحة:
RAM المسموح بها لـPHP
÷
RAM التقريبية لكل Worker
=
حد مبدئي للـWorkers
ثم اختبر Load فعلية.
لا تنس أن الهدف ليس تشغيل أكبر عدد Workers ممكن.
الهدف هو أعلى Throughput تستطيع Server تحمله بدون انهيار الذاكرة أو Database.
8:34 مساءً — أول 502
بدأ Nginx يعرض:
502 Bad Gateway
بعض PHP Workers توقفت.
ثم ظهرت OOM Events.
Linux أصبح تحت ضغط ذاكرة كافٍ لدرجة أن النظام بدأ يحاول استعادة الذاكرة بالقوة.
إعادة تشغيل PHP-FPM أعادت الموقع مؤقتًا.
لكن السبب لم يختف.
بعد دقائق عادت المشكلة.
8:47 مساءً — جاء أسوأ توقيت ممكن للـBackup
المتجر لديه Backup تلقائية.
وهذا شيء ممتاز.
لكن توقيتها كان:
Friday
8:45 PM
أي أثناء أعلى وقت مبيعات.
بدأت المهمة:
mysqldump
+
ضغط الملفات
+
رفع النسخة إلى Storage خارجي
فجأة أضفنا:
- قراءة إضافية من Database.
- CPU للضغط.
- Disk I/O.
- Network Traffic.
إلى Server كانت أصلًا على حافة الانهيار.
الخطأ الرابع: مهمة الصيانة تعمل وقت Peak
المشكلة لم تكن وجود Backup.
بل عدم التفكير في وقت تنفيذها وتأثيرها.
هناك Jobs كثيرة تبدو «خفيفة» عندما نراها منفردة:
Backup
Log rotation
Image processing
Reports
Database cleanup
Index maintenance
Scheduled imports
لكن تشغيلها مع Peak Traffic قد يحول Server مستقرة إلى Server مزدحمة.
الحل
قسم Jobs إلى:
Critical
يمكن أن تعمل في أي وقت
Heavy
تعمل خارج Peak
Deferrable
يمكن تأجيلها عند ارتفاع الحمل
وفي البيئات الأكثر أهمية يمكن نقل بعض Jobs إلى Worker منفصلة أو Server مختلفة بدل تنفيذ كل شيء على نفس VPS.
8:52 مساءً — الإدارة تعرف بالمشكلة من العملاء
بدأت رسائل:
«الدفع لا يفتح.»
«الموقع يعلق.»
«خصم المبلغ والصفحة ما كملت.»
هنا اكتشف الفريق مشكلة خامسة.
لم يكن هناك Monitoring خارجي فعلي.
الخطأ الخامس: المراقبة موجودة بعد المشكلة، لا قبلها
معرفة أن Server توقفت بعد اتصال العميل ليست Monitoring.
المراقبة المفيدة يجب أن تجيب قبل الانهيار عن أسئلة مثل:
كم Available RAM الآن؟
هل Swap ترتفع؟
هل Database Connections تقترب من الحد؟
ما P95 Response Time؟
هل Checkout نفسها تعمل؟
هل HTTP 200 ما زالت ترجع؟
هل Disk I/O مرتفعة؟
هل مساحة التخزين تقترب من الامتلاء؟
وكان يجب أن توجد Alerts مثل:
Available RAM < 15% لمدة 5 دقائق
CPU > 85% لمدة 10 دقائق
P95 > 2 seconds
HTTP failures > threshold
Disk > 85%
Database connections > threshold
الأرقام نفسها تختلف حسب التطبيق.
المهم أن يكون هناك إنذار قبل وصول العميل إلى صفحة بيضاء.
9:06 مساءً — وقف النزيف أولًا
في Incident حقيقية، لا تبدأ بإعادة تصميم التطبيق بينما العملاء لا يستطيعون الشراء.
الأولوية الأولى هي Stabilization.
في سيناريو هذه الحادثة كان التدخل المؤقت:
- إيقاف Backup الثقيلة.
- إعادة PHP Workers إلى رقم آمن.
- زيادة RAM مؤقتًا.
- إعادة تشغيل الخدمات المتضررة بصورة منظمة.
- تقليل الحمل غير الضروري.
عاد المتجر.
لكن هذا لم يكن الحل النهائي.
9:18 مساءً — الموقع عاد، لكن لا نغلق Incident
أحد أخطر الأخطاء بعد عودة الخدمة هو قول:
«خلاص اشتغل.»
عودة الموقع تعني فقط أن مرحلة الطوارئ انتهت.
الآن تبدأ مرحلة معرفة لماذا حدثت المشكلة.
تشريح الأسباب الخمسة
| الخطأ | ما الذي حدث؟ | الإصلاح الدائم |
|---|---|---|
| RAM بلا Headroom | Peak تجاوزت الذاكرة المتاحة | Capacity Planning + هامش نمو |
| Query بلا Index مناسبة | Database CPU ووقت الاستجابة ارتفعا | Slow Query Analysis + Indexing |
| Workers أكثر من قدرة Server | PHP استهلكت الذاكرة بسرعة | حساب Worker Memory واختبار Load |
| Backup وقت Peak | CPU وDisk وDatabase تحملت عملًا إضافيًا | Scheduling أو فصل Jobs الثقيلة |
| لا توجد Alerts مبكرة | العميل اكتشف المشكلة قبل الفريق | Monitoring + Thresholds + External Checks |
كم كلفت 35 دقيقة من التوقف؟
لنستخدم مثالًا افتراضيًا فقط.
إذا كان المتجر يحقق أثناء الحملة:
120 طلبًا في الساعة
ومتوسط الطلب:
35 ريال
فالإيراد النظري في الساعة:
120 × 35
=
4,200 ريال
إذا تعطلت تجربة الشراء بصورة كبيرة لمدة 35 دقيقة، لا يعني ذلك أن كل هذا المبلغ ضاع حرفيًا.
بعض العملاء سيعودون، وبعض الطلبات ستتم لاحقًا.
لكن الخسارة لا تتكون من المبيعات الفورية فقط.
هناك أيضًا:
- ميزانية إعلانات استمرت في جلب زوار لا يستطيعون الشراء.
- طلبات دعم إضافية.
- Cart Abandonment.
- عملاء فقدوا الثقة.
- وقت فريق كامل في Incident.
- احتمال مشاكل دفع تحتاج مراجعة يدوية.
ولهذا قد تتحول دقائق توقف قليلة أثناء Peak إلى تكلفة كبيرة جدًا.
لكن شراء Server بضعف الحجم ليس الدرس
من السهل قراءة القصة والوصول إلى:
«إذًا نشتري 16GB RAM وتنتهي المشكلة.»
هذا استنتاج ناقص.
لو نقلنا نفس Query السيئة، ونفس Backup، ونفس إعداد Workers، ونفس غياب Monitoring إلى Server أكبر، فنحن غالبًا اشترينا وقتًا فقط.
بعد نمو المتجر ستعود المشكلة.
الحل المستقبلي بدأ من أربعة أسئلة
قبل اختيار Server جديدة، تم حساب:
1. ما Peak RAM الحقيقية؟
2. كم Concurrent Request نحتاج؟
3. ما الموارد التي تحتاجها Database؟
4. ما Headroom التي نحتاجها للحمل والنمو؟
ثم أضيفت خطة تشغيل:
Monitoring
+
Alerts
+
Backup schedule
+
Database maintenance
+
Load testing
هذه هي النقطة التي تتحول فيها الاستضافة من:
«Server عليها موقع»
إلى بنية تستطيع الوثوق بها وقت الضغط.
ما الذي تم تغييره قبل الجمعة التالية؟
لم يكن التغيير واحدًا.
تم بناء قائمة واضحة:
- رفع الموارد مع Headroom محسوبة.
- مراجعة Slow Queries وإضافة Indexes المطلوبة.
- إعادة ضبط PHP-FPM بناءً على استهلاك Worker الفعلي.
- نقل Backup إلى فترة أقل ازدحامًا.
- إضافة Monitoring وAlerts خارجية.
- اختبار Checkout تحت Load قبل الحملة.
- توثيق خطوات Incident والاسترجاع.
اختبار الضغط قبل الحملة أرخص من اختباره بالعملاء
إذا كنت تتوقع حملة كبيرة ليلة الجمعة، لا تنتظر ليلة الجمعة حتى تعرف قدرة Server.
قم بتجربة Load مسبقًا في بيئة مناسبة.
لا يكفي اختبار Homepage فقط.
المسار المهم في متجر قد يكون:
Homepage
↓
Category
↓
Product
↓
Add to Cart
↓
Cart
↓
Checkout
↓
Payment initialization
قد تكون Homepage سريعة بفضل Cache بينما Checkout بطيئة بسبب Database.
راقب رحلة العميل، لا Ping فقط
Server قد ترد:
HTTP 200
لكن Checkout تستغرق 12 ثانية.
من الناحية التقنية:
«الموقع شغال.»
من ناحية العميل:
المتجر متوقف تقريبًا.
لهذا راقب أيضًا:
P95 Response Time
Checkout latency
Error rate
Database latency
Queue depth
Payment failures
لو رأيت RAM 95%، لا تقفز فورًا إلى الترقية
اسأل أولًا:
من يستخدمها؟
هل هي Application Memory؟
Database Cache؟
Linux Buffers/Cache؟
Memory Leak؟
عدد Workers؟
Container limit؟
ثم اتخذ القرار بناءً على السبب.
ولو رأيت Database CPU 100%؟
اسأل:
ما Slow Queries؟
هل توجد Full Table Scans؟
هل Indexes مناسبة؟
هل Connections كثيرة؟
هل Queries تتكرر بلا Cache؟
هل التطبيق يرسل نفس Query عدة مرات لكل Page؟
الـMonitoring تخبرك أين الألم.
التحليل يخبرك لماذا.
أفضل Postmortem لا يبحث عن شخص نلومه
السؤال غير المفيد:
«من اختار Server 4GB؟»
السؤال الأفضل:
«لماذا لم يكن لدينا نظام يخبرنا أن 4GB لم تعد تكفي قبل ليلة الحملة؟»
الأنظمة تنمو.
عدد الطلبات يتغير.
Database تكبر.
Plugins تتغير.
ولذلك مواصفات كانت صحيحة قبل ستة أشهر قد لا تكون صحيحة اليوم.
قبل الحملة القادمة
إذا كان متجرك يعتمد على VPS، لا تنتظر Peak لتعرف هل Server مناسبة.
راجع:
Current RAM
Peak RAM
Available RAM
Swap
CPU peak
Load Average
P95 response time
Database slow queries
Workers
Disk I/O
Backup schedule
Monitoring
Alerts
ثم حدد الموارد بناءً على هذه الأرقام.
إذا كنت تخطط لنقل متجر إلى VPS أو تجهيز Server جديدة، فباقات VPS Linux في روافد تتيح لك اختيار الموارد المناسبة لحجم مشروعك، لكن الأهم قبل اختيار الباقة هو معرفة الحمل الذي تحتاج إلى تشغيله فعليًا.
Server الصحيحة ليست التي تحمل أكبر رقم RAM.
هي التي تم اختيارها وتهيئتها ومراقبتها بحيث لا تكون ليلة الجمعة هي أول اختبار حقيقي لها.
لا تجعل وقت الذروة أول اختبار حقيقي لسيرفرك
إذا كنت تجهز متجرًا أو تنقل مشروعًا إلى VPS، احسب Peak والـHeadroom أولًا ثم اختر الموارد المناسبة. يمكنك الاطلاع على باقات VPS Linux في روافد ومقارنة الموارد مع حمل مشروعك الفعلي.