ユーザーは、低同時実行性能の低下により、改善が高同時実行性能の向上ほどにはっきりと感じられやすい傾向があります。そのため、低同時実行性能を維持することは重要であり、これは直接ユーザーエクスペリエンスやアップグレードの意欲に影響を与えます[1]。
多くのユーザーフィードバックによると、MySQL 8.0にアップグレードした後、ユーザーは一般的にパフォーマンスの低下を感じており、特にバッチ挿入および結合操作で顕著です。この低下傾向は、より高いバージョンのMySQLでより明確になっています。さらに、一部のMySQL愛好家やテスターは、アップグレード後に複数のsysbenchテストでパフォーマンスの劣化を報告しています。
これらのパフォーマンスの問題は避けることができるのでしょうか?具体的には、パフォーマンスの低下傾向を科学的に評価するにはどうすればよいのでしょうか?これらは考慮すべき重要な問題です。
公式チームは引き続き最適化を行っていますが、パフォーマンスの漸進的な劣化は見逃すことができません。特定のシナリオでは改善が見られる場合がありますが、これはすべてのシナリオでパフォーマンスが同様に最適化されているという意味ではありません。さらに、特定のシナリオのパフォーマンスを最適化することは容易ですが、その代償として他の領域のパフォーマンスが低下することもあります。
MySQLパフォーマンス低下の根本原因
一般的に、機能が追加されるにつれてコードベースが成長し、機能の継続的な拡張に伴い、パフォーマンスをコントロールすることがますます困難になります。
MySQLの開発者は、コードベースへの各追加がパフォーマンスのわずかな低下しかもたらさないため、パフォーマンスの低下に気づかないことがよくあります。しかし、時間の経過とともに、これらの小さな低下が蓄積され、著しい累積効果をもたらし、ユーザーがMySQLの新しいバージョンで明らかなパフォーマンスの低下を認識する原因となります。
たとえば、次の図は、MySQL 8.0.40がMySQL 8.0.27と比較してパフォーマンスが低下していることを示しています:
次の図は、シングルコンカレンシーの下でのバッチ挿入パフォーマンステストを示し、MySQL 8.0.40のパフォーマンスがバージョン5.7.44と比較して低下していることを示しています:
上記の2つのグラフから、バージョン8.0.40のパフォーマンスが良くないことがわかります。
次に、MySQLのパフォーマンス低下の原因をコードレベルで分析してみましょう。以下はMySQL 8.0のPT_insert_values_list::contextualize
関数です:
対応するMySQL 5.7のPT_insert_values_list::contextualize
関数は次のようになります:
コードの比較から、MySQL 8.0はより洗練されたコードを持っているようで、進歩しているようです。
残念ながら、多くの場合、これらのコードの改善の背後にある動機がパフォーマンスの低下につながります。MySQL公式チームは、以前のList
データ構造をdeque
で置き換えたことが、徐々にパフォーマンスの低下の原因の1つとなっています。次に、deque
のドキュメントを見てみましょう:
std::deque (double-ended queue) is an indexed sequence container that allows fast insertion and deletion at both its
beginning and its end. In addition, insertion and deletion at either end of a deque never invalidates pointers or
references to the rest of the elements.
As opposed to std::vector, the elements of a deque are not stored contiguously: typical implementations use a sequence
of individually allocated fixed-size arrays, with additional bookkeeping, which means indexed access to deque must
perform two pointer dereferences, compared to vector's indexed access which performs only one.
The storage of a deque is automatically expanded and contracted as needed. Expansion of a deque is cheaper than the
expansion of a std::vector because it does not involve copying of the existing elements to a new memory location. On
the other hand, deques typically have large minimal memory cost; a deque holding just one element has to allocate its
full internal array (e.g. 8 times the object size on 64-bit libstdc++; 16 times the object size or 4096 bytes,
whichever is larger, on 64-bit libc++).
The complexity (efficiency) of common operations on deques is as follows:
Random access - constant O(1).
Insertion or removal of elements at the end or beginning - constant O(1).
Insertion or removal of elements - linear O(n).
上記の説明に示されているように、極端な場合には、単一の要素を保持するために配列全体を割り当てる必要があり、非常に低いメモリ効率をもたらします。たとえば、大量のレコードを挿入する必要があるバルク挿入の場合、公式の実装では各レコードを別々のデックに格納します。レコードの内容が最小限であっても、デックは依然として割り当てる必要があります。MySQLのデック実装では、迅速な検索をサポートするために各デックに1KBのメモリを割り当てています。
The implementation is the same as classic std::deque: Elements are held in blocks of about 1 kB each.
公式の実装では、インデックス情報を格納するために1KBのメモリを使用しており、レコードの長さが大きくなくてもレコードが多数ある場合、メモリアクセスアドレスが非連続になる可能性があり、キャッシュフレンドリーさが低下します。この設計はキャッシュフレンドリーさを改善することを意図していましたが、完全には効果を発揮していません。
元の実装では、メモリプールを通じてメモリが割り当てられるListデータ構造が使用されており、ある程度のキャッシュフレンドリーさを提供していました。ランダムアクセスの効率は低下しますが、List要素への順次アクセスを最適化することで、パフォーマンスが大幅に向上します。
MySQL 8.0へのアップグレード中に、ユーザーはバッチ挿入パフォーマンスの著しい低下を観察し、その主な原因の一つは基盤となるデータ構造の大幅な変更でした。
さらに、公式チームがredoログメカニズムを改善したことで、MTRコミット操作の効率も低下しました。MySQL 5.7と比較して、追加されたコードは個々のコミットのパフォーマンスを大幅に低下させており、全体的な書き込みスループットは大幅に改善されたにもかかわらずです。
MySQL 5.7.44のMTRコミットのコアexecute
操作を見てみましょう:
MySQL 8.0.40のMTRコミットのコアexecute
操作を調査しましょう。
比較すると、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
関数です。
MySQL 8.0.40のrec_init_offsets_comp_ordinary
関数は以下の通りです。
上記のコードからわかるように、インスタント追加/削除カラム機能の導入により、rec_init_offsets_comp_ordinary
関数は明らかに複雑になり、関数呼び出しが増え、キャッシュ最適化に大きな影響を与えるスイッチ文が追加されています。この関数は頻繁に呼び出されるため、更新インデックス、バッチ挿入、結合のパフォーマンスに直接影響を与え、重大なパフォーマンス低下を引き起こします。
さらに、MySQL 8.0におけるパフォーマンスの低下は上記に限らず、全体的なパフォーマンス低下に寄与する他の多くの領域があり、特にインライン関数の展開への影響が顕著です。例えば、以下のコードはインライン関数の展開に影響を与えます:
私たちのテストによると、ib::fatal
ステートメントはインライン最適化に対して深刻な干渉を引き起こします。頻繁にアクセスされる関数においては、インライン最適化を妨げるステートメントは避けることが推奨されます。
次に、類似の問題を見てみましょう。row_sel_store_mysql_field
関数は頻繁に呼び出され、その中でrow_sel_field_store_in_mysql_format
がホットスポット関数です。具体的なコードは以下の通りです:
row_sel_field_store_in_mysql_format
関数は最終的にrow_sel_field_store_in_mysql_format_func
を呼び出します。
row_sel_field_store_in_mysql_format_func
関数はib::fatal
コードの存在によりインライン化できません。
頻繁に呼び出される非効率的な関数が毎秒何千万回も実行されることは、結合パフォーマンスに深刻な影響を与える可能性があります。
パフォーマンス低下の理由を引き続き探ってみましょう。次の公式パフォーマンス最適化は、実際には結合パフォーマンスの低下の根本的な原因の1つです。特定のクエリを改善できる場合もありますが、それらは通常の結合操作のパフォーマンス degradatioの一因です。
MySQLの問題はこれに留まりません。上記の分析に示されているように、MySQLのパフォーマンス低下には理由があります。一連の小さな問題が蓄積されると、ユーザーが経験する明らかなパフォーマンスの低下につながる可能性があります。ただし、これらの問題を特定することはしばしば困難であり、それを解決することはさらに困難になります。
いわゆる「早すぎる最適化」はすべての悪の根源であり、MySQLの開発には当てはまりません。データベースの開発は複雑なプロセスであり、時間の経過とともにパフォーマンスを無視すると、後続のパフォーマンス改善が著しく困難になります。
MySQLパフォーマンス低下の緩和策
書き込みパフォーマンスの低下の主な原因は、MTRコミットの問題、瞬時の列の追加/削除、およびその他いくつかの要因に関連しています。これらは従来の方法で最適化するのが難しいです。ただし、ユーザーはPGO最適化を介してパフォーマンスの低下を補うことができます。適切な戦略を立てれば、一般的にパフォーマンスを安定させることができます。
バッチ挿入のパフォーマンス低下について、私たちのオープンソース版[2]は公式のdequeを改善したリスト実装に置き換えています。これは主にメモリ効率の問題に対処し、パフォーマンスの低下を部分的に緩和することができます。PGO最適化を私たちのオープンソース版と組み合わせることで、バッチ挿入のパフォーマンスはMySQL 5.7に近づくことができます。
ユーザーは複数のスレッドを利用して同時バッチ処理を行うことができ、redoログの改善された同時実行性を最大限に活用することで、バッチ挿入のパフォーマンスを大幅に向上させることができます。
更新インデックスの問題については、新しいコードの追加が避けられないため、PGO最適化がこの問題を軽減するのに役立ちます。私たちのPGO版[2]はこの問題を大幅に緩和することができます。
読み取りパフォーマンス、特に結合パフォーマンスについては、インラインの問題を修正し、その他の最適化を行うことで大幅な改善を図りました。PGOの追加により、結合パフォーマンスは公式版に比べて30%以上向上する可能性があります。
私たちは低同時実行性パフォーマンスの最適化に引き続き時間を投資します。このプロセスは長いですが、改善が必要な多くの領域を含んでいます。
オープンソース版はテスト用に利用可能であり、MySQLパフォーマンスの向上に向けた努力は続けられます。
参考文献
[1] Bin Wang (2024). ソフトウェアエンジニアリングにおける問題解決の技法: MySQLをより良くする方法。
Source:
https://dzone.com/articles/mysql-80-performance-degradation-analysis