تحليل عميق لتدهور أداء MySQL 8.0

يميل المستخدمون إلى ملاحظة تراجع الأداء في الحالات ذات التزامن المنخفض بسهولة أكبر، بينما يكون من الصعب غالبًا إدراك التحسينات في الأداء في الحالات ذات التزامن العالي. لذلك، فإن الحفاظ على أداء التزامن المنخفض أمر بالغ الأهمية، حيث يؤثر بشكل مباشر على تجربة المستخدم واستعداده للترقية [1].

وفقًا لتغذية راجعة واسعة من المستخدمين، بعد الترقية إلى MySQL 8.0، لاحظ المستخدمون عمومًا تراجعًا في الأداء، وخاصة في عمليات الإدراج الجماعي وعمليات الانضمام. لقد أصبح هذا الاتجاه التنازلي أكثر وضوحًا في الإصدارات الأعلى من MySQL. بالإضافة إلى ذلك، أبلغ بعض عشاق MySQL والمختبرين عن تدهور الأداء في عدة اختبارات sysbench بعد الترقية.

هل يمكن تجنب هذه المشكلات في الأداء؟ أو، بشكل أكثر تحديدًا، كيف ينبغي علينا تقييم الاتجاه المستمر لتراجع الأداء بشكل علمي؟ هذه أسئلة مهمة يجب مراعاتها.

على الرغم من أن الفريق الرسمي يواصل تحسين الأمور، لا يمكن تجاهل التدهور التدريجي في الأداء. في بعض السيناريوهات، قد يبدو أن هناك تحسنًا، لكن هذا لا يعني أن الأداء في جميع السيناريوهات قد تم تحسينه بشكل متساوٍ. علاوة على ذلك، من السهل أيضًا تحسين الأداء لسيناريوهات محددة على حساب تدهور الأداء في مجالات أخرى.

الأسباب الجذرية لتراجع أداء MySQL

عمومًا، مع إضافة المزيد من الميزات، ينمو قاعدة الشيفرة، ومع التوسع المستمر في الوظائف، يصبح من الصعب بشكل متزايد السيطرة على الأداء.

يفشل مطورو MySQL في كثير من الأحيان في ملاحظة تراجع الأداء، حيث يؤدي كل إضافة جديدة إلى قاعدة الشفرة إلى انخفاض بسيط جدًا في الأداء. ومع ذلك، مع مرور الوقت، تتراكم هذه الانخفاضات البسيطة، مما يؤدي إلى تأثير تراكمي كبير، والذي يجعل المستخدمين يلاحظون تدهورًا ملحوظًا في الأداء في الإصدارات الأحدث من MySQL.

على سبيل المثال، تُظهر الشكل التالي أداء عملية الوصلة الفردية البسيطة، حيث يظهر انخفاض في الأداء في MySQL 8.0.40 مقارنة بـ MySQL 8.0.27:

Figure 1. Significant decline in join performance in MySQL 8.0.40.

ويُظهر الشكل التالي اختبار أداء إدخال الدفعات تحت التوازن الفردي، مع انخفاض في الأداء في MySQL 8.0.40 مقارنة بالإصدار 5.7.44:

Figure 2. Significant decline in bulk insert performance in MySQL 8.0.40.

من الرسمين أعلاه، يُمكن رؤية أن أداء الإصدار 8.0.40 ليس جيدًا.

الآن، دعونا نحلل سبب تدهور الأداء في MySQL من مستوى الشفرة. أدناه وظيفة PT_insert_values_list::contextualize في MySQL 8.0:

C++

 

وتكون الوظيفة المقابلة PT_insert_values_list::contextualize في MySQL 5.7 كما يلي:

C++

 

من خلال مقارنة الشفرات، يبدو أن MySQL 8.0 لديها شفرة أكثر أناقة، ويبدو أنها تحقق تقدمًا.

للأسف، في كثير من الأحيان، يكون من دوافع هذه التحسينات في الشفرة هي التي تؤدي بالضبط إلى تدهور الأداء. فريق MySQL الرسمي قام بتبديل الهيكل البياني السابق List بـ deque، والذي أصبح واحدًا من جذور تدهور الأداء التدريجي. دعونا نلقي نظرة على وثائق deque:

Markdown

 

كما هو موضح في الوصف أعلاه، في حالات متطرفة، يتطلب الاحتفاظ بعنصر واحد تخصيص الصف الكامل، مما يؤدي إلى كفاءة ذاكرة منخفضة جدًا. على سبيل المثال، في إدراجات الجملة، حيث يحتاج إلى إدراج عدد كبير من السجلات، يقوم التنفيذ الرسمي بتخزين كل سجل في صف ديك مختلف. حتى إذا كان محتوى السجل بحد أدنى، يجب لا يزال تخصيص ديك. تقوم تنفيذية MySQL ديك بتخصيص 1KB من الذاكرة لكل ديك لدعم البحث السريع.

