Kafkaレプリケーション設定:データの永続性と可用性を確保する

永続性を損なわずにKafkaのレプリケーション、ISR、プロデューサーの確認応答、ラック認識を設定する方法。

Kafkaレプリケーション設定:データの永続性と可用性を確保する

Kafkaのレプリケーション設定は、クラスターが単なるブローカーの集まりから、障害時に信頼できるシステムへと変わるポイントです。設定自体は複雑ではありません。レプリケーションファクター、同期レプリカ、プロデューサーの確認応答、リーダー選出、ラック配置です。難しいのは、これらが組み合わさって初めて意味をなすことです。

3つのレプリカを持つトピックでも、プロデューサーが弱い確認応答を使用していると、確認済みのデータが失われる可能性があります。acks=allを使用するプロデューサーでも、min.insync.replicasが現在稼働中のブローカー数に対して厳しすぎると、書き込みが失敗することがあります。可用性ゾーンに分散されたクラスターでも、ホットパーティションのすべてのレプリカが同じ障害ドメインに配置されると、問題が発生する可能性があります。レプリケーションは単一のチェックボックスではありません。

私がKafkaのレプリケーションを考えるシンプルな方法は次の通りです。各パーティションについて、Kafkaは複数のコピーを保持し、読み取りと書き込みを受け付ける1つのコピーを選択し、他のコピーを引き継げる状態に保ちます。あなたの仕事は、十分なコピーの数、書き込みが成功とみなされる前に同期していなければならないコピーの数、そしてクラスターがデータの安全性よりも可用性を優先すべきかどうかを決定することです。

Kafkaトピックはパーティションに分割されます。各パーティションには1つのリーダーレプリカと0個以上のフォロワーレプリカがあります。プロデューサーはリーダーに書き込みます。コンシューマーは通常リーダーから読み取ります。フォロワーはリーダーからレコードを取得し、ローカルログを同期します。リーダーのブローカーが故障した場合、Kafkaは安全な候補と見なされるレプリカから新しいリーダーを選出します。

その安全な候補リストがISR(In-Sync Replicas)です。レプリカがISRに含まれるのは、Kafkaのレプリカラグルールに従ってリーダーに十分追従している場合です。フォロワーがフェッチを停止したり、長時間遅れたり、ブローカーが消失した場合、KafkaはそのレプリカをISRから削除します。追いついた場合、再び参加できます。

この詳細が重要なのは、ISRがKafkaの永続性を単なる願望以上のものにするからです。acks=allの場合、リーダーはレコードが必要な同期レプリカにレプリケートされるまでプロデュースリクエストを確認応答しません。正確な要件はmin.insync.replicasで制御されます。トピックがreplication.factor=3min.insync.replicas=2の場合、Kafkaはacks=allの書き込みが成功する前に少なくとも2つの同期レプリカが必要です。

この組み合わせは、実用的なバランスを提供するため、本番環境で一般的です。1つのブローカーが故障しても、トピックは強力に確認応答された書き込みを受け入れ続けられます。最初のブローカーが復旧する前に2つ目のブローカーが故障した場合、acks=allを使用するプロデューサーはNotEnoughReplicasNotEnoughReplicasAfterAppendなどのエラーを確認し始めるはずです。これはインシデント中は厄介ですが、通常は正しい動作です。Kafkaは、十分な安全なコピーがない場合に書き込みが永続的であると偽ることを拒否しています。

以下は、通常の3ブローカー以上のクラスターにおける典型的な本番ベースラインです。

default.replication.factor=3
min.insync.replicas=2
unclean.leader.election.enable=false

これらの値はすべてのワークロードを自動的に安全にするわけではありませんが、妥当な出発点を提供します。default.replication.factor=3は、トピック作成コマンドで別途指定がない限り、新しいトピックが3つのコピーを取得することを意味します。min.insync.replicas=2は、強力な書き込みのために少なくとも2つのレプリカが同期している必要があることを意味します。unclean.leader.election.enable=falseは、パーティションを書き込み可能に保つためだけに、古いレプリカをリーダーとして選出しないようKafkaに指示します。

レプリケーションファクターをブローカー数より高く設定しないでください。Kafkaは、ブローカーが2つしかない場合、3つのレプリカを3つの異なるブローカーに配置できません。小規模な開発クラスターでは、利便性が障害耐性よりも重要であるため、replication.factor=1で問題ありません。本番環境では、1は単一のブローカー損失でデータが利用不可になり、そのブローカーのみに保存されたレコードが永久に失われる可能性があります。

プロデューサー側はトピック側と一致している必要があります。重要なデータにはacks=allを使用してください。また、特別な理由がない限り、冪等性を有効にしてください。最新のKafkaクライアントでは、冪等プロデューサーはリトライによる重複を減らすための標準的な選択肢です。

acks=all
enable.idempotence=true
retries=2147483647
max.in.flight.requests.per.connection=5

