Глубокий анализ деградации производительности MySQL 8.0

Пользователи, как правило, легче замечают снижение производительности при низкойConcurrency, в то время как улучшения в производительности при высокойConcurrency часто труднее воспринимаются. Поэтому поддержание производительности при низкойConcurrency имеет решающее значение, так как это напрямую влияет на пользовательский опыт и готовность к обновлению [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 выделяет 1 КБ памяти для каждой деки для поддержки быстрых запросов.

Plain Text

 

Официальная реализация использует 1 КБ памяти для хранения информации об индексе, и даже если длина записи невелика, но записей много, адреса доступа к памяти могут стать несмежными, что приведет к плохой дружественности кэша. Этот дизайн был задуман для улучшения дружественности кэша, но не оказался полностью эффективным.

Следует отметить, что в исходной реализации использовалась структура данных List, в которой память выделялась через пул памяти, обеспечивая определенный уровень дружественности кэша. Хотя случайный доступ менее эффективен, оптимизация последовательного доступа к элементам List значительно улучшает производительность.

При обновлении до MySQL 8.0 пользователи заметили значительное снижение производительности пакетной вставки, и одной из основных причин было значительное изменение базовых структур данных.

Кроме того, хотя официальная команда улучшила механизм журнала изменений redo, это также привело к снижению эффективности операции фиксации MTR. По сравнению с MySQL 5.7, добавленный код значительно снижает производительность отдельных фиксаций, хотя общая пропускная способность записи была значительно улучшена.

Давайте рассмотрим основную операцию execute фиксации MTR в MySQL 5.7.44:

C++

 

Давайте рассмотрим основную операцию execute коммита MTR в MySQL 8.0.40:

C++

 

По сравнению, ясно, что в MySQL 8.0.40 операция execute в коммите MTR стала намного сложнее, в нее включено больше шагов. Эта сложность является одной из основных причин снижения производительности при низкой конкурентности записи.

В частности, операции m_impl->m_log.for_each_block(write_log) и log_wait_for_space_in_log_recent_closed(*log_sys, handle.start_lsn) имеют значительные накладные расходы. Эти изменения были внесены для улучшения производительности при высокой конкурентности, но это произошло за счет производительности при низкой конкурентности.

Приоритизация журнала повтора в режиме высокой конкурентности приводит к плохой производительности для рабочих нагрузок с низкой конкурентностью. Хотя введение 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] заменяет официальную очередь на улучшенную реализацию списка. Это в первую очередь решает проблемы с эффективностью использования памяти и может частично смягчить снижение производительности. Совмещение оптимизации PGO с нашей версией с открытым исходным кодом позволяет производительности пакетной вставки приблизиться к MySQL 5.7.

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

Пользователи также могут использовать несколько потоков для параллельной пакетной обработки, полностью задействуя улучшенную параллельность журнала повторной записи, что может значительно повысить производительность пакетной вставки.

Что касается проблем с обновлением индекса, то из-за неизбежного добавления нового кода оптимизация PGO может помочь смягчить эту проблему. Наша версия PGO [2] может значительно облегчить эту проблему.

Что касается производительности чтения, особенно производительности соединений, мы внесли значительные улучшения, включая исправление инлайн-проблем и другие оптимизации. С добавлением 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