Apache RocketMQにおけるロックのパフォーマンスボトルネックを突破する方法

> 本記事は[こちらのブログ](https://www.alibabacloud.com/blog/the-way-to-breaking-through-the-performance-bottleneck-of-locks-in-apache-rocketmq_601937?spm=a2c65.11461433.0.0.4ee353558K5XDa)を参考にしています。
> 翻訳にはアリババクラウドの[ModelStudio](https://www.alibabacloud.com/en/product/modelstudio)([Qwen](https://www.alibabacloud.com/en/solutions/generative-ai/qwen))を使用しております。

# Apache RocketMQのロック機構の最適化

著者: 王淮源 および 紀俊濤

## 背景
Apache RocketMQは、イベント駆動型アプリケーションの作成を簡素化するクラウドネイティブのメッセージングおよびストリーミングプラットフォームです。長年にわたるApache RocketMQの進化に伴い、マルチコアプロセッサの活用と並行処理によるプログラム効率の向上のために、大量のコードが書かれています。その結果、並行処理のパフォーマンス管理が重要となり、共有リソースへのアクセス時に複数の実行スレッドの安全な同期を確保するためにロックが不可欠となります。ロックはマルチコアシステムでの相互排他性を確保するために必要ですが、その使用は最適化の課題も引き起こします。並行システムが内部的に複雑になるにつれて、効果的なロック管理戦略の展開がパフォーマンスの維持に重要となります。

したがって、Google Summer of Code (GSoC) 2024において、私たちのApache RocketMQオープンソースコミュニティは非常に挑戦的なプロジェクトを提案しました：GSOC-269: Apache RocketMQにおけるロック機構の最適化。このプロジェクトでは、ロックの動作を最適化してApache RocketMQのパフォーマンスを向上させ、リソース消費を削減することを目指しています。このプロジェクトを通じて、私たちは革新的にABSロック（Adaptive Backoff Spin Lock）を提案しました。ABSロックのアイデアは、軽量スピンロックに対して一連のバックオフ戦略を提供し、低コストで制限されたロックスピン動作を実現し、さまざまなレベルのリソース競合に対応することです。ABSという名前は、その設計思想がブレーキシステムのABSに類似しているため選ばれました。

初期のシステムでは、スピンロックは継続的にスピンしていました。しかし、クリティカルセクションが大きい場合、多くの不要なスピンが生成され、ブレーキシステムでのロックアップに似た状況になり、リソース利用率が急激に増加しました。このようなリソース消費を防ぐために、コミュニティはスピンロックをミューテックスに置き換え、大きなクリティカルセクションの場合にリソースの浪費を避けることにしました。しかし、この代替手段を実装した後、ミューテックスのブロッキングとウェイキングメカニズムは小さなメッセージの送信応答時間を影響させ、CPU消費量を高めました。スピンロックは、クリティカルセクションが小さい場合、応答時間（RT）とCPU利用率の面で優れた性能を発揮します。唯一の欠点は、リソース競合時のリソースの浪費です。そのため、私たちはスピンロックを最適化して不要なリソース消費を削減し、スピンロックの利点をより有効に活用し、高競合と低競合の両方のシナリオに適応させることが決定されました。

実際には、ロック戦略の調整がApache RocketMQのメッセージ送信性能に影響を与え、大幅なパフォーマンス改善をもたらすことを証明しました。ABSロックはまた、オープンソースユーザーが直面するロック選択のジレンマにも対応します：Apache RocketMQでメッセージを配信する際、SpinLock(swapAndSet)とReentrantLockミューテックスの2つのロック機構がありますが、それぞれがどのシナリオに適しているかを分析するドキュメントが不足しています。そこで、私たちはABSロックを通じてこれら2つのロック機構を統合し、最適な状態を達成し、サーバー側のロック機構のループを完成させました。ユーザーは現在のシナリオに適したロック機構を決定する必要はありません。ABSロックは自然に競合シナリオに基づいてロックパラメータを微調整します。ABSロックは、実行時条件に基づいてその動作を動的に調整できます。例えば、ロック競合のレベルや同じリソースを競合するスレッドの数などに基づいて、ロック取得と解放に関連するオーバーヘッドを最小限に抑えることができます。特に高競合シナリオでは、リアルタイムでシステムパフォーマンス指標を監視することで、ABSロックは異なるロック戦略間で切り替えることができます。

私たちは現在、適応型のロック機構（ABSロック）を実装し、実験結果が異なるシナリオでミューテックスまたはスピンロックを使用するよりも最適な効果を達成することを検証しました。要するに、異なるシナリオで最良のロック機構の効果を達成しています。この記事では、Apache RocketMQのロック機構の進化過程とABSロックの最適化効果について説明します。

## 概念
本文の主な内容に入る前に、この記事で頻繁に使用されるいくつかの概念を紹介する必要があります：クリティカルセクション、ミューテックス、およびスピンロック。これらの概念を理解することで、この記事で提示される最適化アイデアをよりよく把握することができます。

### クリティカルセクション
クリティカルセクションは、スレッドによって独占的にアクセスされるコードのセグメントです。つまり、あるスレッドが現在このコードセグメントにアクセスしている場合、他のスレッドは現在のスレッドがコードセグメントを離れるまで待つ必要があります。これによりスレッドの安全性が確保されます。クリティカルセクションのサイズは様々な要素によって影響を受けます。例えば、この記事では、メッセージ送信時のクリティカルセクションのサイズはメッセージボディのサイズによって影響を受ける可能性があります。

### ミューテックス
ミューテックスは排他的ロックです。スレッドAがミューテックスを取得すると、それがロックを独占的に保持します。スレッドAがロックを解放するまで、スレッドBがロックを取得しようとすると失敗し、スレッドBはCPUを他のスレッドに譲渡し、そのロックコードをブロックします。ミューテックス取得失敗時のブロッキング動作はオペレーティングシステムのカーネルによって管理されます。スレッドがロックに失敗すると、カーネルはスレッドをスリープ状態に移し、ロックが利用可能になるとスレッドを覚醒させて、ロックを取得したらスレッドが実行を再開できるようにします。ミューテックスの動作の詳細なイラストについては、以下の図を参照してください。
![1](https://yqintl.alicdn.com/708315ce383a24c2abab1c015a53cec09e0db583.png)

ミューテックスに関連するパフォーマンスオーバーヘッドは主に2つのスレッドコンテキストスイッチングのコストです。スレッドがロックを取得できない場合、カーネルはスレッドの状態を実行中からスリープに変更し、CPUの制御を別のスレッドに移します。ロックが解放されると、以前にスリープしていたスレッドは準備完了状態に移り、その後カーネルは適切なタイミングでCPUをそのスレッドに戻します。

### スピンロック
スピンロックは、CPUが提供する原子操作CAS（CompareAndSet）を使用して、ユーザーモードでロックとアンロック操作を完了し、アクティブにスレッドコンテキストスイッチングを引き起こしません。そのため、ミューテックスと比較して、スピンロックは高速でオーバーヘッドが少ないです。スピンロックとミューテックスの主な違いは、ロックに失敗した場合、ミューテックスはスレッドスイッチングで応答するのに対し、スピンロックはビジーローティングで応答することです。

## Apache RocketMQのロック機構の進化
このセクションでは、Apache RocketMQのロック機構の進化について説明します。スピンロックとミューテックスにはそれぞれ長所と短所があります：スピンロックは軽量でコンテキストスイッチングのオーバーヘッドが少なく、短期間の待ち時間に適しています；一方、ミューテックスはリソースを節約するため、長期にわたってロックを
# 同期ディスクフラッシュの動作をX86アーキテクチャでの例として

[4](https://yqintl.alicdn.com/0a3553a077bf0fa56b7781b998046c07a04bd674.png)

実験結果によると、k=10^3の場合、送信速度がピーク（155019.20）に達し、CPU利用率が最も低くなります。これは、バックオフ戦略がCPUリソースを節約していることを示しています。この時点では、CPUは低い利用率でより高いパフォーマンスレベルをサポートしており、パフォーマンスボトルネックが移動した可能性があることを示唆しています。おそらくディスクに移動したと考えられます。上記の表は、同じk(10^3)と設定パラメータ（最新コード、SYNCフラッシングモード）を使用した場合、Apache RocketMQのパフォーマンスがARM CPUアーキテクチャで10.4%向上することを示しています。さらに、上図に示すように、k=10^3の場合、CPU利用率は平均1000%以上から約750%に大幅に減少します。リソース消費量の減少は、他のシステムボトルネックを緩和すると、より顕著なパフォーマンス改善につながる可能性があることを示唆しています。

## ABSロックで最適なパフォーマンスを達成

上記でk-バックオフスピンロックの有効性を示しました。しかし、まだ解決すべき問題があります：クリティカルセクションが十分に大きい場合、スピンロックのリソース消費はミューテックスよりも遥かに高くなることがわかりました。これは、複数のバックオフ後でもロックを取得できないためです。このような場合、k値は負の影響を与えます。最終的にはコンテキストスイッチングのコストを排除できず、スピンによる時間消費も引き起こします。したがって、さらなる最適化のためにk値を動的に調整することにしました。クリティカルセクションが大きい場合、kは適応的に増加します。リソース消費量がミューテックスと同じレベルになると（kが適応的な最大値に達する）、スピンロックはこのシナリオには適していないと判断されます。この時点で、ミューテックスに切り替えます。これが私たちの最終的な実装であるABSロックの中心的な考え方です。

### 1. ABSロックの実装

! [5](https://yqintl.alicdn.com/e26b8e0eff5727187fa33d62f5321e345ef6684c.png)

様々なロック機構について理論分析を行い、それぞれが最も適したシナリオを特定しました。その後、それらのシナリオで広範な実験テストを行って最適なロック機構を特定し、競合するスレッド数、TPS、メッセージサイズ、クリティカルセクションのサイズなどのランタイム条件に基づいて最適なロック機構に動的に切り替えるようにしました。

### 2. ABSロックの適応的K値戦略

上記で示したように、スピン回数を制御することはパフォーマンス最適化にポジティブな影響を持つことが明らかになりましたが、このK値は異なるシステムによって異なります。したがって、スピン回数(K)に対して適応的な戦略を採用する必要があります。要するに、クリティカルセクションの競合が増えるか、またはクリティカルセクションのサイズが大きくなるにつれて、適応的なK戦略は低頻度のスピンから高頻度のスピンへと進化します。kが徐々に増加するにつれて、スレッドがバックオフする前にロックを取得する確率が高まります。しかし、スピン回数が一定のオーダーに達すると、スピンのオーバーヘッドがスレッドコンテキストスイッチングのコストよりも高くなり、スピンロックがもう適していないことを示します。つまり、ミューテックスに退化します。実験により、スピン回数の適応的な最大k値を10,000に設定することが決定されました。これは、スピン回数が10,000を超えると、高競合下での最適化の利点が大きく影響を受け、単一のCPUコンテキストスイッチのコストを超えることがわかったためです。したがって、この時点でミューテックスのブロッキング待機機構に切り替えます。K値を適応的に調整するために、以下の図に示すようなクローズドループワークフローを提案します。

! [6](https://yqintl.alicdn.com/068d49d30e997f98fd88030f77f42259ba7a41c0.png)

図は、現在のk値でのスピンによるロック取得成功率を主に測定することを示しています。現在のk値でのロック取得成功率が十分でない場合、k値を段階的に増やしてロック取得の確率を高めます。しかし、k値を増やすことが効果的に成功確率を高めるものでない場合、ミューテックスへの切り替えの方が大きな利益を得ることができます。ミューテックスへの切り替えは、潜在的な高バーストトラフィックを示し、激しいロック競合につながります。しかし、ミューテックスは低競合シナリオには適していないため、ミューテックスからスピンロックへの切り替え方法も決定する必要があります。そのため、スピンロックがミューテックスに切り替わった際の要求レートを監視します。全体の要求レートが記録された値の80%未満になった場合、スピンロックに戻ります。

## 実験と結果

ABSロックの正確さとパフォーマンス最適化効果を検証するために、パフォーマンステストとカオス障害テストを含む複数の実験を行いました。このセクションでは具体的な実験設計と結果を説明します。

### 1. 設定

- 1つのnamesrv
- 1つのbroker
- 1つのopenmessage-benchmarkストレステストツール

上記3台のマシンの構成は以下の通りです：
- プロセッサ: 8 vCPUs
- メモリ: 16 GiB
- インスタンスタイプ: ecs.c7.2xlarge
- 公開ネットワーク帯域幅: 5Mbps
- 内部ネットワーク帯域幅: 5 Gbps/ 最大10 Gbps
- メッセージ本文サイズ: 1KBおよび2B
- この設定は、メッセージ送信時のクリティカルセクションのサイズに影響を与えるように設計されています。

### 2. 結果

(1) **CPU利用率とレイテンシーの最適化**

- メッセージ本文サイズが1 KBの場合、異なるメッセージ送信レートでのCPU利用率を記録しました。結果は以下の図に示します。

! [7](https://yqintl.alicdn.com/4860ed4fa7c28e8f13bf2b4487ef14c33a9f345a.png)

- 結果は、メッセージ数が増加するにつれて、適応型ロックが顕著な優位性を示し、CPU利用率を効果的に削減することを示しています。
- また、メッセージ送信中のP9999を記録しました。P9999は、尾部リクエストの99.99パーセンタイルの送信レイテンシを表し、この期間における最も遅いリクエスト時間を反映しています。結果は以下の図に示します。

! [8](https://yqintl.alicdn.com/bc543b04cc27bc560d8279425485d743aa48feba.png)

- 図からわかるように、異なるTPSの下で、適応型ロックはP9999のパフォーマンスを改善し、メッセージ送信中のロック競合によるレイテンシを効果的に削減します。ただし、尾部
# Apache RocketMQの適応型ロック機構の検証と混沌エンジニアリング

この目的は、システムが不確定かつ複雑な実際のシナリオで安定性を維持できるか、実際の問題に直面しても効率的に動作し続けることができるかを確認することです。Apache RocketMQに適応型ロック機構が導入された後、厳格な混沌エンジニアリング実験が行われました。これには、分散ノード間の通信障害やピーク負荷状況のシミュレーションなどが含まれます。これらのテストの目的は、ストレス下での新しい機構のパフォーマンスと回復能力を検証することでした。このような強度のストレステストを乗り越えることで、システムが動的な環境で高可用性を維持する能力を証明し、その成熟度と信頼性を確認することができます。要するに、混沌エンジニアリングを通じて、Apache RocketMQを実際のシミュレーションにさらし、高可用性を達成するための真の強さを測定します。このような耐障害性テストを通じて、ロック機構への調整がデータ書き込みの正確性に影響を与えないことを示すことを目指しています。

## (1) 設定
私たちの混沌テスト検証環境は以下の通り設定されています：

- 1つのnamesrv：namesrvプロセスを実行。
- 1つのopenchaos混沌テスター：brokerに制御コマンドを送信。
- 3つのbroker：同じクラスターに属し、それぞれbrokerプロセスを実行。

上記5台のマシンの構成は以下の通りです：
- プロセッサ: 8 vCPUs
- メモリ: 16 GiB
- インスタンスタイプ: ecs.c7.2xlarge
- 公開ネットワーク帯域幅: 5 Mbps
- 内部ネットワーク帯域幅: 5 Mbps / 最大10 Gbps

テスト中、以下のランダムなテストシナリオを設定しました。各シナリオは少なくとも30秒続き、その後次の障害を注入する前に30秒の回復期間があります。

- マシンクラッシュ: この混沌障害はkill -9コマンドを使用して注入され、指定された範囲内のプロセスをランダムに終了します。
- マシン停止: この混沌障害はkill -SIGSTOPコマンドを使用して注入され、プロセスが一時停止した状況をシミュレートします。

各シナリオは少なくとも5回テストされ、各テストは少なくとも60分続きます。

## (2) 結論
上記のすべてのシナリオについて、混沌テストの総時間は少なくとも以下になります：
- 2（シナリオ数）× 5（各シナリオのテスト回数）× 60（単一テストの時間）= 600分

各障害注入が30秒、各回復期間も30秒であることを考慮すると、障害は少なくとも600（600/1 = 600）回注入されます。実際には、注入回数とその期間は上記計算値よりもはるかに多いです。これらの文書化されたテスト結果では、Apache RocketMQでメッセージの損失はなく、障害注入前後のデータは強く一貫しています。

## まとめ
この記事では、Apache RocketMQのロック機構の進化と、適応型バックオフスピンロック機構（ABSロック）の設計と実装を実現しました。並列システムがますます複雑になるにつれて、効果的なロック管理戦略の展開はパフォーマンスを維持する鍵となります。したがって、この分野でのパフォーマンス最適化の可能性をさらに探索し、達成可能な限界を押し広げることを目指しています。将来は、様々な分散ロック機構や他の高度な概念をABSロック機構と統合し、その実装とパフォーマンステストを行う計画です。単一の通信でマルチエンドローカルロックコンセンサスを達成することで、不要なネットワーク通信を減らし、トータルバスリソース消費を低減することを目指しています。ただし、現在の焦点はメッセージ送信中のロック機構の実装であり、具体的な実装とテストはまだ行われていません。

### 将来の潜在的なロック最適化アイデア
- **遅延カードスロットアイデア**:
  各クライアントに異なる遅延スロットを割り当て、遅延後にリクエストを再試行させます。これにより、バス上の大量の不要な衝突を回避できます。
  
- **CLHアイデア**:
  CLHロックは、先行ノードの値を連続的に読み取ることで状態を更新します。
  ! [11](https://yqintl.alicdn.com/18b44be4c5c94fc811e8efd8e0fd55096c56ca10.png)

- **MCSアイデア**:
  MCSロックはCLHロックに似ていますが、次ノードの状態をローカルでスピンして更新します。これは、NUMA（非一様メモリアクセス）システムアーキテクチャでロックドメインのステータスを取得するメモリアクセスが遠すぎるというCLHロックの問題を解決します。
  ! [12](https://yqintl.alicdn.com/83b6965d47ce0c5513e4bab273363799c5cd640e.png)
  ! [13](https://yqintl.alicdn.com/2064b5347d04a0e87247318e8761018c61a94b7b.png)

- **SMP CPUアーキテクチャ**
  ! [14](https://yqintl.alicdn.com/eaf09564831605a79579550c05d9addc304bac49.png)

- **P-persistent CSMAに基づく**:
  P-persistent CSMA（Persistent Carrier Sense Multiple Access）は、無線LAN（Wi-Fi）などの分散環境での衝突回避戦略を説明するためにしばしば使用されるネットワーク通信プロトコルの概念です。この概念はCSMA/CD（Carrier Sense Multiple Access with Collision Detection）プロトコルから派生していますが、より持続的なリスニングメカニズムを導入しています。
  1. 基本的なプロセス: 各デバイス（ノード）はデータ送信前にチャンネルが空いているかどうかを継続的に監視します。チャンネルがビジーな場合、ノードはしばらく待ってから再度試みます。
  2. Pカウンター: チャンネルがビジーであることを検出した場合、ノードはすぐに待機状態に戻らず、P（持続期間）カウンターを開始します。カウンターが終了したら、ノードは再び送信を試みます。
  3. 衝突処理: 複数のノードが同時に送信を開始し、チャンネル上で衝突が発生した場合、関与したすべてのノードはそれを認識し、それぞれのPカウンターをインクリメントして再試行します。これにより、パケットがより長い期間にわたって通過する確率が高まり、全体的な効率が向上します。
  4. 復旧フェーズ: Pカウンターがゼロになり、チャンネルがビジーな場合、ノードは復旧フェーズに入り、新たなランダムな遅延時間を選択してから再度送信を試みます。
  P-persistent CSMAは、この設計により不要な衝突の発生を減らしますが、ネットワーク渋滞時には長時間のアイドル待機時間が発生する可能性があります。
  ! [15](https://yqintl.alicdn.com/5d9b85501c71a50df74232e5acb3faebb4e31b9c.png)

## 参考文献
[1] Apache RocketMQ<a href=https://github.com/apache/rocketmq target=_blank>https://github.com/apache/rocketmq</a>
[2] OpenMessaging OpenChaos<a href