Kafkaが保証すること — パーティション内の順序と整数で表す位置
一言でいうと
Kafkaのトピックは、パーティションに分かれた追記専用のログで、同じキーのイベントは同じ パーティションに順番に積まれ、コンシューマーグループの「どこまで読んだか」は、パーティションごとに整数 1つ(オフセット)です。消費しても削除されません。保持設定が削除するだけです。この4つ の文が、後に出てくる「2回」と「消失」を読み解く鍵です。
なぜ必要なのか
キューを使ったことがある人は、Kafkaをキューと誤解します。キューは、メッセージを取り出すと消え、ブローカーが 「誰が何を受け取ったか」をメッセージごとに覚えています。設計ドキュメント の「Consumer Position」の節は、その方式のコストを書いています。渡した途端に消費済みと マークすると、コンシューマーが処理中に落ちたときにメッセージが消え、確認(ack)を待つと、 処理したのに確認を送れなかった場合に2回消費され、ブローカーはメッセージごとに状態を 複数持たなければなりません。
Kafkaは別の道を選びました。トピックを全順序(totally ordered)のパーティションに分け、 パーティション1つは、1つのコンシューマーグループの中でちょうど1つのコンシューマーだけが読むようにすると、コンシューマーの 位置は「次に読むオフセット」の整数1つになります。その整数を定期的にチェックポイント することが確認のすべてなので非常に安く、副産物として巻き戻しが生まれます。コードに バグがあったなら、古いオフセットに戻って読み直せます。ドキュメントは、これがキューの 契約に反するものの、多くのコンシューマーにとって必須の機能だと書いています。
どう動くのか
イベントとトピック: 紹介ドキュメントのとおり、イベントは キー・値・タイムスタンプ(と任意のヘッダー)を持ち、トピックはそのイベントが積まれる場所です。 トピックは、複数のプロデューサーと複数のコンシューマーを同時に受け付け、イベントは消費された後も 削除されません。どれだけ長く残すかは、トピックごとの保持設定が決めます(モジュール4)。
パーティションとキー: トピックは複数のパーティション(「バケット」)に分かれて、複数のブローカーに配置されます。
新しいイベントは、そのうち1つのパーティションの末尾に付きますが、同じキー(例: 注文番号)のイベントは
同じパーティションに行きます。プロデューサー設定のドキュメント
のpartitioner.classの項目が、デフォルトの動作を書いています。キーがあればキーの
ハッシュでパーティションを選び、キーがなければbatch.sizeの分だけ埋まるまで1つの
パーティションに付けるスティッキー(sticky)方式です。そのため、キーなしで送った少量のイベントは1つの
パーティションに集中し、キーを指定すると、注文ごとに一列に並びます。
Kafkaが約束する順序は、パーティションの中だけです。注文1つのcreated → paid → shippedは同じパーティションなのでその順序で読まれますが、別の注文どうしでは、どのパーティションが
先に読まれるかは決まっていません。順序が必要な単位がそのままキーでなければならない理由です。
orders (3 파티션) 컨슈머 그룹 order-svc 의 위치
P0: order-2 created, order-3 created, order-2 paid → 오프셋 3
P1: order-1 created, order-1 paid, order-1 shipped → 오프셋 3
P2: order-4 created, order-5 created → 오프셋 2
コンシューマーグループとオフセット: グループは、group.idでまとめられたコンシューマーで、グループの位置は、
パーティションごとにブローカーにコミットされたオフセットです。別のグループは別の位置を持ちます。
order-svcが最後まで読んでいても、analyticsは最初から読み直します。ログの末尾の
オフセットとコミットされたオフセットの差がラグ(lag)で、運用で最初に見る
数字です。kafka-consumer-groups.sh --describeが、パーティションごとにCURRENT-OFFSET、
LOG-END-OFFSET、LAGを表示します。
パーティション数は増やすことしかできません: 運用ドキュメント
の「Modifying topics」の節は、パーティションを増やすときの副作用を3つ挙げています。データが
hash(key) % 파티션 수(プレースホルダーはパーティション数です)で分かれるので、数が変わると同じキーが別のパーティションに行く
可能性があり、既存のデータは再配置されません(キーの順序保証が壊れることがあります)。
auto.offset.reset=latestの既存のコンシューマーは、新しいパーティションに気づく前に入ってきた
メッセージを見逃すことがあります。メタデータの伝播に遅延があります。そして、減らすことは
サポートされていません。
現場での姿
「注文の状態が逆転して見える」という報告のよくある原因は、キーです。注文サービスがキーなしで 送っていたか、注文番号ではなくイベントの種類をキーにしていたか、パーティション数を 増やした後、同じ注文の古いイベントと新しいイベントが別のパーティションに分かれている 場合です。3つの場合とも、ブローカーは正常で、ログも正常です。約束していないことを 期待してしまったのです。
ラグのグラフが急に上がるなら、2つのうちのどちらかです。プロデューサーがたくさん送っているか、 コンシューマーが止まっているか。グループをdescribeして、CONSUMER-IDの列が空なら後者です。 コンシューマーが落ちても、コミットされたオフセットはブローカーに残っているので、再び起動すれば、その位置から 読みます。そのとき何が2回届き、何が消えるのかが、モジュール3です。
次のラボですること
パーティション3つのトピックに、キー付きのイベントを入れて、同じキーが同じパーティションに順番に 積まれるのを確認し、グループのオフセットとラグを読み、2つのグループが独立していることを確認した後、 パーティションを6つに増やして、キーの配置が変わるのを自分で観察します。