本文へ移動

ホーム / Data / 第3部 データパイプライン

第3部 データパイプライン・第6章

ストリームと Kafka

★★☆ 重要6トピック・面接 4問

イベントを数秒〜数分で流し続ける仕組みと、その落とし穴を押さえる。

ひとことで

イベントをパーティションに分けて保存し、コンシューマーグループで並列に読む。

注文・クリック・DB の変更などの出来事を、複数のシステム(DWH・検索・通知など)にすぐ届けたいとき、送る側が受け手ごとに直接つなぐと、受け手が増えるたび・止まるたびに送る側を直すことになります。間に Kafka のようなメッセージブローカー(メッセージを預かり、受け手に渡す仕組み)を置くと、送る側は書くだけ、受け手はそれぞれのペースで読むだけになります。

データエンジニアにとっては、ストリーム処理や CDC の土台です。面接でも、順番・重複・並列度の3つはよく聞かれます。

Kafka は、イベントを順番にためておく「追記専用のログ」です。書く側(プロデューサー)はトピックにイベントを書き、トピックはパーティションに分かれて複数のサーバーに置かれます。読む側(コンシューマー)はコンシューマーグループを作り、パーティションを分担して並列に読みます。読んだ位置(オフセット)を記録しておくので、落ちても続きから読めますし、保持期間の中なら過去にさかのぼって読み直すこともできます。

順番が守られるのは、同じパーティションの中だけプロデューサーキーで行き先を決めるトピック ordersP00123456P1012345P201234order_id=42 のイベントは、いつも P1 に入る(作成 → 支払い → 発送の順が守られる)コンシューマーグループコンシューマー 1P0・P1 を読むコンシューマー 2P2 を読む
Kafka のトピック・パーティション・コンシューマーグループ
よくある誤解実際
トピック全体で順番が守られる守られるのは同じパーティションの中だけ
1回送れば1回だけ届く基本は少なくとも1回。再送で重複し得る
コンシューマーを増やせば必ず速くなる1つのパーティションはグループ内の1台しか読めない。パーティション数が並列度の上限

下の「ブラウザで試す」の図では、プロデューサーがキー(user_id)のハッシュでパーティションを決めて書き、3台のコンシューマーがパーティションを1つずつ分担して読み、処理したところまでのオフセットをコミットします。後半では、コンシューマー 3 が止まり、リバランスで残りの2台が分担をやり直して、最後にコミットされたオフセットから読み直す様子を見られます。上の表の「順番は同じパーティションの中だけ」「再送で重複し得る」「パーティション数が並列度の上限」が、それぞれどの手順から来るかに注目してください。

面接ではこう答える

Kafka はイベントを順番にためておく追記専用のログです。プロデューサーがトピックに書き、トピックはパーティションに分かれて複数のサーバーに置かれます。コンシューマーはグループを作ってパーティションを分担して読み、読んだ位置のオフセットを記録するので、落ちても続きから読め、保持期間の中なら過去を読み直せます。順番が守られるのは同じパーティションの中だけで、パーティションの数が並列度の上限です。

別のページで開く →

ネットワーク図を読み込んでいます…

同じキーは、同じパーティションに入る注文A 作成注文B 作成注文A 支払い注文A 発送注文B 支払いパーティション 0パーティション 1A 作成A 支払いA 発送B 作成B 支払いhash(order_id)パーティションをまたぐ順番(A と B のどちらが先か)は保証されない
キーとパーティションの対応(order_id をキーにした例)

同じキー(例:order_id)のイベントは同じパーティションに入るので、「注文作成 → 支払い → 発送」の順番は守られます。キーを決めずに送ると、ばらばらのパーティションに入り、順番が入れ替わります。順番が大事なものは「どの単位で順番が要るか」を考えてキーにします。ただしキーが偏る(1人の大口ユーザー)と、1つのパーティションに集中して遅れます。また、あとからパーティション数を増やすと、キーと行き先の対応が変わる点にも注意します。

キーの選び方起きること
キーなしばらばらのパーティションに入り、順番は守られない。負荷は均等
order_id など出来事の単位その単位の中の順番が守られる。たいていはこれ
shop_id など大きな単位大口の店にイベントが集中し、1つのパーティションだけ遅れる
ひとことで

配信は「少なくとも1回」が基本で、重複して届き得る。受け取る側を冪等にして、重複を吸収する。

保証意味起きること
最大1回(at most once)先にオフセットを記録してから処理落ちるとイベントが失われる
少なくとも1回(at least once)処理してからオフセットを記録落ちると同じイベントを2回処理する。基本はこれ
ちょうど1回(exactly once)重複も欠けもないKafka の中で読んで書く処理なら近づけられる。外の DB までは守られない
処理のあと、オフセットを記録する前に落ちると、もう一度処理する読む処理(DB に書く)落ちる再起動・同じ位置から書き込み 1回目オフセット未記録書き込み 2回目
少なくとも1回の配信では、同じイベントを2回受け取ることがある

