كيف تسبب عمود غير مفهرس في تدمير قاعدة بياناتنا

فهرس واحد مفقود حوّل استجابة الـ API من 8 مللي ثانية إلى كابوس استغرق 8 ثوانٍ.

أجرينا اختبار حمل (load test) قبل إصدار رئيسي. تعاملت بيئة التطوير المحلية مع 100 صف دون مشاكل، ولكن في بيئة الاختبار (staging)، قمنا بمحاكاة 500 مستخدم مقابل 1.5 مليون صف.

انهار كل شيء.

  • قفزت أوقات استجابة الـ API من 45 مللي ثانية إلى 8,000 مللي ثانية.
  • وصل استهلاك المعالج (CPU) لقاعدة البيانات إلى 100%.
  • استنفدت مجموعات الاتصال (Connection pools).
  • انتهت مهلة الطلبات (Requests timed out).

كان المسبب استعلاماً بسيطاً يجلب سجل الطلبات عبر user_id و status.

أظهر تشغيل EXPLAIN ANALYZE على PostgreSQL وجود مسح تسلسلي (Sequential Scan). وبسبب عدم وجود فهرس على user_id ، اضطر المحرك لقراءة جميع الـ 1.5 مليون صف مع كل طلب.

عند وجود 100 طلب متزامن، قامت قاعدة البيانات بمسح 150 مليون صف دفعة واحدة.

استغرق الإصلاح خمس دقائق. قمنا بإنشاء فهرس مركب (composite index) على (user_id, status, created_at DESC) باستخدام CREATE INDEX CONCURRENTLY لضمان بقاء الجدول متاحاً (online).

النتائج:

  • انخفض وقت الاستعلام من 8,150 مللي ثانية إلى 0.14 مللي ثانية.
  • انخفض زمن انتقال الـ API (latency) من 8 ثوانٍ إلى 12 مللي ثانية.
  • انخفض استهلاك المعالج من 100% إلى أقل من 8%.

الدروس المستفادة:

  • الاختبارات المحلية قد تكون مضللة؛ فـ 100 صف ليست بديلاً عن مليون صف.
  • قم بفهرسة المفاتيح الخارجية (foreign keys) — فمعظم الـ ORMs تتجاهل ذلك.
  • استخدم EXPLAIN ANALYZE؛ فالمحرك سيشير إلى المشكلة.
  • قم بتحسين الاستعلامات قبل شراء المزيد من الأجهزة (hardware).

لا تقم بتوسيع الخوادم أولاً. بل قم بتحسين الاستعلامات.

المصدر: https://dev.to/mia_keller_ffd2584c046ecb/how-an-unindexed-column-silently-killed-our-database-under-load-and-the-5-minute-fix-m32