クライアントのバージョンと配信要件を理解せずに、リトライ値を盲目的にすべてのクライアントにコピーしないでください。重要な考え方は、永続的なKafka本番環境では通常、リトライ、冪等性、acks=allを一緒に使用する必要があるということです。acks=1を設定すると、リーダーはフォロワーがコピーする前にレコードを確認応答できます。そのリーダーが不適切なタイミングで停止すると、確認応答されたレコードが消失する可能性があります。これは一部のテレメトリストリームでは許容されますが、支払い、監査証跡、在庫移動、またはダウンストリームチームが信頼できる情報源として扱うものには許容されません。

トピックを作成する際は、ブローカーのデフォルトに依存するのではなく、レプリケーションの選択肢を意図的に設定してください。

kafka-topics.sh --create   --bootstrap-server broker1:9092   --topic orders.v1   --partitions 12   --replication-factor 3   --config min.insync.replicas=2

パーティション数はレプリケーションとは別です。レプリケーションファクター3で12のパーティションは、合計36のパーティションレプリカを意味します。これにはストレージ、ネットワーク、ファイルハンドル、コントローラーメタデータのコストがかかります。レプリケーションは永続性を向上させますが、無料ではありません。

既存のトピックの場合、min.insync.replicasの変更は簡単です。

kafka-configs.sh --alter   --bootstrap-server broker1:9092   --entity-type topics   --entity-name orders.v1   --add-config min.insync.replicas=2

既存のトピックのレプリケーションファクターを変更するには、Kafkaのバージョンとツールに依存します。新しいKafkaリリースではkafka-reassign-partitions.shをサポートしており、場合によっては増加を容易にするトピック変更ワークフローもあります。古いクラスターでは、レプリケーションを増やすには通常、パーティション再割り当て計画を生成して実行する必要があります。レプリケーションを減らすことは、コピーを削除するため、より敏感です。これを計画された操作として扱い、騒がしいインシデント中に入力されるカジュアルなコマンドとして扱わないでください。

トピックが大きい場合やクラスターがすでにビジーな場合、再割り当てはスロットルされるべきです。レプリケーションのキャッチアップは、既存のレプリカから古いデータを読み取り、新しいレプリカに書き込みます。これにより、稼働中のプロデューサーやコンシューマーからディスクやネットワーク容量が奪われる可能性があります。安全なランブックには通常、メンテナンスウィンドウ、前後の--describe出力、再割り当てスロットル、ロールバック計画が含まれます。

トピックは次のように検査できます。

kafka-topics.sh --describe   --bootstrap-server broker1:9092   --topic orders.v1

出力の3つのフィールド、LeaderReplicasIsrを確認してください。Replicasは割り当てられたセットです。Isrは現在キャッチアップしているセットです。Replicas1,2,3Isr1,2の場合、ブローカー3はそのパーティションに対して遅れているか利用不可です。多くのパーティションでISRに欠落しているブローカーが見られる場合、そのブローカーのディスク、ネットワーク、プロセスヘルス、ログを確認してください。影響を受けるホットパーティションが少数の場合、リーダーが過負荷になっているか、パーティションに異常に高いトラフィックがある可能性があります。

アンクリーンリーダー選出には特別な注意が必要です。パーティションのすべての同期レプリカが失われた場合、Kafkaには2つの選択肢があります。安全なレプリカが戻るまでパーティションを利用不可にするか、同期していないレプリカを選出して古いリーダーで確認応答されたレコードを失うリスクを負うかです。unclean.leader.election.enable=falseは安全性を選択します。trueはデータ損失のリスクを伴う可用性を選択します。

アンクリーン選出が許容される可能性があるワークロードもあります。短命なクリックストリームデータ、使い捨てのメトリクス、または上流システムがすべてをリプレイできるパイプラインです。ほとんどのビジネスデータでは、無効のままにしてください。パーティションの可用性を失うことは痛みを伴いますが、サイレントなデータ損失はさらに悪化します。なぜなら、コンシューマーは何もなかったかのように続行する可能性があるからです。

ラック認識レプリケーションは、異なる種類の障害に対処するのに役立ちます。ブローカーがラック、ゾーン、または共有電源/ネットワークパスを持つホストに分散されている場合、各ブローカーの場所をKafkaに伝えてください。

broker.rack=zone-a

すべてのブローカーに正しい値を設定してください。Kafkaはレプリカをラック全体に分散しようとし、単一ゾーンの障害がパーティションのすべてのコピーを失う可能性を低減します。これは魔法ではありません。各ゾーンに十分なブローカー、十分なディスク、注意深いパーティション配置が必要です。しかし、broker.rackがないと、Kafkaは2つのブローカーが同じ障害ドメインを共有していることを知る方法がありません。