コンシューマーは「処理してからオフセットを記録する」のが基本です。処理の後、記録の前に落ちると、再起動後に同じイベントをもう一度処理します。そのため受け取る側を冪等(同じものを2回処理しても結果が同じ)にします。

-- イベント ID を主キーにして、2回目は何もしない(PostgreSQL の例)
INSERT INTO payments (event_id, order_id, amount, paid_at)
VALUES (:event_id, :order_id, :amount, :paid_at)
ON CONFLICT (event_id) DO NOTHING;

Kafka にはプロデューサーの重複を防ぐ設定(enable.idempotence)やトランザクションもあり、Kafka の中で読んで書く処理なら「ちょうど1回」に近づけられます。ただし外部の DB への書き込みまでは守られないので、結局は書き込み先の一意キーや MERGE で重複を吸収するのが確実です。

面接ではこう答える

ストリームの配信の保証には、最大1回、少なくとも1回、ちょうど1回があります。普通は処理してからオフセットを記録する少なくとも1回で、その間に落ちると同じイベントを2回処理します。Kafka にはプロデューサーの冪等性やトランザクションがありますが、外部の DB への書き込みまでは守られないので、受け取る側をイベント ID の一意キーや MERGE で冪等にして重複を吸収するのが確実です。

ひとことで

書かれた位置と、コンシューマーが読んだ位置の差。増え続けるなら処理が追いついていない。

監視の要はコンシューマーラグ(書かれた位置と読んだ位置の差)です。ラグが増え続けるなら、処理が追いついていません。

パーティションごとのラグ(書かれた位置 − 読んだ位置)パーティション 0120件パーティション 190件パーティション 248,000件(増え続ける)
ラグが1つのパーティションだけ増え続けるなら、キーの偏りや、そのイベントの処理の遅さを疑う
ラグの形疑うこと
全パーティションでじわじわ増える処理が追いついていない。並列度(パーティション数とコンシューマー数)か処理の速さ
1つのパーティションだけ増えるキーの偏り、特定のイベントで処理が詰まっている
急に増えて止まらないコンシューマーが落ちた・再試行を繰り返している・書き込み先の障害
増えたり減ったりを繰り返すコンシューマーグループの再割り当て(リバランス)が頻繁に起きている

ラグは件数のほかに時間(いちばん古い未処理のイベントが何秒前か)でも見ると、利用者への影響(データが何分古いか)で説明できます。

面接ではこう答える

コンシューマーラグは、パーティションの最新の位置と、コンシューマーグループが読み終えた位置の差です。増え続けるなら処理が追いついていないので、全体で増えているなら並列度や処理の速さ、1つのパーティションだけならキーの偏りを疑います。件数だけでなく、未処理のいちばん古いイベントが何秒前かという時間で見ると、データが何分古いかとして利用者に説明できます。

ひとことで

流れ続けるイベントを起きた時刻で区切って集計し、ウォーターマークで「もう届いた」とみなして結果を確定する。

ストリームで「5分ごとの注文数」を出すには、終わりのない流れを時間で区切ります。この区切りをウィンドウと呼びます。区切りは、イベントが起きた時刻(イベント時刻)で決めるのが基本です。届いた時刻で区切ると、遅れて届いたイベントが別の区切りに数えられてしまいます。

起きた時刻で区切り、ウォーターマークで締める10:00〜10:0510:05〜10:1010:10〜10:1510:04 に起きて10:07 に届いたイベントウォーターマークウォーターマーク=ここまでは届いたとみなす時刻。遅れの許容を長くすると正確に、短くすると早く出る
ウィンドウとウォーターマーク
ウィンドウ形例
タンブリング重ならない固定の区切り5分ごとの注文数
スライディング(ホッピング)重なりのある区切り直近10分の件数を1分ごとに更新
セッション一定時間イベントがなければ区切る1回の訪問の長さ

ウォーターマークは「この時刻より前のイベントは、もうほぼ届いた」とみなす目安です。ウォーターマークを過ぎたら区切りの結果を確定して出します。それより遅れたイベントは、捨てる・別に記録する・結果を更新するのどれかを決めておきます。

面接ではこう答える

ストリームの集計では、イベントを起きた時刻でウィンドウに区切ります。固定の区切りのタンブリング、重なりのあるスライディング、間が空いたら区切るセッションがあります。遅れて届くイベントがあるので、ウォーターマークで「この時刻より前はもう届いた」とみなして結果を確定し、それより遅いものの扱いを決めておきます。遅れの許容を長くすると正確に、短くすると早く結果が出るトレードオフです。

