DUICUO

安価なマシン3台で1秒あたり200万レコードを書き込み!Kafkaはなぜこんなに速いのか?

Kafka のメッセージはディスクに保存またはキャッシュされます。一般的に、ディスク上のデータの読み書きはアドレス指定に時間がかかるためパフォーマンスが低下すると考えられていますが、実際には、Kafka の特長の一つは高いスループットです。

Kafka は一般的なサーバーでも、1秒あたりの書き込みリクエストを容易にサポートでき、ほとんどのメッセージブローカーを凌駕します。この機能により、Kafka はログ処理など、大量のデータを扱うシナリオで広く利用されています。

Kafka のベンチマーク テストについては、Apache Kafka ベンチマーク テスト (クリックすると元の記事が読めます)「2 Million Writes Per Second (on Three Inexpensive Machines)」を参照してください。

  1. http://ifeve.com/benchmarking-apache-kafka-2-million-writes-second-three-cheap-machines/

次の分析では、データの書き込みと読み取りの両方に焦点を当てて、Kafka がなぜ高速なのかを検証します。

データの書き込み

Kafka は受信したすべてのメッセージをディスクに書き込み、データ損失を防ぎます。書き込み速度を最適化するために、Kafka はシーケンシャル書き込みと MMFile(メモリマップファイル)という 2 つの手法を採用しています。

シーケンシャルライト

ディスクの読み書き速度は、シーケンシャル読み書きかランダム読み書きかなど、使用方法によって異なります。シーケンシャル読み書きの場合、ディスクのシーケンシャル読み書き速度はメモリの速度と同じです。

ハード ドライブは機械的なため、各読み取り/書き込み操作にはアドレス指定と書き込みが伴い、アドレス指定は最も時間のかかる「機械的な操作」です。

そのため、ハードドライブはランダムI/Oを嫌い、シーケンシャルI/Oを好みます。ハードドライブへの読み書き速度を向上させるために、KafkaはシーケンシャルI/Oを使用します。

さらに、Linux は、先読み、後書き、ディスク キャッシュなど、ディスクの読み取りおよび書き込み操作に対してさまざまな最適化を提供します。

これらの操作をメモリ内で実行すると、Java オブジェクトによってメモリのオーバーヘッドが大幅に増加するという問題があり、また、ヒープ内のデータ量が増加すると、Java のガベージ コレクション (GC) 時間が非常に長くなるという問題もあります。

ディスク操作を使用すると、次の利点があります。

  • ディスクのシーケンシャル読み取り/書き込み速度は、メモリのランダム読み取り/書き込み速度を上回ります。
  • JVMのガベージコレクション(GC)は非効率で、大量のメモリを消費します。ディスクを使用することでこの問題を回避できます。
  • ディスク キャッシュは、システムのコールド ブート後も引き続き使用できます。

下の図は、Kafka がデータを書き込む方法を示しています。各パーティションは基本的にファイルであり、メッセージを受信すると、Kafka はファイルの末尾(点線の枠)にデータを挿入します。

この方法には欠点があります。データを削除する方法がないのです。そのため、Kafka はデータを削除せず、すべてのデータを保持します。各コンシューマーは、トピックごとにオフセットを持ち、どのデータ項目が読み取られたかを示します。

2 人の消費者:

  • Consumer1 には、それぞれ Partition0 と Partition1 に対応する 2 つのオフセットがあります (トピックごとに 1 つのパーティションがあると想定)。
  • Consumer2 には、Partition2 に対応するオフセットがあります。

このオフセットはクライアント SDK によって保存され、Kafka ブローカーはその存在を完全に無視します。

通常、SDK はそれを Zookeeper に保存するので、コンシューマーに Zookeeper アドレスを提供する必要があります。

データを削除しないと、ハードドライブは確実にいっぱいになります。そのため、Kakfa ではデータを削除するための 2 つの戦略を提供しています。

  • 時間に基づいて
  • パーティションファイルサイズに基づく

詳細な構成情報については、構成ドキュメントを参照してください。

メモリマップファイル

ディスクへのシーケンシャル書き込みを行っても、ディスクアクセス速度はメモリに追いつくことができません。そのため、Kafka はデータをリアルタイムでディスクに書き込むのではなく、最新のオペレーティングシステムのページングストレージを最大限に活用して、メモリを使用した I/O 効率を向上させます。

メモリマップファイル(以下、mmap)は、メモリマップファイルとも呼ばれます。64ビットオペレーティングシステムでは、通常20GBのデータファイルを表すことができます。動作原理は、オペレーティングシステムのページを直接使用して、ファイルを物理メモリに直接マッピングすることです。

マッピングが完了すると、物理メモリ上の操作がハードドライブに同期されます (オペレーティング システムによって適切なタイミングで)。

mmap を使用すると、プロセスはハード ドライブの読み取りと書き込みと同じようにメモリ (仮想マシン メモリ) の読み取りと書き込みを実行できます。残りの処理は仮想メモリが行うため、メモリのサイズを気にする必要はありません。

