Kafkaアーキテクチャの解説:コアコンポーネントとその役割

Apache Kafkaの分散イベントストリーミングアーキテクチャの基本的な構成要素を探ります。このガイドでは、Kafka Broker、Topic、Partition、Producer、Consumerの役割、およびZooKeeperの調整役について明確に解説します。これらのコンポーネントがどのように相互作用し、高スループットで耐障害性の高いデータ処理とストレージを保証しているかを学びましょう。これは、あらゆるKafka実装に不可欠な知識です。

Kafkaアーキテクチャ解説:コアコンポーネントとその役割

Kafkaのアーキテクチャは、同じシステムがストレージ、ストリーミング、レプリケーション、コンシューマーの進行状況を処理するため、最初は混乱して見えるかもしれません。主要な部分を分離すると、モデルははるかに簡単になります。プロデューサーはレコードをトピックパーティションに書き込み、ブローカーはそれらのパーティションを保存し、コンシューマーはオフセットによってレコードを読み取ります。

このガイドでは、Kafkaのコアコンポーネントと、それらが実際のクラスターでどのように連携するかを説明します。

ブローカー:Kafkaサーバー

Kafkaクラスターは、1つ以上のブローカーで構成されています。ブローカーは、パーティションデータを保存し、プロデューサーとコンシューマーからのクライアントリクエストを処理するKafkaサーバーです。

プロデューサーがレコードを送信すると、現在ターゲットパーティションをリードしているブローカーに書き込みます。コンシューマーがレコードを読み取るときは、そのパーティションを提供するブローカーからフェッチします。通常の設定では、各ブローカーは多くのトピックからの多くのパーティションを処理します。

ブローカーを追加すると、ストレージ容量を増やし、トラフィックを分散できますが、すべてのボトルネックを自動的に修正するわけではありません。十分なパーティション、バランスの取れたレプリカ配置、健全なディスク、およびネットワーク容量も必要です。

トピック:名前付きレコードストリーム

トピックは、orderspaymentsuser_activityなどの名前付きレコードストリームです。プロデューサーはトピックに書き込み、コンシューマーはトピックを購読します。

トピックはパーティションに分割されます。各パーティションは、順序付けられた追加専用ログです。Kafkaは、単一パーティション内でのレコードの順序を保証しますが、トピック全体では保証しません。

その詳細は重要です。1人の顧客のすべてのイベントを順番に処理する必要がある場合は、customer_idなどの安定したキーを使用します。Kafkaのデフォルトのパーティショナーはキーを使用してパーティションを選択するため、同じキーを持つレコードは通常、同じパーティションに送られます。

パーティションとオフセット

パーティション内の各レコードにはオフセットが割り当てられます。オフセットは、そのパーティション内でのレコードの位置を識別する番号です。

たとえば、3つのパーティションを持つordersという名前のトピックは、次のようになります。

orders-0: offset 0, offset 1, offset 2
orders-1: offset 0, offset 1
orders-2: offset 0, offset 1, offset 2, offset 3

オフセットは、自身のパーティション内でのみ意味があります。orders-2のオフセット3は、別のパーティションのオフセット3とは関係ありません。

パーティションはKafkaに並列性をもたらします。パーティションが多いほど、同じコンシューマーグループ内のより多くのコンシューマーが同時に作業できるようになり、そのグループ内ではパーティションごとに1つのアクティブなコンシューマーまで可能です。

レプリケーションとリーダー

Kafkaはレプリケーションを使用して、ブローカーに障害が発生した場合でもデータを利用可能に保ちます。各パーティションは、異なるブローカー上に複数のレプリカを持つことができます。

1つのレプリカがリーダーです。プロデューサーとコンシューマーは通常、そのパーティションのリーダーと通信します。他のレプリカはフォロワーです。フォロワーはリーダーからデータをコピーし、リーダーに障害が発生した場合に引き継ぐ準備を整えています。

レプリケーションファクターは、Kafkaが保持するコピーの数を制御します。レプリケーションファクターが3の場合、十分なブローカーが利用可能であれば、Kafkaは各パーティションの3つのコピーを3つのブローカーに保存します。

次のようにしてトピックを作成できます。

kafka-topics.sh --create \
  --topic user_activity \
  --bootstrap-server localhost:9092 \
  --partitions 3 \
  --replication-factor 3

このコマンドには、少なくとも3つのブローカーを持つクラスターが必要です。シングルブローカーのローカル設定では、レプリケーションファクターを1にします。

プロデューサー:イベントを書き込むアプリケーション

プロデューサーは、Kafkaトピックにレコードを送信します。レコードには、キー、値、タイムスタンプ、およびヘッダーを含めることができます。

プロデューサーは最初にクラスターにメタデータを要求して、各パーティションをどのブローカーがリードしているかを把握します。次に、レコードを適切なブローカーに直接送信します。

プロデューサーの信頼性は、次のような設定に大きく依存します。