Plain Text

 

تستخدم التنفيذة الرسمية 1KB من الذاكرة لتخزين معلومات الفهرس، وحتى إذا لم يكن طول السجل كبيرًا ولكن هناك العديد من السجلات، فإن عناوين وصول الذاكرة قد تصبح غير متتالية، مما يؤدي إلى عدم ودية الكاش. كان هذا التصميم مقصودًا لتحسين ودية الكاش، ولكنه لم يكن فعالًا بشكل كامل.

من الجدير بالذكر أن التنفيذ الأصلي كان يستخدم بنية بيانات List، حيث كانت تتم تخصيص الذاكرة من خلال حوض ذاكرة، مما يوفر مستوى معين من ودية الكاش. على الرغم من أن الوصول العشوائي أقل كفاءة، إلا أن الأمر يتحسن بشكل كبير للوصول التسلسلي إلى عناصر List مما يحسن أداء النظام بشكل كبير.

خلال الترقية إلى MySQL 8.0، لاحظ المستخدمون انخفاضًا كبيرًا في أداء إدراج الدفعات، وكان أحد الأسباب الرئيسية هو التغيير الكبير في البنية الداخلية للبيانات.

بالإضافة إلى ذلك، بينما قام الفريق الرسمي بتحسين آلية سجل إعادة القيد، فقد أدى ذلك أيضًا إلى انخفاض كفاءة عملية تأكيد MTR. مقارنة ب MySQL 5.7، يقلل الكود الإضافي بشكل كبير من أداء العمليات الفردية، على الرغم من تحسين إجمالي معدل كتابة البيانات بشكل كبير.

لنفحص العملية الأساسية execute لعملية تأكيد MTR في MySQL 5.7.44:

C++

 

لنفحص عملية execute الأساسية للتأكد من التعهد MTR في MySQL 8.0.40:

C++

 

من المقارنة، يتضح أن في MySQL 8.0.40، أصبحت عملية التنفيذ في التأكد MTR أكثر تعقيدًا بكثير، مع مزيد من الخطوات المتضمنة. هذا التعقيد هو أحد الأسباب الرئيسية لانخفاض أداء الكتابة عند القليل من التنافر.

بشكل خاص، العمليات m_impl->m_log.for_each_block(write_log) و log_wait_for_space_in_log_recent_closed(*log_sys, handle.start_lsn) تحملان عبء كبير. تمت إجراء هذه التغييرات لتعزيز أداء التنافر العالي، ولكن جاءت على حساب أداء التنافر القليل.

أولوية سجل الإعادة (redo log) لوضعية التنافر العالي تؤدي إلى أداء ضعيف لأعباء العمل التنافر القليل. على الرغم من أن إدخال innodb_log_writer_threads كان يهدف إلى التخفيف من مشاكل أداء التنافر القليل، إلا أنه لا يؤثر على تنفيذ الوظائف المذكورة أعلاه. نظرًا لأن هذه العمليات أصبحت أكثر تعقيدًا وتتطلب التأكد المتكرر من MTR، فإن الأداء قد انخفض بشكل كبير.

دعونا نلقي نظرة على تأثير ميزة الإضافة/الإسقاط الفوري على الأداء. فيما يلي وظيفة rec_init_offsets_comp_ordinary في MySQL 5.7:

C++

 

وظيفة rec_init_offsets_comp_ordinary في MySQL 8.0.40 كما يلي:

C++

 

من الشيفرة أعلاه، يتضح أن مع إدخال ميزة العمود الجديدة الفورية / الإسقاط، أصبحت وظيفة rec_init_offsets_comp_ordinary أكثر تعقيدًا بشكل ملحوظ، مما يقدم المزيد من استدعاءات الوظائف وإضافة بيان switch الذي يؤثر بشكل كبير على تحسين ذاكرة الكاش. نظرًا لتكرار استدعاء هذه الوظيفة، فإن ذلك يؤثر مباشرة على أداء تحديث الفهرس، وإدراج الدفعات، والانضمام، مما يؤدي إلى تأثير كبير على الأداء.

علاوة على ذلك، فان الانخفاض في الأداء في MySQL 8.0 ليس مقتصرًا على ما سبق؛ بل هناك العديد من المجالات الأخرى التي تسهم في تدهور الأداء الشامل، خاصة التأثير على توسيع الوظائف الداخلية. على سبيل المثال، يؤثر الشيفرة التالية على توسيع الوظائف الداخلية:

C++

 

ووفقًا لاختباراتنا، فإن بيان ib::fatal يتداخل بشكل كبير مع تحسين الوظائف الداخلية. بالنسبة للوظائف التي يتم الوصول إليها بشكل متكرر، من النصيحة تجنب البيانات التي تتداخل مع تحسين الوظائف الداخلية.

