How Intel PAUSE instruction changes affect MySQL performance

読み方の手引き

x86 や ARM の命令は数多く存在します。アプリケーションプログラマーもデータベースカーネル開発者も、普段はこれらの命令を深く理解する必要はありませんが、PAUSE 命令とデータベースの操作は密接に関わっています。本記事では、非常に興味深いパフォーマンス最適化を通じて PAUSE 命令の理解を紹介し、CPU 命令セットがプログラムにどのように影響するかを効率的に解明することを目的としています。

記事は 2 部構成です。前半は MySQL クラスターのフルテーブルスキャンにおけるパフォーマンス最適化の過程、後半は問題解決後の原理分析、および PAUSE 命令のコンテキスト、長所と短所、適用シーンの分析です。

ビジネス構成

理解しやすくするため、いくつか簡略化を行っています:

クライアント -> Tomcat -> LVS -> MySQL (32 個の MySQL インスタンスクラスター、各インスタンス 8 コア)

シナリオの説明

クライアントは Tomcat および MySQL クラスターに負荷をかけます (データのサブデータベースとサブテーブル化のため)。MySQL クラスターは 32 インスタンスです。各ビジネス SQL は Tomcat で 256 個の SQL ステートメントに分割され、32 個の MySQL データベースに送信されます (各 MySQL には 8 個のサブデータベースがあります)。MySQL に送信される 256 個の SQL ステートメントは完全なシリアルではなく、完全なパラレルでもなく、ある程度の並列性を持っています。

ビジネス SQL はシンプルな SELECT SUM で、各 MySQL では (インデックスにより) 高速に処理されます。


SELECT SUM(emp_arr_amt) FROM table_ c WHERE INSUTYPE='310' AND Revs_ Flag='Z' AND accrym='201910' AND emp_ no='1050457';

監視指標の説明

● 以下またはスクリーンショットのロジカル RT / QPS は、クライアント側から見た Tomcat の RT と QPS を指します。

● RT:応答時間。パフォーマンスボトルネックを判断する唯一の指標です。

● 物理 RT / QPS は、Tomcat から見た MySQL の RT と QPS を指します (ここでの RT は Tomcat ノードのネットワークカードに到達するまでの RT であり、ネットワークの消費も含みます)。

問題の説明:

クライアントが 1 台の Tomcat ノード + 32 台の MySQL ノードに負荷をかけると、QPS は約 430 です。Tomcat ノードの CPU はフル稼働で、MySQL の RT は 0.5ms です。Tomcat ノードを 1 台追加すると、QPS は約 700 になります。Tomcat の CPU はほぼフル稼働で、MySQL の RT は 0.6ms です。ここではパフォーマンスがスケールアウトに比例してほぼ線形に向上しており、期待通りです。

さらに Tomcat ノードを追加して水平方向にスケールアウトします。クライアントが 3 台の Tomcat ノード + 32 台の MySQL ノードに負荷をかけると、QPS は 700 のままです。Tomcat ノードの CPU は十分に稼働しておらず、MySQL の RT は 0.8ms で、期待に反する深刻な状態です。


負荷テストの原則:

同時実行数を増やしても QPS が上がらなくなった場合、どこかにボトルネックがあることを示しており、ボトルネックは RT が最も大きく増加している場所にあります。

MySQL のトラブルシューティング

モニタリングを通じて、オンサイトの DBA は MySQL の CPU が 20% 未満であり、スロークエリもないことを確認しました。クライアントを使って中間のリンクをすべて bypass し、1 台の MySQL に直接負荷をかけたところ、MySQL の CPU をフル稼働させることができました。この時の QPS は約 38,000 でした (上記のシナリオでは、クライアント QPS が 700 の時、単一 MySQL 上の QPS は 6,000 しか稼働していませんでした)。これにより MySQL の疑いは排除されました (この推論は十分に厳密ではなく、後の調査に大きな落とし穴を残しました)。

次に疑いはネットワーク、LVS、その他の中間リンクに向かいます。

LVS とネットワークの疑い

まず、大規模なクエリを通じて帯域幅の問題を排除します。ここでは小パケットだからです。PPS が 720,000 に達した時、ゲートウェイや LVS の流量制限が自然に疑われます。

PPS モニタリング。この物理マシンには 4 個の MySQL インスタンスがあります。PPS は約 90,000 で、9 * 32 / 4 = 720,000 です。

最終的に、すべてのネットワーク要因が排除されました。重要な根拠は、負荷テスト中に Tomcat から MySQL へ繰り返し PING を行った際、RT が無負荷時と同じであったことです。これはネットワークに問題がないことを示しています (この PING の役割について考えてみてください)。

問題の確認

Tomcat でログを有効にし、スロークエリのしきい値を 100ms に設定してみます。すると、ログから MySQL 上の大量のスロークエリを確認できました。この SQL は Tomcat で 256 個の SQL ステートメントに分割され同時に発行されるため、1 つの SQL ステートメントの戻りが遅れると、リクエスト全体がその弱点に引きずられます。平均 RT は 0.8ms ですが、100ms を超えるものがあれば全体への影響は依然として大きくなります。

