|
Kafka はどのようなパフォーマンス最適化対策を講じていますか?これは Kafka の面接でよく聞かれる質問であり、面接官にとって難しい質問とは考えられていません。「Kafka はなぜこんなに速いのか?」といった広く流布している記事をはじめ、この質問について解説した記事はオンラインで数多くあります。これらの記事はよく書かれていますが、要点を列挙しているだけで、すべてを詳しく説明しているわけではありません。この記事では、オンラインで見つけられるよりも多くの要点が列挙されています。この記事を読んだ後に面接でこの質問を受けた場合、面接官に間違いなく好印象を与えられるでしょう。
バッチ処理 従来のメッセージブローカーは、メッセージの送信と消費を単一メッセージ単位で処理します。プロデューサーはまずメッセージを送信し、ブローカーは受信を示すACKを返します。これにより、2つのRPCが発生します。コンシューマーは、まずメッセージの受信を要求し、ブローカーはメッセージを返し、最後に消費を示すACKを送信します。これにより、3つのRPCが発生します(一部のメッセージブローカーは、ブローカーの応答で複数のメッセージを返すことで、この処理を最適化しています)。しかし、Kafkaはバッチ処理を使用します。プロデューサーは複数のメッセージをまとめてバッチ処理し、それらをブローカーに保存するために2つのRPCを実行します。これは通常、多数のRPCを必要とする操作です。例えば、1KBのメッセージを1000件送信する必要がある場合、従来のメッセージブローカーでは2000回のRPCが必要になりますが、Kafkaではこれらの1000件のメッセージを1MBの単一のメッセージにパッケージ化することで、わずか2回のRPCでタスクを完了できます。この改善はかつては「ごまかし」と考えられていましたが、マイクロバッチ処理の台頭により、他のメッセージブローカーもこれに追随し始めました。 クライアントの最適化 バッチ処理の概念を踏襲しつつ、新しいプロデューサークライアントは従来のシングルスレッド方式を廃止し、メインスレッドと送信スレッドからなるデュアルスレッドアーキテクチャを採用しています。メインスレッドはクライアントのキャッシュにメッセージを配置する役割を担い、送信スレッドはキャッシュからメッセージを送信する役割を担います。これにより、複数のメッセージがバッチ処理に集約されます。一部のメッセージブローカーは、メッセージをブローカーに直接送信します。 ログ形式 Kafka のログ形式は、バージョン 0.8 以降、v0、v1、v2 の 3 つの変更が行われました。 ログエンコーディング Kafka のログ形式(上の画像を参照)を理解している方は、基本的なキーと値に加えて、ログ(またはメッセージ)には他のフィールドも含まれていることをご存知でしょう。従来、これらの追加フィールドは固定長のスペースを占有していました(上の画像左側を参照)。しかし、最新バージョンの Kafka では可変長フィールド(Varint)と ZigZag エンコーディングが採用されており、これらの追加フィールドのサイズが効果的に削減されています。ログ(またはメッセージ)のサイズが小さくなることで、ネットワーク転送効率とログ保存効率が向上し、全体的なパフォーマンスが向上します。 メッセージ圧縮 Kafka は様々なメッセージ圧縮方式(gzip、snappy、lz4)をサポートしています。メッセージ圧縮はネットワークトラフィックとネットワークI/Oを大幅に削減し、全体的なパフォーマンスを向上させます。メッセージ圧縮は、時間とスペースをトレードオフする最適化手法です。レイテンシが重要な要素である場合は、メッセージ圧縮は推奨されません。 インデックスを作成する 各ログ セグメント ファイルは 2 つのインデックス ファイルに対応しており、主にメッセージの検索効率を向上させるために使用され、パフォーマンスを向上させる方法でもあります (具体的な内容については、本書の第 5 章で詳しく説明されています)。 パーティション 多くの人が見落としがちですが、パーティショニングは実際にはパフォーマンス向上に非常に効果的な手段であり、その効果は前述のログエンコーディングやメッセージ圧縮といったものよりも顕著です。パーティショニングは他の分散コンポーネントでも広く利用されているため、ここではパーティショニングがパフォーマンスを向上させる基本的な理由については詳しく説明しません。ただし、パーティションの数を増やすだけでは必ずしもパフォーマンス向上につながらないことに注意することが重要です。興味のある方は、こちらの記事「Kafkaトピックのパーティション数を増やすと、必ずスループットが向上するのか?」をご覧ください。 一貫性 Kafka のパフォーマンス最適化策を議論するリソースのほとんどは、一貫性について言及していません。Paxos、Raft、Gossip といった一般的な一貫性プロトコルはよく知られていますが、Kafka の Pacific-A に似た代替アプローチは、衝動的な決定ではありませんでした。このモデルは処理効率を向上させます。具体的な詳細については、「Kafka で Pacific-A を Raft に置き換えることの実現可能性分析とメリット/デメリット」のような記事で後日取り上げます。 シーケンシャルディスク書き込み オペレーティングシステムは、線形の読み書き操作に対して、先読み(大きなディスクブロックを事前にメモリに読み込む)や後書き(多数の小さな論理書き込み操作を1つの大きな物理書き込み操作に統合する)といった高度な最適化を実行できます。Kafka は設計上、メッセージの書き込みに追記のみのアプローチを採用しています。つまり、新しいメッセージはログファイルの末尾にのみ追加でき、既に書き込まれたメッセージへの変更は許可されません。これは典型的なシーケンシャル書き込み操作であるため、Kafka はストレージ媒体としてディスクを使用していますが、スループットは非常に優れています。 ページキャッシュ Kafka がなぜこれほど高性能なのか?この質問に直面すると、多くの人は前述のシーケンシャルディスク書き込み機能を思い浮かべるでしょう。しかし実際には、シーケンシャルディスク書き込み機能の前に、PageCache と呼ばれる最適化レイヤーが存在します。 ページキャッシュは、ディスクI/O操作を削減するためにオペレーティングシステムによって実装される主要なディスクキャッシュ方式です。具体的には、ディスクからのデータをメモリにキャッシュし、ディスクアクセスをメモリアクセスに変換します。パフォーマンスの違いを補うため、最近のオペレーティングシステムはメモリをディスクキャッシュとして「積極的に」使用する傾向が強まっており、場合によっては利用可能なメモリをすべて使い切ることもあります。これにより、メモリが再利用されてもパフォーマンスの低下はほとんどなく、すべてのディスク読み取りと書き込みは統合キャッシュを経由します。 プロセスがディスクからファイルの内容を読み取る準備をするとき、オペレーティング システムはまず、読み取るデータを含むページがページ キャッシュ内にあるかどうかを確認します。ページ キャッシュ内にページ キャッシュが存在する場合 (ページ キャッシュ ヒット)、データが直接返されるため、物理ディスクに対する I/O 操作は回避されます。ページ キャッシュ ミスの場合、オペレーティング システムはディスクに読み取り要求を送信し、読み取ったデータ ページをページ キャッシュに格納してから、データをプロセスに返します。同様に、プロセスがディスクにデータを書き込む必要がある場合も、オペレーティング システムは対応するページがページ キャッシュ内にあるかどうかを確認します。ページ キャッシュ内にページ キャッシュが存在しない場合、オペレーティング システムはまず対応するページをページ キャッシュに追加し、次にデータを対応するページに書き込みます。変更されたページはダーティ ページになり、オペレーティング システムはデータの一貫性を維持するために、ダーティ ページのデータを適切なタイミングでディスクに書き込みます。 プロセスは必要なデータを内部的にキャッシュします。しかし、このデータはオペレーティングシステムのページキャッシュにもキャッシュされる可能性があり、同じデータが2回キャッシュされる可能性があります。さらに、Direct I/Oを使用しない限り、ページキャッシュを無効にすることは困難です。さらに、Javaに精通している人は一般的に2つの事実を知っています。オブジェクトはメモリオーバーヘッドが非常に高く、実際のデータサイズの数倍以上になることが多く、その結果、スペース利用率が低くなります。また、Javaのガベージコレクションは、ヒープ内のデータ量が増えるにつれて速度が徐々に低下します。これらの要因に基づくと、ファイルシステムを使用してページキャッシュに依存する方が、インプロセスキャッシュなどの構造を維持するよりも明らかに優れています。少なくとも、オブジェクトの代わりにコンパクトなバイトコードを使用することで、インプロセスキャッシュのオーバーヘッドを回避し、より多くのスペースを節約できます。したがって、32GBのマシンで28GBから30GBのメモリを使用しても、GCによるパフォーマンスの問題を心配する必要はありません。さらに、Kafkaサービスが再起動しても、ページキャッシュは有効なままですが、インプロセスキャッシュは再構築する必要があります。また、これにより、ページ キャッシュとファイル間の一貫性の維持がオペレーティング システムによって処理されるため、コード ロジックが大幅に簡素化され、プロセス内で維持するよりも安全かつ効率的になります。 Kafka はページキャッシュを多用しており、これが Kafka の高いスループットの重要な要素の一つとなっています。メッセージはまずページキャッシュに書き込まれますが、その後のディスクへの実際のフラッシュはオペレーティングシステムが処理します。 ゼロコピー 以前「ゼロコピーとは何か?」という記事を公開しましたので、もしKafkaについてよく知らない方はそちらを参考にしてください。Kafkaは消費効率を向上させるためにゼロコピー技術を採用しています。前述の通り、Kafkaはまずメッセージをページキャッシュに書き込みます。コンシューマーがメッセージを読む際にページキャッシュ内でメッセージを見つけることができれば、ページキャッシュから直接読み取ることができるため、ディスクからページキャッシュへのコピーにかかるオーバーヘッドを削減できます。さらに、ライトアンプリフィケーションとリードアンプリフィケーションについて学ぶことで、読み取りと書き込みの概念を理解することができます。 付録 ディスク I/O プロセスは次の図に示されています。 詳細な分析については、「Linux IO ディスクに関する簡単なメモ」を参照してください。 結論は この記事で紹介するKafkaのパフォーマンス最適化のヒントを活用すれば、面接でKafka関連の質問を受けた際に、自信を持ってスキルをアピールできるようになります。ぜひ習得し、マスターしてください! |