Apache Kafka on Heroku の堅牢な使用法
最終更新日 2022年12月12日(月)
Table of Contents
Apache Kafka は、膨大な量の複製、さらには高可用性と障害処理についての注意深い考えにより、全体として信頼性の高い分散システムになるように設計されています。ただし、Kafka を使用するアプリケーションを開発するときは、次に示すあり得る現実が発生した場合でもその堅牢性を確実に維持するために考慮する必要のある事項がいくつか存在します。
- パーティションリーダーが変更される可能性
- Kafka は “最低 1 回”
- コンシューマーはオフセットのリセットについて注意する必要がある
- クライアントオブジェクトはアプリケーションのライフサイクル全体を通して再利用する必要がある
- 低レベルの Kafka クライアント API を使用する場合は注意する必要がある
Apache Kafka on Heroku は、インスタンス障害の処理、基礎となるソフトウェアのアップグレード、Kafka 自体のアップグレードなどの多くの理由から、日常的にインスタンスを置き換えます。これらの障害に対して堅牢でないアプリケーションではエラーが発生し、これらのイベント中に予期せず中断される可能性があります。
1. パーティションリーダーの選出
障害のシナリオ: Kafka 自体で個々のブローカーの障害を処理する方法では、障害が発生したブローカー上に存在するパーティションごとに新しい “リーダー” ブローカーを選出します。このプロセスは通常、ブローカー障害から (一般には) 約 10 ~ 20 秒以内に実行され、すべての機能が復元されます。ブローカーが回復されると、Apache Kafka on Heroku は、パーティションリーダーシップをそのブローカーに再移行します。
アプリケーションへの潜在的な影響: メッセージの削除、重複したメッセージ、エラー率の上昇。
このシナリオを防ぐ方法: 最も適切に設定された高レベルの Kafka クライアントライブラリは、パーティションリーダーの選出が行われた場合でも堅牢です。ただし、リーダーシップが移動された場合でも、アプリケーションはレイテンシーが増加するだけであり、エラーに対して再試行するように、プロデューサーが再試行を設定していることを確認する必要があります。
低レベルの Kafka API を使用している場合は、パーティションリーダーシップの変更を処理することが重要です。それを行うための基本的なメカニズムとして、NotLeaderForPartition
エラーがないかどうかを確認し、新しいリーダーの詳細をフェッチするために Kafka のメタデータエンドポイントを再ポーリングします。
2. Kafka は “最低 1 回”
障害のシナリオ: アプリケーションプロセスが再起動するか、または Kafka 自体に障害が発生します。インスタンス障害が発生している場合、Kafka の基礎となる複製では、メッセージは削除されるのではなく、複製されます。
アプリケーションへの潜在的な影響: 予期されていなかった重複したメッセージ。
このシナリオを処理する方法: Kafka に基づいてシステムを構築する場合は、コンシューマーが個々のメッセージの複製を処理できることを確認してください。一般的な修正方法として、次の 2 つがあります。
べき等性: コンシューマーが同じメッセージを 2 回受信しても、何も変更されないようにします。たとえば、コンシューマーが SQL データベースに書き込んでいる場合は、UPSERT を使用すると、重複したメッセージを安全に受信できる可能性があります。
重複したメッセージの追跡: 各メッセージに何らかの一意識別子を挿入してから、各コンシューマーがどの識別子を受信したかを追跡し、複製されている識別子をすべて無視します。
3. コンシューマーはオフセットのリセットについて注意する必要がある
コンシューマーによっては、一部の Kafka エラーでオフセットを自動的にリセットするためのメカニズムを備えている場合があります。これらのメカニズムは通常、本番環境では安全でないため、これらのケースを手動で処理することをお勧めします。
障害のシナリオ: コンシューマーが Kafka から “範囲外オフセットのエラー” を受信し、そのオフセットをどこかでリセットすることを決定します。
アプリケーションへの潜在的な影響: アプリケーションは、保持されている最初または最後のオフセットから再生を開始するため、処理が遅れるか、またはメッセージが失われる可能性があります。
このシナリオを処理する方法: コンシューマーが追跡しているオフセットを自動的にリセットするアプリケーションコードを記述しないでください。それを行うコンシューマーの設定オプションを有効にすることも止めて、人間が介入するようにしてください。
4. クライアントオブジェクトの再利用
障害のシナリオ: 個々のブローカーに何らかの理由で障害が発生した場合、クライアントオブジェクトを再利用していないクライアントアプリケーションでは問題が発生します。
アプリケーションへの潜在的な影響: 新しいクライアントを (たとえば、受信した HTTP リクエストごとに) 作成する場合、アプリケーションはブローカー障害中にレイテンシーが増加し、Kafka にアクセスするとエラーが発生します。これは、クライアントライブラリがクラスターへの最初の接続で重要な作業を実行しているためです。各リクエストで新しいクライアントを立ち上げると、その作業がもう一度、おそらく障害が発生したブローカーに対して実行されます。
このシナリオを処理する方法: アプリケーションでは、Kafka クライアントオブジェクトを常に長時間使用するようにしてください。プロデューサーとコンシューマーのオブジェクトをアプリケーションのライフサイクルの早い時期に構成し、それらを再利用します。
5. 低レベルの Kafka クライアント API
障害のシナリオ: “低レベルの” Kafka API の使用 (たとえば、ブローカーに個々のリクエストを送信するなど) は、障害が発生した場合は複雑になります。
アプリケーションへの潜在的な影響: エラー、潜在的なデータ損失、潜在的なダウンタイム。
このシナリオを防ぐ方法: 低レベルの Kafka クライアント API の使用を (できるだけ) 避けてください。個々のブローカーの指定が必要な操作はすべて、障害が発生すると、適切に実行することが困難になります。低レベルの API を使用する場合は、Kafka レベルの障害が発生した場合のコードを必ずテストするようにしてください。
同様に、1 つのトピックですべてのパーティションからの調整されたフェッチを必要とするどのような種類のロジックも回避してください。特定のパーティションのリーダーが再選出または再均衡されている最中である可能性があるため、コンシューマーは、一時的な障害が発生している個々のどのパーティションに対しても堅牢である必要があります。
最後に: ステージング環境で障害をテストする
Apache Kafka on Heroku には、いずれかのブローカーでインスタンス障害を発生させることができるコマンド heroku kafka:fail
があります。このコマンドの使用は、アプリケーションが Kafka レベルの障害を処理できるかどうかを確認するための最善の方法であり、これをステージング環境でいつでも実行できます。その構文は次のとおりです。
heroku kafka:fail KAFKA_URL --app sushi
ここで、sushi
はアプリの名前です。
このコマンドを安易に使用しないでください。本番環境でのブローカーの障害は安定性の向上にはまったくつながらず、クラスターの安定性を大幅に低下させます。
同様に、複数のブローカーを起動し、そのいずれかのプロセスを強制終了することによって、開発環境でも障害をテストできます。