Tomcat で記録されたスロークエリ (Tomcat がユニーク ID を付与して MySQL に送信) を MySQL のログで確認すると、MySQL が実際に遅いことがわかりました。これにより、問題が MySQL であることがほぼ確定しました。これでネットワーク問題かどうかを気にする必要はなくなりました。

同時に Tomcat でパケットキャプチャを行い、ネットワークカード上の RT を統計分析しました:

上記は Tomcat でキャプチャした各 SQL の平均物理 RT です。上段は QPS 430 の時の RT 0.6ms、下段は 3 台のサーバーで QPS 700 の時のものです。しかし RT は 0.9ms に上昇しており、Tomcat のモニタリングに記録された物理 RT とほぼ一致しています。MySQL 側でも同様のパケットキャプチャで RT を計算できれば、ネットワークの問題を素早く排除できます。

ネットワークパケットキャプチャから得た RT データは、すべての関係者により受け入れられやすいものです。MySQL 側でもパケットキャプチャを試みましたが、LVS モジュールによりアクセスポートと IP アドレスが変更されているため、ストリームの応答時間を分析できませんでした。

MySQL に再フォーカス

この時点で問題箇所がほぼ確認できたため、MySQL 内部に問題があるかどうかの調査方針が変わりました。単に CPU やスロークエリを見るのではなく、問題は明らかに複雑です。

教訓:CPU はパフォーマンスに影響する一要素に過ぎず、RT が結果である。CPU ではなく RT に注目せよ

モニタリングによると、MySQL の CPU は常に高くないものの、実行中スレッドが頻繁に 100 以上に急増し、すぐに下降することがわかりました。突発的な同時クエリリクエストがキューイングを引き起こしているように見えます。各 MySQL インスタンスは 8 コア CPU です。MySQL インスタンスを 16 コアに拡張してみると (この問題の検証のため)、QPS は確かに 1,000 まで上昇しました (理想的な 1,400 ではありませんが)。

これは Tomcat 上で監視した MySQL のステータスです:

同時に MySQL 上の vmstat でもこの急増を確認できます:

上記の分析から、MySQL 全体の負荷は大きくないものの、時折バースト的な実行タスクの急増が発生していることが明確にわかります。

このような一時的で突発的な同時トラフィックのモニタリングは、ほぼすべて平均化されてしまうため、把握が困難です。リアルタイムモニタリングが時折このような一時的なスパイクをサンプリングするのみであり、それが当初 MySQL の見落としにつながりました。

そこで次の核心的な疑問は、なぜ MySQL にこのような急増が発生するのか、そしてこの急増がどのような影響をもたらすのかです。

perf top

直接 perf を使って MySQL プロセスを確認すると、ut_delay が非論理的な CPU 消費をしていることが判明しました:

展開すると、基本的にオプティマイザーでのインデックスヒット行数の選択を確認できます:

MySQL コマンドラインで show processlist を実行した時に見える内容と基本的に同じです:

これは MySQL オプティマイザーがインデックスの統計情報を収集する仕組みです。統計収集時にはロックが必要で、スレッドランニングがジッターを起こしている時、show processlist から多くのスレッドが statistics 状態にあることがわかります。つまり、高い並列性によるロック競合が CPU の非効率と RT の急激な増加を引き起こしています。

ここで ut_delay が消費する 28% の CPU は明らかに異常です。そこで innodb_spin_wait_delay を 30 から 6 に変更すると、パフォーマンスは即座に改善しました。さらに Tomcat ノードを追加すれば、QPS も線形に増加できます。

最も CPU を消費している呼び出し関数スタックは... mutex_spin_wait -> ut_delay で、ロック待機のロジックに属します。InnoDB はここでスピンロックを使用しており、ロック待機は ut_delay を呼び出すことで CPU に空サイクルを実行させ、CPU を解放せずにロックを待つことでコンテキストスイッチを回避します。これにより比較的高い CPU を消費します。

最終パフォーマンス

パラメーター innodb_spin_wait_delay=6 に調整後、4 台の Tomcat ノードで同時実行数 40 の条件下で、QPS は 1,700 に達し、物理 RT は 0.7ms、ロジカル RT は 19.6ms、CPU は 90% です。この時点では Tomcat ノード数を増やし続けることで QPS を増加できます。

調整前との比較:innodb_spin_wait_delay=30、同時実行数 40、QPS 500+、物理 RT:2.6ms、ロジカル RT:72.1ms、CPU:37%

調整前の負荷テスト中の vmstat と tsar --cpu を見てください。プロセスランニングが明らかにジッターしているのがわかります。

delay 変更後と比較すると、QPS が 3 倍大きいにもかかわらず、プロセスランニングは安定しています。