レプリケーションを継続的に監視してください。最も有用な早期警告サインは、レプリカ不足のパーティション、オフラインパーティション、ISR縮小イベント、およびレプリカ不足に関連するプロデュースエラーです。Prometheusベースのセットアップでは、チームは一般的にKafkaブローカーメトリクスを監視してレプリカ不足のパーティションとオフラインパーティションを確認し、それらのアラートをブローカーのディスク、ネットワーク、JVMメトリクスとペアにします。

良いインシデントの質問は次の通りです。ISRが縮小したのは、ブローカーが停止したためか、レプリケーションが追いつかないためか、それともネットワークが信頼できないためか?修正方法は異なります。停止したブローカーはサービスの復旧が必要です。遅いブローカーはディスク交換、I/O調査、またはパーティションリーダーの削減が必要かもしれません。ネットワークの問題は、CPUとディスクが正常に見えても、繰り返しの切断とフェッチャーラグとして現れることがあります。

ローリングブローカー再起動は、レプリケーション設定の価値を示すもう1つの場面です。一度に1つのブローカーを再起動してください。次のブローカーを再起動する前に、パーティションが健全なISRを回復するのを待ってください。min.insync.replicas=2でブローカーをあまりに早く再起動すると、同期しているレプリカが少なすぎるため、プロデューサーが失敗し始める可能性があります。その失敗は予想されますが、忍耐と監視で回避できます。

実用的なチェックリストは短いです。ほとんどの本番トピックにはレプリケーションファクター3を使用してください。重要なデータにはmin.insync.replicas=2とプロデューサーのacks=allを使用してください。データが明示的に使い捨てでない限り、アンクリーンリーダー選出を無効にしてください。ラック認識でレプリカを障害ドメイン全体に分散してください。ブローカーの稼働時間だけでなく、ISRの健全性を監視してください。そして、実際の障害が発生する前に、制御されたウィンドウでブローカーを再起動して仮定をテストしてください。

レビュー中に役立つ詳細の1つは、永続性と可用性を平易な言葉で分離することです。永続性は、「Kafkaが書き込み成功と言った後、その確認応答されたレコードが危険にさらされるまでに何回の障害が発生するか?」を問います。可用性は、「プロデューサーとコンシューマーは今すぐパーティションを使用できるか?」を問います。強力な設定は時々可用性を低下させます。なぜなら、Kafkaは弱くレプリケートされたデータを受け入れるよりも書き込みを拒否するからです。それはKafkaの失敗ではありません。それはKafkaが設定した契約を尊重しているのです。

例えば、レプリケーションファクター3、min.insync.replicas=2、プロデューサーがacks=allを使用するトピックを想像してください。ブローカー1がリーダー、ブローカー2と3がフォロワーです。ブローカー3がダウンすると、ISRは1,2になります。2つのレプリカが同期しているため、書き込みは成功します。ブローカー3が戻る前にブローカー2がダウンすると、ISRは1だけになります。書き込みは失敗します。一部のチームはこれを本番環境で初めて見て、リーダーがまだ生きているのにKafkaがダウンしている理由を尋ねます。答えは、トピックは一部の読み取りには利用可能ですが、強力に確認応答された書き込みには安全ではないということです。

また、コンシューマーのリカバリーについても考えるべきです。レプリケーションはブローカー側のレコードのコピーを保護します。コンシューマーオフセットをすべてのワークフローのミスから自動的に保護するわけではありません。コンシューマーオフセットもKafkaに保存され、通常は__consumer_offsetsに保存されるため、その内部トピックも健全なレプリケーションが必要です。ユーザートピックが注意深く設定されていても、内部トピックが初期のクラスタービルドで弱いレプリケーションで作成された場合、フェイルオーバーの動作は予想よりも悪化する可能性があります。本番環境準備レビューの一環として、内部トピックのレプリケーションを確認してください。

マルチテナントクラスターでは、すべてのトピックが同じ設定に値するわけではありません。高ボリュームでビジネス価値が低い使い捨てのメトリクストピックは、短い保持期間を使用し、弱い保証を許容するかもしれません。請求トピックはそうすべきではありません。間違いは、偶然のデフォルトにその区別を任せることです。トピッククラスを文書化してください。重要なイベントストリーム、リプレイ可能なテレメトリー、コンパクト化された状態トピック、一時的な開発トピック。次に、各クラスをレプリケーション、ISR、保持、プロデューサー設定にマッピングしてください。

インシデント中は、全員がトレードオフを理解しない限り、エラーを静めるために永続性設定を変更しないでください。min.insync.replicas2から1に下げるとプロデューサーが動くようになるかもしれませんが、確認応答された書き込みが1つのブローカーにしか存在しないことを意味します。アンクリーンリーダー選出を有効にするとパーティションの可用性が回復するかもしれませんが、古いレプリカはレコードを失う可能性があります。時にはビジネスがそのトレードオフを選択することもあります。それは意識的なインシデントの決定であるべきであり、隠れたオペレーターのショートカットであってはなりません。