ストリーム処理には Apache Flink、Spark Structured Streaming、Kafka Streams などの道具や、クラウドの管理サービスがあります。どれも「状態(途中の集計)を持ちながら、イベントを1件ずつ、または小さな塊で処理する」仕組みです。

ストリームにするか、短い間隔のバッチにするか短い間隔のバッチ(5〜15分)今の SQL と道具がそのまま使える遅れて届く分も作り直しで吸収鮮度は間隔までストリーム処理秒〜数秒の鮮度状態・ウォーターマークの管理が要る運用と障害対応が難しい
分単位で足りるなら、まず短い間隔のバッチを検討する
向くもの例
秒単位で反応が要る不正の検知、在庫の引当て、アラート
イベントをそのまま別の場所に流すCDC を検索エンジンやキャッシュに反映
分〜時間でよい集計→ 短い間隔のバッチで十分なことが多い

バッチとストリームで同じ指標を別々に計算すると、2つの数字がずれます。ストリームの結果は「速報」、バッチで作り直した結果を「確定」として、利用者に区別を伝えるのが現実的です。

この章のまとめ

🖨 まとめシート(印刷・PDF)
  • Kafka のパーティション・コンシューマーグループ・オフセット
  • 順番の保証の範囲と、少なくとも1回の配信
  • ウィンドウ集計とウォーターマーク

この章のクイズ

面接ではこう聞かれる

まず答えを見ずに声に出して答え、そのあと開いて比べる。

Q1Kafka で順番を守りたいときは、どうしますか?

模範回答

Kafka が順番を守るのは同じパーティションの中だけなので、順番が必要な単位をメッセージのキーにします。例えば注文の「作成 → 支払い → 発送」の順が大事なら、order_id をキーにすると、同じ注文のイベントはいつも同じパーティションに入り、その順番で読めます。

注意点は3つあります。キーが偏ると1つのパーティションに集中して遅れること、あとからパーティション数を増やすとキーと行き先の対応が変わること、そして書く側の再試行で順番が入れ替わらないように、プロデューサーの冪等性の設定を有効にしておくことです。読む側でも、1つのパーティションを並列に処理すると順番が崩れるので気をつけます。

深掘りされたら

  • トピック全体での順番が必要なら?
  • コンシューマーラグが増え続けたら、どうしますか?

見られているポイントパーティションとキーの関係の理解

Q2「ちょうど1回(exactly once)」の処理は実現できますか?

模範回答

完全に保証するのは難しく、実務では「少なくとも1回届く」前提で、受け取る側を冪等にして、結果として1回と同じにするのが基本です。読む側は処理してからオフセットを記録するので、その間に落ちると同じイベントをもう一度処理します。

Kafka の中で読んで書く処理なら、プロデューサーの冪等性やトランザクションで「ちょうど1回」に近づけられます。しかし外部の DB や API への書き込みまでは守られないので、イベント ID を一意キーにして ON CONFLICT DO NOTHING や MERGE で重複を無視する、といった仕組みを書き込み先に用意します。

深掘りされたら

  • イベントに ID がないときは、どうしますか?

見られているポイント配信の保証と、冪等な書き込みの組み合わせ

Q3Kafka のコンシューマーラグが増え続けています。何を調べますか?

模範回答

まずラグがどのパーティションで増えているかを見ます。全体でじわじわ増えているなら処理が追いついていないので、処理の時間、書き込み先の DB の遅さ、コンシューマーの数とパーティションの数を確かめます。コンシューマーはパーティションの数より多くしても速くならないので、足りなければパーティションを増やすことも考えます。

1つのパーティションだけ増えているなら、キーの偏りか、特定のイベントで処理が失敗して再試行を繰り返している可能性があります。また、ラグが増えたり減ったりするならリバランスが頻繁に起きていないかを見ます。対応のあいだは、データが何分遅れているかを利用者に伝えます。

深掘りされたら

  • パーティションを増やすときの注意は?
  • ラグのアラートはどんな条件にしますか?

見られているポイント偏りと全体の遅れを分けて考えられるか

Q4ストリームで出した「5分ごとの注文数」が、翌日のバッチの集計と合いません。なぜですか?

模範回答

よくある原因は3つです。1つめは遅れて届いたイベントで、ウォーターマークを過ぎて届いた分はストリームの結果に入らず、翌日のバッチには入ります。2つめは区切りの時刻の違いで、ストリームが届いた時刻、バッチが起きた時刻で区切っていると、境目の分がずれます。3つめは重複で、少なくとも1回の配信で重複したイベントをストリーム側で除いていないと、多く数えます。

対策は、両方を起きた時刻で区切り、event_id で重複を除き、ストリームの結果は速報、バッチの結果を確定として扱うことを利用者と合意することです。

深掘りされたら

  • 遅れて届いたイベントは、ストリーム側でどう扱いますか?

見られているポイントストリームとバッチの違いを、時刻・遅れ・重複で説明できるか