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.

上記の2つのグラフから、バージョン8.0.40のパフォーマンスが良くないことがわかります。

次に、MySQLのパフォーマンス低下の原因をコードレベルで分析してみましょう。以下はMySQL 8.0のPT_insert_values_list::contextualize関数です:

C++

 

対応するMySQL 5.7のPT_insert_values_list::contextualize関数は次のようになります:

C++

 

コードの比較から、MySQL 8.0はより洗練されたコードを持っているようで、進歩しているようです。

残念ながら、多くの場合、これらのコードの改善の背後にある動機がパフォーマンスの低下につながります。MySQL公式チームは、以前のListデータ構造をdequeで置き換えたことが、徐々にパフォーマンスの低下の原因の1つとなっています。次に、dequeのドキュメントを見てみましょう:

Markdown

 

上記の説明に示されているように、極端な場合には、単一の要素を保持するために配列全体を割り当てる必要があり、非常に低いメモリ効率をもたらします。たとえば、大量のレコードを挿入する必要があるバルク挿入の場合、公式の実装では各レコードを別々のデックに格納します。レコードの内容が最小限であっても、デックは依然として割り当てる必要があります。MySQLのデック実装では、迅速な検索をサポートするために各デックに1KBのメモリを割り当てています。

Plain Text

 

公式の実装では、インデックス情報を格納するために1KBのメモリを使用しており、レコードの長さが大きくなくてもレコードが多数ある場合、メモリアクセスアドレスが非連続になる可能性があり、キャッシュフレンドリーさが低下します。この設計はキャッシュフレンドリーさを改善することを意図していましたが、完全には効果を発揮していません。

元の実装では、メモリプールを通じてメモリが割り当てられるListデータ構造が使用されており、ある程度のキャッシュフレンドリーさを提供していました。ランダムアクセスの効率は低下しますが、List要素への順次アクセスを最適化することで、パフォーマンスが大幅に向上します。

MySQL 8.0へのアップグレード中に、ユーザーはバッチ挿入パフォーマンスの著しい低下を観察し、その主な原因の一つは基盤となるデータ構造の大幅な変更でした。

さらに、公式チームがredoログメカニズムを改善したことで、MTRコミット操作の効率も低下しました。MySQL 5.7と比較して、追加されたコードは個々のコミットのパフォーマンスを大幅に低下させており、全体的な書き込みスループットは大幅に改善されたにもかかわらずです。

MySQL 5.7.44のMTRコミットのコアexecute操作を見てみましょう:

C++

 

MySQL 8.0.40のMTRコミットのコアexecute操作を調査しましょう。

C++

 

比較すると、MySQL 8.0.40では、MTRコミット内のexecute操作がより複雑になり、より多くのステップが関与することが明らかです。この複雑さが、低同時書き込みパフォーマンスの低下の主な原因の一つです。

特に、操作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コミットが必要となったため、パフォーマンスは依然として大幅に低下しています。

インスタント追加/削除機能がパフォーマンスに与える影響を見てみましょう。以下はMySQL 5.7のrec_init_offsets_comp_ordinary関数です。

C++

 

MySQL 8.0.40のrec_init_offsets_comp_ordinary関数は以下の通りです。

C++

 

上記のコードからわかるように、インスタント追加/削除カラム機能の導入により、rec_init_offsets_comp_ordinary関数は明らかに複雑になり、関数呼び出しが増え、キャッシュ最適化に大きな影響を与えるスイッチ文が追加されています。この関数は頻繁に呼び出されるため、更新インデックス、バッチ挿入、結合のパフォーマンスに直接影響を与え、重大なパフォーマンス低下を引き起こします。

さらに、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++

 

頻繁に呼び出される非効率的な関数が毎秒何千万回も実行されることは、結合パフォーマンスに深刻な影響を与える可能性があります。

パフォーマンス低下の理由を引き続き探ってみましょう。次の公式パフォーマンス最適化は、実際には結合パフォーマンスの低下の根本的な原因の1つです。特定のクエリを改善できる場合もありますが、それらは通常の結合操作のパフォーマンス degradatioの一因です。

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.

ユーザーは複数のスレッドを利用して同時バッチ処理を行うことができ、redoログの改善された同時実行性を最大限に活用することで、バッチ挿入のパフォーマンスを大幅に向上させることができます。

更新インデックスの問題については、新しいコードの追加が避けられないため、PGO最適化がこの問題を軽減するのに役立ちます。私たちのPGO版[2]はこの問題を大幅に緩和することができます。

読み取りパフォーマンス、特に結合パフォーマンスについては、インラインの問題を修正し、その他の最適化を行うことで大幅な改善を図りました。PGOの追加により、結合パフォーマンスは公式版に比べて30%以上向上する可能性があります。

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

私たちは低同時実行性パフォーマンスの最適化に引き続き時間を投資します。このプロセスは長いですが、改善が必要な多くの領域を含んでいます。

オープンソース版はテスト用に利用可能であり、MySQLパフォーマンスの向上に向けた努力は続けられます。

参考文献

[1] Bin Wang (2024). ソフトウェアエンジニアリングにおける問題解決の技法: MySQLをより良くする方法。

[2] MySQLのために強化された · GitHub

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