لماذا تبطؤ قاعدة البيانات مع زيادة عدد السجلات؟
عالم البرمجة
قد يعمل التطبيق بسرعة ممتازة في بدايته، ثم تبدأ بعض الصفحات والاستعلامات في التباطؤ بعد أشهر
من الاستخدام.
يظهر الأمر كأن قاعدة البيانات «تعبت» لأن الجدول أصبح يضم مئات الآلاف أو ملايين الصفوف، فيكون
أول اقتراح هو زيادة الذاكرة أو نقل الخادم إلى فئة أغلى.
لكن عدد السجلات وحده لا يفسر الأداء.
جدول ضخم قد يجيب عن استعلام محدد بسرعة، بينما جدول أصغر قد يعاني من استعلام سيئ أو خطة تنفيذ غير مناسبة.
ما يتغير مع نمو البيانات هو تكلفة الطرق المختلفة للوصول إلى الصفوف، وحجم ما يجب قراءته أو فرزه
أو ربطه، ودقة تقديرات المحسن، والضغط المتزامن على الموارد.
لذلك لا تبدأ بسؤال: كم أصبح عدد الصفوف؟ ابدأ بسؤال أدق: ما الخطة التي ينفذها المحرك لهذا الاستعلام الآن، وكم صفًا يقرأ للوصول إلى النتيجة التي يحتاجها التطبيق؟
الحجم يكشف المشكلة ولا يصنعها وحده
الاستعلام الذي يبدو سريعًا على ألف صف قد يكون ببساطة صغيرًا بما يكفي لإخفاء كلفته.
إذا كان المحرك يقرأ جزءًا كبيرًا من الجدول كل مرة، فلن تشعر بالمشكلة مبكرًا، لكن الكلفة تصبح أوضح عندما يتضاعف الحجم.
وفي المقابل، ليس كل مسح للجدول خطأ.
إذا كان الاستعلام يحتاج نسبة كبيرة من الصفوف، فقد يقرر المحسن أن القراءة المتسلسلة
أرخص من المرور عبر فهرس ثم العودة إلى الصفوف واحدًا واحدًا.
الفهرس ليس هدفًا بحد ذاته؛ الهدف هو أقل خطة كلفة للمهمة الفعلية.
لهذا لا تحكم على الاستعلام من اسمه ولا من عدد الصفوف فقط.
انظر إلى خطة التنفيذ والصفوف المقدرة والمقروءة فعليًا.
ابدأ بخطة التنفيذ
أهم أداة قبل إضافة خادم أو فهرس جديد هي EXPLAIN أو ما يعادلها في محركك.
خطة التنفيذ تكشف كيف ينوي المحرك الوصول إلى البيانات: مسح متسلسل، فحص فهرس، Bitmap، ترتيب، تجميع، أو نوع من أنواع الربط بين الجداول.
وعندما يكون المحرك يدعم EXPLAIN ANALYZE أو أداة مشابهة، تستطيع مقارنة التقديرات
بما حدث فعلًا أثناء التنفيذ.
هذه المقارنة مهمة لأن الخطة قد تكون منطقية وفق الإحصاءات، ثم يتبين أن عدد الصفوف الحقيقي مختلف كثيرًا عن تقدير المحسن.
لا تكتفِ بعبارة «الاستعلام يستخدم فهرسًا».
اسأل: كم صفًا قرأ؟ كم صفًا أعاد؟ أين ذهب الوقت؟ وهل ظهرت عملية فرز أو تجميع أو قراءة واسعة
لم تكن متوقعة؟
الفهرس ليس مفتاح سرعة دائمًا
الفهرس يستطيع تقليل كمية البيانات التي يحتاج المحرك إلى فحصها عندما يكون الشرط انتقائيًا ومناسبًا لبنية الفهرس.
لكنه يضيف أيضًا تكلفة عند الإدخال والتحديث والحذف، لأن المحرك يحتاج إلى إبقاء الفهرس متزامنًا
مع البيانات.
لهذا إضافة فهرس لكل عمود ليست استراتيجية جيدة.
قد تحصل على قراءة أسرع في استعلام نادر، مقابل كتابة أبطأ ومساحة أكبر وصيانة أكثر في جدول كثيف التحديث.
ابدأ من الاستعلامات المهمة فعلًا.
راقب شروط WHERE والربط والترتيب، ثم صمم الفهرس الذي يخدم نمط الوصول المتكرر.
وبعد إضافته، عد إلى خطة التنفيذ وتحقق أن المحسن يستخدمه عندما تكون كلفته أفضل.
الفهرس قرار مبني على عبء العمل، لا زينة ثابتة للجداول.
ترتيب أعمدة الفهرس المركب مهم
وجود فهرس على الأعمدة الصحيحة لا يعني أنه سيخدم كل ترتيب ممكن من الشروط بالكفاءة نفسها.
في الفهارس متعددة الأعمدة، ترتيب المفاتيح يؤثر في الطريقة التي يستطيع المحرك الاستفادة
بها من الفهرس.
إذا كان التطبيق يبحث غالبًا عن طلبات عميل محدد ضمن حالة أو نطاق زمني، فالفهرس ينبغي أن يُصمم حول الاستعلامات الفعلية، لا حول قائمة الأعمدة التي تبدو مهمة للمطور.
ولا تفترض أن فهرسين منفصلين يساويان دائمًا فهرسًا مركبًا، أو أن الفهرس المركب هو الأفضل
في كل الحالات.
بعض المحركات تستطيع دمج عدة فهارس في خطط معينة، وقد تختار مسارًا مختلفًا حسب الانتقائية والتكلفة.
اختبر الخطة بدل حفظ قاعدة واحدة وتطبيقها على كل جدول.
الإحصاءات قد تغيّر الخطة
محسن الاستعلام لا يعرف المستقبل ولا يقرأ كل الصفوف قبل اتخاذ القرار.
يعتمد على إحصاءات وتقديرات تساعده على توقع عدد الصفوف التي ستطابق الشروط وتكلفة المسارات المتاحة.
مع تغير توزيع البيانات قد تصبح تقديرات قديمة أو غير كافية سببًا في اختيار خطة أضعف.
هذا مهم خصوصًا عندما تكون بعض القيم شائعة جدًا وأخرى نادرة، أو عندما توجد علاقة قوية
بين أكثر من عمود.
إذا ظهرت خطة غريبة بعد نمو الجدول، لا تفرض فهرسًا مباشرة.
افحص الإحصاءات، وقارن عدد الصفوف المقدر بالفعلي، واعرف كيف يقوم محركك بتحديث إحصاءاته.
الخطة السيئة قد تكون مشكلة معلومات قبل أن تكون مشكلة موارد.
وقد يتغير القرار نفسه بعد نمو البيانات، لأن نسبة الصفوف المطابقة وتوزيع القيم قد تتغير
حتى لو بقي نص الاستعلام كما هو.
لا تفترض أن القرص هو عنق الزجاجة
الملف الأصلي يجعل التخزين السبب الأساسي للبطء، لكن قواعد البيانات الحديثة تستفيد من الذاكرة المؤقتة وأنظمة التشغيل ووسائط التخزين السريعة، كما أن الاستعلام قد يصبح محدودًا بالمعالج
أو الذاكرة أو الشبكة أو التزامن بدل القراءة من القرص.
قد يكون الاستعلام بطيئًا لأنه يفرز ملايين الصفوف، أو ينفذ تجميعًا كبيرًا، أو يربط جداول بطريقة مكلفة،
أو يعيد بيانات أكثر مما يحتاج التطبيق.
وقد تكون الصفحات المطلوبة موجودة أصلًا في الذاكرة.
لذلك راقب المؤشرات الفعلية: زمن المعالج، القراءة، الكتابة، الذاكرة، الانتظار، وحجم البيانات المنقولة.
لا تشتري قرصًا أسرع قبل أن تثبت أن التخزين هو العنق الحقيقي.
الصفوف العريضة ترفع تكلفة القراءة
كلما كان الصف يحتوي أعمدة كثيرة أو بيانات نصية كبيرة، زادت كمية البيانات التي قد يحتاج المحرك
إلى التعامل معها عند قراءة عدد كبير من الصفوف.
ولهذا فإن SELECT * قد يكون مكلفًا إذا كان التطبيق لا يحتاج معظم الأعمدة، خصوصًا مع نتائج كبيرة.
لكن SELECT * ليس «كارثة» تلقائية.
إذا كنت تجلب صفًا واحدًا صغيرًا، فالفرق قد يكون غير مهم.
المشكلة تظهر عندما تتكرر القراءة على نطاق واسع أو تنتقل بيانات كبيرة من قاعدة البيانات إلى التطبيق بلا حاجة.
اطلب الأعمدة التي يستخدمها المسار الفعلي، وضع حدودًا معقولة للنتائج، وافحص أحجام الاستجابات
بدل استعمال قاعدة جامدة.
التحسين الجيد يرتبط بما يستهلك موارد فعلًا.
الفرز والربط قد يكونان أبطأ من البحث
أحيانًا يكون العثور على الصفوف سهلًا، لكن ORDER BY أو GROUP BY أو JOIN هو الجزء المكلف.
قد يحتاج المحرك إلى فرز مجموعة كبيرة أو إنشاء بنية مؤقتة أو المرور على عدد كبير من المطابقات
قبل إخراج النتيجة النهائية.
الفهرس المناسب قد يساعد في بعض عمليات الترتيب أو الربط، لكنه لا يلغي كل عمليات الفرز ولا يجعل
كل JOIN سريعًا.
اقرأ ايضا : كيف تكتب اختبارات برمجية تكشف الأعطال مبكرًا؟
نوع الربط الذي يختاره المحسن يعتمد على حجم المدخلات والتقديرات والفهارس والذاكرة المتاحة.
عندما تقرأ الخطة، لا تتوقف عند أول Scan.
تابع الشجرة حتى تعرف أين تتضخم الصفوف وأين تظهر أكبر كلفة.
الاستعلام البطيء قد يبدأ بسرعة ثم يتباطأ عند مرحلة لاحقة.
التصفح نفسه قد يزداد تكلفة
صفحات النتائج الكبيرة تحتاج استراتيجية واضحة.
استخدام OFFSET بقيم مرتفعة قد يجعل بعض الأنظمة تعمل على تجاوز عدد متزايد من الصفوف
قبل الوصول إلى الصفحة المطلوبة، حتى لو كان المستخدم لا يرى تلك الصفوف.
في المسارات التي تعرض تسلسلًا مستمرًا من النتائج، قد يكون التصفح القائم على مفتاح ثابت أو آخر قيمة شوهدت مناسبًا أكثر من القفز إلى OFFSET بعيد، إذا كان تصميم المنتج يسمح بذلك.
هذا ليس بديلًا عامًا لكل Pagination.
بعض الواجهات تحتاج الانتقال المباشر إلى رقم صفحة، وبعض التقارير تعمل بطريقة مختلفة.
المهم أن تختبر تكلفة طريقة التصفح مع حجم بيانات قريب من الإنتاج، لا مع عشرات الصفوف في بيئة التطوير.
التزامن يغيّر الصورة
قد يكون الاستعلام سريعًا عندما تختبره منفردًا، ثم يصبح التطبيق بطيئًا عند وجود عشرات الطلبات والعمليات المتزامنة.
هنا تدخل عوامل مثل انتظار الأقفال، طول المعاملات، عدد الاتصالات، وتنافس الجلسات على المعالج والذاكرة والتخزين.
ولا يصح القول إن كل قراءة تمنع الكتابة أو إن كل تحديث يقفل الجدول.
سلوك التزامن يختلف حسب المحرك ونوع العملية ومستوى العزل وآلية التحكم بالتزامن.
راقب وقت الانتظار لا زمن التنفيذ وحده.
قد ترى طلبًا استغرق ثانيتين، بينما الجزء الأكبر منه كان انتظار معاملة أخرى لا تنفيذ الاستعلام نفسه.
اجعل المعاملات قصيرة قدر الإمكان بما يحافظ على صحة العملية، وتجنب إبقاء معاملة مفتوحة أثناء أعمال لا تحتاج قاعدة البيانات.
الترقية قد تساعد لكنها لا تصلح الخطة
زيادة الذاكرة أو المعالج أو سرعة التخزين ليست دائمًا هدرًا.
إذا كان النظام يستخدم موارده بكفاءة وأصبح الحمل أكبر فعلًا، فالترقية قد تكون قرارًا صحيحًا.
المشكلة هي استعمالها لتعويض استعلام يقرأ ملايين الصفوف بلا حاجة أو خطة تعتمد على تقديرات خاطئة.
موارد أكبر قد تؤخر ظهور المشكلة، لكنها لا تغير طبيعة العمل الذي يطلبه الاستعلام.
قارن قبل الترقية: هل زمن الاستعلام يرتبط باستهلاك مورد وصل إلى حد واضح؟ وهل خطة التنفيذ معقولة؟ وهل الاختبار تحت حمل واقعي؟
التوسع الرأسي أداة هندسية، وليس اعترافًا بالفشل، لكنه يأتي بعد فهم السبب.
التقسيم ليس زر تسريع
Partitioning مفيد عندما يتوافق مفتاح التقسيم مع طريقة الوصول إلى البيانات ويمكن للمحرك استبعاد أجزاء لا يحتاجها الاستعلام.
مثال واضح هو بيانات زمنية تُسأل غالبًا ضمن نطاقات محددة.
لكن تقسيم جدول كبير لا يجعل كل استعلام أسرع.
إذا كان الاستعلام يحتاج معظم الأجزاء، أو كان مفتاح التقسيم لا يظهر في الفلاتر المهمة، فقد لا تحصل على الفائدة التي تتوقعها، وتضيف بدلًا منها تعقيدًا تشغيليًا وإداريًا.
استخدم التقسيم عندما تحل به مشكلة واضحة في الوصول أو الإدارة أو دورة حياة البيانات، لا لأن الجدول تجاوز رقمًا معينًا من الصفوف.
عدد الصفوف ليس عتبة سحرية تستدعي Partitioning.
الأرشفة والكاش يعالجان أنماطًا محددة
نقل البيانات القديمة إلى أرشيف قد يكون مناسبًا إذا كانت متطلبات الوصول والاحتفاظ تسمح
بذلك، لكنه ليس علاجًا عامًا للبطء.
أحيانًا تحتاج البيانات التاريخية داخل الاستعلامات اليومية، وأحيانًا يصبح فصلها عبئًا على التقارير والتشغيل.
والتخزين المؤقت قد يقلل الاستعلامات المتكررة، لكنه يضيف أسئلة عن انتهاء الصلاحية وتحديث القيم واتساق البيانات.
لا تضع Cache أمام استعلام سيئ ثم تعتبر المشكلة محلولة.
حسّن المسار الأساسي أولًا، ثم استخدم الكاش عندما توجد قراءة متكررة وقيمة يمكن إعادة استخدامها بأمان لمدة معروفة.
كل طبقة تحسين لها تكلفة تشغيلية ينبغي أن تبررها فائدة قابلة للقياس.
اختبر ببيانات تشبه الإنتاج
من أكثر أسباب المفاجأة أن بيئة التطوير تحتوي مئات الصفوف وتوزيعًا نظيفًا، بينما الإنتاج يحتوي ملايين الصفوف وقيمًا غير متوازنة وحملًا متزامنًا.
لا تحتاج إلى نسخ بيانات حساسة إلى جهاز المطور.
أنشئ بيانات اختبار آمنة تحاكي الحجم والتوزيع والعلاقات المهمة، أو استخدم بيئة اختبار مخصصة
وفق سياسات الأمان والخصوصية لديك.
قِس الاستعلامات المهمة مع بيانات كافية لكشف تغير الخطة، واختبر السيناريوهات الشائعة والأسوأ المتوقع بصورة معقولة.
الأداء الذي لم يُختبر مع الحجم ليس ضمانًا بأنه سيبقى كما هو بعد النمو.
استخدم خريطة تشخيص بطء البيانات
عندما يصبح استعلام أبطأ مع نمو الجدول، استخدم «خريطة تشخيص بطء البيانات» من سبع نقاط.
الأولى: الخطة، ماذا ينفذ المحرك فعلًا؟ الثانية: الانتقائية، كم صفًا يفحص وكم صفًا يعيد؟ الثالثة: الفهارس، هل تخدم WHERE وJOIN وORDER BY المهمين؟ الرابعة: الإحصاءات، هل التقديرات قريبة من الواقع؟
الخامسة: الكلفة، هل المشكلة قراءة أم فرزًا أم تجميعًا أم شبكة؟ السادسة: التزامن، هل يوجد انتظار أقفال أو تنافس موارد؟ السابعة: المعمار، هل تحتاج فعلًا إلى Partitioning أو Cache أو أرشفة بعد تحسين المسار الأساسي؟
سجل قبل وبعد كل تعديل.
لا تضف فهرسين وترفع الذاكرة وتغير الاستعلام في الوقت نفسه ثم تحاول معرفة أي خطوة صنعت الفرق.
أصلح أبطأ استعلام قابل للإثبات
ابدأ من القياس لا من أكبر جدول.
ابحث عن الاستعلامات الأعلى زمنًا أو الأكثر استهلاكًا أو الأكثر تكرارًا في مسار مهم للمستخدم،
ثم اختر واحدًا يمكن قياسه.
احفظ خطة التنفيذ وزمنه والصفوف التي يفحصها ويعيدها.
غيّر عنصرًا واحدًا: صياغة شرط، فهرس، أعمدة مطلوبة، إحصاءات، أو طريقة Pagination، ثم أعد القياس تحت ظروف قريبة قدر الإمكان.
إذا تحسن، لديك دليل.
اقرأ ايضا : كيف تختار بنية مشروع تتحمل نمو التطبيق؟
وإذا لم يتحسن، تراجعت عن فرضية واحدة من دون تغيير بنية النظام كلها.
قاعدة البيانات لا تصبح بطيئة لمجرد أن عداد السجلات ارتفع.
النمو يجعل تكلفة القرارات القديمة أكثر وضوحًا.
عندما تقرأ خطة التنفيذ وتفهم أين يذهب العمل الفعلي، تتحول مشكلة «الملايين من الصفوف»
من خوف عام إلى مجموعة اختبارات وقرارات هندسية قابلة للقياس.