この方法は、ユーザー空間からカーネル空間へのデータのコピーのオーバーヘッドを排除することで、大幅な I/O 改善を実現します。(ファイルに対して `Read` を呼び出すと、まずデータがカーネル空間メモリに配置され、次にユーザー空間メモリにコピーされます。)

しかし、これには重大な欠点もあります。それは、信頼性が低いということです。mmapに書き込まれたデータは、実際にはハードドライブに書き込まれません。オペレーティングシステムは、プログラムがFlushを明示的に呼び出した場合にのみ、データをハードドライブに書き込みます。

Kafka は、アクティブ フラッシュを実行するかどうかを制御するパラメータ `producer.type` を提供します。

  • Kafka が mmap に書き込み、プロデューサーに戻る前にすぐにフラッシュする場合は、Sync と呼ばれます。
  • Kafka が mmap に書き込み、Flush を呼び出さずにすぐに Producer に戻る場合、これは非同期と呼ばれます。

データの読み取り

Kafka はディスクから読み取るときにどのような最適化を行いますか?

Sendfileに基づくゼロコピー

従来のモデルでは、ファイルを転送するための具体的なプロセスは次のとおりです。

  • Read 関数が呼び出されると、ファイル データがカーネル バッファーにコピーされます。
  • Read 関数が戻ると、ファイル データはカーネル バッファーからユーザー バッファーにコピーされています。
  • 書き込み関数呼び出しは、ユーザー バッファーからカーネルのソケット関連バッファーにファイル データをコピーします。
  • データはソケット バッファから関連するプロトコル エンジンにコピーされます。

上記は、ネットワーク経由でファイルを転送するための従来の読み取り/書き込み方式の詳細です。このプロセスでは、ファイルデータは実際には4つのコピー操作を経ることがわかります。

ハードディスク -> カーネルバッファ -> ユーザーバッファ -> ソケット関連バッファ -> プロトコルエンジン

Sendfile システム コールは、上記のコピー数を減らし、ファイル転送のパフォーマンスを向上させる方法を提供します。

カーネル バージョン 2.1 では、ネットワーク経由および 2 つのローカル ファイル間のデータ転送を簡素化するために Sendfile システム コールが導入されました。

Sendfile の導入により、データのコピーが削減されるだけでなく、コンテキストの切り替えも削減されます。

  1. sendfile(ソケット、ファイル、長さ);

操作手順は以下のとおりです。

  • Sendfile システム コールは、ファイル データをカーネル バッファーにコピーします。
  • 次に、カーネル バッファからカーネル内のソケット関連バッファにコピーします。
  • ***ソケット関連のバッファはプロトコル エンジンにコピーされます。

従来の読み取り/書き込み方式と比較して、カーネル バージョン 2.1 で導入された Sendfile により、カーネル バッファーからユーザー バッファーへのファイル コピー プロセス、そしてユーザー バッファーからソケット関連バッファーへのファイル コピー プロセスが削減されました。

カーネル バージョン 2.4 以降では、ファイル記述子の結果が変更され、Sendfile はより簡単な方法を実装し、1 回のコピー操作がさらに削減されました。

Apache、Nginx、Lighttpd などの Web サーバーにはすべて Sendfile 構成オプションがあり、ファイル転送のパフォーマンスを大幅に向上させることができます。

Kafka はすべてのメッセージを個別のファイルに保存します。コンシューマーがデータを必要とする場合、Kafka は mmap をファイルの読み取り/書き込みメソッドとして使用してファイルを直接コンシューマーに送信し、その後 Sendfile に直接渡します。

バッチ圧縮

多くの場合、システムのボトルネックとなるのは CPU やディスクではなく、ネットワーク I/O です。特に、広域ネットワーク上のデータセンター間でメッセージを送信する必要があるデータ パイプラインの場合はそれが顕著です。

データ圧縮では CPU リソースが少量消費されますが、Kafka の場合はネットワーク I/O をより慎重に考慮する必要があります。

  • 各メッセージは圧縮されますが、圧縮率は比較的低いため、Kafka では、個々のメッセージを圧縮するのではなく、複数のメッセージをまとめて圧縮するバッチ圧縮を使用します。
  • Kafka では再帰メッセージ セットの使用が許可されており、メッセージのバッチを圧縮形式で送信し、コンシューマーによって解凍されるまでログに圧縮形式で残すことができます。
  • Kafka は、Gzip や Snappy など複数の圧縮プロトコルをサポートしています。

要約

Kafka の速度の秘密は、すべてのメッセージをバッチ ファイルに変換し、適切なバッチ圧縮を実行してネットワーク I/O オーバーヘッドを削減し、mmap を通じて I/O 速度を向上させる機能にあります。

データの書き込み時は各パーティションが末尾に追加されるため速度が非常に速く、データの読み取り時は Sendfile を使用して直接出力されます。