振り返りと分析

ここで問題は完全に解決しましたが、なぜそうなるのかという疑問が湧きます。ut_delay はどのように動作するのか?そして innodb_spin_wait_delay とスピンロックの関係は?

原理分析

innodb_spin_wait_delay の調整でこの問題が解決できたので、まず innodb_spin_wait_delay の役割を分析します。

innodb_spin_wait_delay について

InnoDB は大量のスピンロック (InnoDB ミューテックスや rw-lock など) を使用して、CPU 消費の高いコンテキストスイッチを回避しています。これはスピンロックの正しい使用方法です。マルチコア環境では、複数のスレッドが同時に同じロックをスピンして奪い合うため、キャッシュピンポンを引き起こす可能性が高いです。さらに、複数の CPU コアが互いのキャッシュを部分的に無効化します。そこで InnoDB は innodb_spin_wait_delay と PAUSE を連携させてキャッシュピンポンを緩和しています。つまり、CPU による高速なスピンロック奪取を、ロック奪取失敗後に遅延 (PAUSE) を入れつつ CPU は解放しない方式に置き換えています。遅延時間が経過した後にロック奪取を再開します。すなわち、継続的なスピンロック奪取を、より疎なポイント単位のロック奪取に変換しています (間隔の遅延は乱数です)。これによりコンテキストスイッチを回避しつつ、キャッシュピンポンも大幅に削減できます。

スピンロックがキャッシュピンポンを削減する仕組み

複数のスレッドがロックを競合する場合、ロック獲得に失敗したスレッドはロックを獲得するまで「ビジーウェイト」状態になります。「ビジーウェイト」とは何か?CAS 関数を常に実行し続けるわけではなく、CPU と連携して動作することを指します。CPU が提供する PAUSE 命令によって、サイクルウェイト中のキャッシュピンポンと消費電力を削減します。なお、シングルコア CPU の場合、ビジーウェイトに意味がないため、スレッドを積極的にスリープさせます。

x86 PAUSE 命令

x86 では PAUSE 命令が設計されています。PAUSE 命令を呼び出すコードは CPU を解放しませんが、CPU は短いスリープ状態に入ります。たとえば 10 クロックサイクルですが、これは 1 回のコンテキストスイッチの数千クロックサイクルよりも多いです。

このように、スピンロックの獲得に失敗した場合は PAUSE で一時停止できますが、MySQL にとっては停止時間が不十分なので、innodb_spin_wait_delay パラメーターを追加して停止時間を拡大する必要があります。

今回のシナリオでは、各 SQL の RT ジッターに非常に敏感です (256 倍に拡大されるため)。遅延が大きすぎると一部の SQL の RT が上昇する原因となります。

CPU に PAUSE が必要な理由

まず PAUSE 命令の機能を見ていきます:

● コンテキストスイッチを回避する。アプリケーション層で休息したい場合は yield や sleep を使うこともできますが、これら 2 つの操作は CPU にとって重すぎます (コンテキストスイッチを伴います)。

● ハイパースレッドのコンピューティング能力を解放できます (HT はコアを共有しますが、レジスタなどの格納ユニットは個別です。CPU が PAUSE を実行すると、対応する HT がコンピューティングリソースを占有できます)。たとえば同じコア上で複数の PAUSE を先に実行し、その後 nop 命令を実行すると、nop 命令の IPC は PAUSE の影響をほとんど受けません。

● 省エネルギー (CPU を解放せずに休息できます)。CPU PAUSE 中に top コマンドで CPU 100% と表示されますが、実際のエネルギーは消費しません。

したがって、PAUSE 命令はハイパースレッドの利用率向上、省エネルギー、コンテキストスイッチの削減、およびスピンロックの効率向上を実現します。

まとめの分析

異なる Intel CPU アーキテクチャにより PAUSE 命令のクロックサイクル数が異なり、その結果 MySQL の innodb_spin_wait_delay によるスピンロック失敗時の遅延 (この時 PAUSE を innodb_spin_wait_delay の N 回実行する必要があります) が長くなり、呼び出し側から見た MySQL の RT がより大きくなります。これにより Tomcat Server 上のビジネス同時実行数が上限に達しなくなり、最終的に負荷が上がらなくなります。

長いリンクのトラブルシューティングでは、どのノードに問題があるかを特定することが最も困難です。CPU ではなく RT に注目すべきです。

速くするだけでは不十分です。負荷テストを行う際は、ある同時実行数から QPS と RT を注意深く観察し、負荷が高くなりすぎるまで段階的に増加させ、QPS と RT の変化からボトルネックを特定する必要があります。

Related Articles

Explore More Special Offers

  1. Short Message Service(SMS) & Mail Service

    50,000 email package starts as low as USD 1.99, 120 short messages start at only USD 1.00

phone お問い合わせ
Hi, I'm Alibaba Cloud AI Assistant!
I can help with questions and solutions.