設定 影響するもの
acks 書き込みが成功と見なされる前に必要なブローカーの確認応答の数
retries プロデューサーが一時的な障害を再試行するかどうか
enable.idempotence プロデューサーの再試行によって発生する重複を回避するのに役立つ
compression.type 多くのワークロードでネットワークとディスクの使用量を削減する

重要なデータの場合、acks=allが一般的です。これは、リーダーが書き込みを確認する前に、同期中のレプリカが応答するのを待つためです。正確な動作は、min.insync.replicasなどのブローカー設定にも依存します。

コンシューマーとコンシューマーグループ

コンシューマーはトピックからレコードを読み取ります。ほとんどの本番環境のコンシューマーは、コンシューマーグループ内で実行されます。

1つのコンシューマーグループ内では、Kafkaは各パーティションを一度に1つのアクティブなコンシューマーにのみ割り当てます。これにより、Kafkaは各パーティション内の順序を維持しながら、処理をスケーリングできます。

たとえば、ordersに3つのパーティションがあり、サービスに同じグループ内に3つのコンシューマーがある場合、各コンシューマーは1つのパーティションを処理できます。同じグループに4番目のコンシューマーを追加すると、割り当て可能なパーティションが3つしかないため、1つのコンシューマーはアイドル状態になります。

異なるコンシューマーグループは独立した読み取りを取得します。請求サービスと分析サービスの両方が、互いのレコードを奪い合うことなくordersトピックを読み取ることができます。

オフセットとコンシューマーの進行状況

コンシューマーは、オフセットをコミットすることで進行状況を追跡します。Kafkaは、コンシューマーグループのコミットされたオフセットを、__consumer_offsetsという内部トピックに保存します。

コンシューマーがクラッシュして再起動した場合、コミットされたオフセットを使用して再開します。コミットのタイミングは、処理動作に影響します。

コミットのタイミング 考えられる結果
処理が完了する前にコミット クラッシュによりレコードがスキップされる可能性がある
処理が完了した後にコミット クラッシュによりレコードが再処理される可能性がある

多くのシステムは、少なくとも1回の処理を選択します。レコードを処理してからオフセットをコミットします。これにより、クラッシュ後に重複が発生する可能性があるため、可能な場合はダウンストリームの書き込みをべき等にする必要があります。

クラスターメタデータ:ZooKeeperとKRaft

古いKafkaクラスターは、Apache ZooKeeperを使用してクラスターメタデータとコントローラーの選出を管理します。多くの既存のインストールは、依然としてこの方法で動作しています。

新しいKafkaデプロイメントでは、Kafkaの組み込みメタデータクォーラムであるKRaftモードを使用できます。KRaftクラスターでは、Kafkaはメタデータ管理のためにZooKeeperに依存しなくなります。

古いKafkaチュートリアルを読むときは、ZooKeeperとKRaftのどちらを前提としているかを確認してください。コマンド、設定ファイル、および運用手順が異なる場合があります。

レコードがKafkaを通過する方法

典型的な書き込みと読み取りのフローは次のようになります。

  1. プロデューサーがブートストラップブローカーに接続し、メタデータをフェッチします。
  2. プロデューサーは、レコードキーまたはパーティショニング戦略に基づいてパーティションを選択します。
  3. プロデューサーは、そのパーティションのリーダーブローカーにレコードを送信します。
  4. リーダーはレコードをログに追加し、フォロワーがそれをレプリケートします。
  5. リーダーは、プロデューサーのacks設定に基づいて書き込みを確認します。
  6. コンシューマーがパーティションをポーリングし、現在のオフセットからレコードを受信します。
  7. コンシューマーはレコードを処理し、コンシューマーグループのオフセットをコミットします。

このフローが、Kafkaがリアルタイム処理とリプレイの両方をサポートできる理由です。コンシューマーはレコードを読み取っても削除しません。

保持:Kafkaはポリシーに基づいてデータを保持する

Kafkaは、メッセージが1つのコンシューマーによって読み取られるとすぐに消える従来のキューではありません。Kafkaは、保持設定に基づいてレコードを保持します。

一般的なトピック設定は次のとおりです。

retention.ms=604800000
retention.bytes=10737418240

retention.msは時間ベースの保持を制御します。retention.bytesはサイズベースの保持を制御します。実際のクリーンアップは、セグメント設定とブローカー構成にも依存します。

一部のトピックでは、削除ベースの保持に代えて、またはそれと併用してログコンパクションを使用します。コンパクションは、各キーの最新の値を保持します。これは、ユーザープロファイルや構成変更などの状態のようなトピックに役立ちます。

覚えておくべきこと

Kafkaのアーキテクチャは、パーティション化されたログを中心に構築されています。ブローカーはパーティションを保存し、プロデューサーはパーティションリーダーに書き込み、コンシューマーはオフセットで読み取り、コンシューマーグループはパーティション間で作業を分割します。

Kafkaトピックを設計するときは、順序、パーティション数、レプリケーションファクター、保持、およびコンシューマーグループの動作を一緒に考えてください。これらの選択によって、システムのスケーリング、障害からの回復、および古いイベントのリプレイ方法が決まります。