الآن، دعونا نلقي نظرة على قضية مماثلة. تُستدعى وظيفة row_sel_store_mysql_field بشكل متكرر، حيث تعد row_sel_field_store_in_mysql_format وظيفة ساخنة ضمنها. الشيفرة المحددة هي كما يلي:

C++

 

تستدعي وظيفة row_sel_field_store_in_mysql_format في النهاية row_sel_field_store_in_mysql_format_func.

C++

 

لا يمكن تضمين وظيفة row_sel_field_store_in_mysql_format_func بسبب وجود الشيفرة ib::fatal .

C++

 

الوظائف غير الفعالة التي تُستدعى بشكل متكرر، وتنفذ عشرات الملايين من المرات في الثانية، يمكن أن تؤثر بشكل كبير على أداء الانضمام.

لنواصل استكشاف أسباب انخفاض الأداء. في الواقع، الأداء الرسمي الأمثل هو واحد من جذور انخفاض أداء الانضمام. على الرغم من أنه قد يتم تحسين بعض الاستعلامات، إلا أنها لا تزال بعض الأسباب لتدهور الأداء لعمليات الانضمام العادية.

GitHub Flavored Markdown

 

تتجاوز مشاكل MySQL هذه. كما هو موضح في التحاليل أعلاه، انخفاض الأداء في MySQL ليس بدون سبب. سلسلة من المشاكل الصغيرة، عند تراكمها، يمكن أن تؤدي إلى تدهور الأداء الملحوظ الذي يواجهه المستخدمون. ومع ذلك، غالبًا ما تكون هذه المشاكل صعبة التحديد، مما يجعل من الصعب حتى حلها.

ما يسمى ‘التحسين المبكر’ هو جذر كل شر، وهذا لا ينطبق في تطوير MySQL. تطوير قواعد البيانات هو عملية معقدة، وتجاهل الأداء مع مرور الوقت يجعل تحسينات الأداء اللاحقة أكثر تحديًا بشكل كبير.

حلول للتخفيف من انخفاض أداء MySQL

الأسباب الرئيسية للانخفاض في أداء الكتابة تتعلق بمشاكل التأكيد MTR، إضافة/إسقاط الأعمدة فورًا، وعوامل أخرى عدة. هذه من الصعب تحسينها بالطرق التقليدية. ومع ذلك، يمكن للمستخدمين تعويض انخفاض الأداء من خلال تحسين PGO. باستراتيجية مناسبة، يمكن الحفاظ على الأداء بشكل عام.

بالنسبة لتدهور أداء إدراج الدُفعة، تقوم النسخة مفتوحة المصدر لدينا [2] بتبديل الdeque الرسمي بتنفيذ قائمة محسن. يتناول هذا في المقام الأول قضايا كفاءة الذاكرة ويمكن أن يخفف جزئيًا من تدهور الأداء. من خلال دمج تحسين PGO مع النسخة مفتوحة المصدر لدينا، يمكن لأداء إدراج الدُفعة أن يقترب من ذلك في MySQL 5.7.

Figure 3. Optimized MySQL 8.0.40 with PGO performs roughly on par with version 5.7.

يمكن للمستخدمين أيضًا الاستفادة من عدة خيوط لمعالجة الدُفعات المتزامنة، مستفيدين تمامًا من تحسين التزامن المحسن لسجل الإعادة، مما يمكن أن يعزز بشكل كبير أداء إدراج الدُفعات.

بالنسبة لقضايا تحديث الفهرس، نظرًا لإضافة الكود الجديد اللازمة، يمكن لتحسين PGO المساعدة في التخفيف من هذه المشكلة. يمكن لنسختنا PGO [2] التخفيف بشكل كبير من هذه المشكلة.

بالنسبة لأداء القراءة، وبخاصة أداء الانضمام، قمنا بتحسينات كبيرة، بما في ذلك إصلاح مشكلات الinline وإجراء تحسينات أخرى. مع إضافة PGO، يمكن زيادة أداء الانضمام بأكثر من 30٪ مقارنة بالنسخة الرسمية.

Figure 4. Using PGO, along with our optimizations, can lead to significant improvements in join performance.

سنستمر في استثمار الوقت في تحسين أداء القليل من التزامن. هذه العملية طويلة ولكنها تشمل العديد من المجالات التي تحتاج إلى تحسين.

النسخة مفتوحة المصدر متاحة للفحص، وسيستمر الجهد في تحسين أداء MySQL..

المراجع

[1] بن وانغ (2024). فن حل المشكلات في هندسة البرمجيات: كيفية جعل MySQL أفضل.

[2] تحسين لـ MySQL · GitHub

Source:
https://dzone.com/articles/mysql-80-performance-degradation-analysis