💫

KafkaとMassTransitを触って気づいた、イベント駆動の本質は「配送」ではなく「データの扱い」だった話

に公開

1. はじめに

現職では MassTransit を初めて触っています。前職ではKafkaを使っていました。

どちらも「イベント駆動アーキテクチャ」を実現するためのツールとして紹介されます。Producerがメッセージを送り、BrokerがそれをさばいてConsumerが受け取る——構造の説明は理解できているのですが違いがいまいちわからなかったのでそれをまとめようと思います。

2. 同じ構成なのに、なぜ挙動が違うのか

KafkaもMassTransitも、概念レベルの構成は同じに見えます。

Producerがメッセージを出し、Brokerが仲介して、Consumerが受け取って処理する。

でも実際に使い始めると、挙動がまるで違います。

  • MassTransit:Consumerがメッセージを処理すると、そのメッセージはキューから消えます
  • Kafka:Consumerがメッセージを読んでも、メッセージはトピックに残り続けます

最初はこれを「配送モデルの違い」として理解しようとしていました。MassTransitはPush型、KafkaはPull型、みたいな説明で何となく納得しようとしていました。

3. 混乱の原因だった Kafka の使い方

振り返ると、前職でKafkaを使ってやっていたことはこんな感じでした。

  • ProducerがイベントをTopicに投げる
  • ConsumerがそれをSubscribeして処理する
  • 処理に失敗したらretryして、それでもダメならDLQ(Dead Letter Queue)に入れる
  • 処理が終われば完結

そうです。Queueとして使っています。アーキテクチャの設計思想としては、RabbitMQやMassTransitとほとんど変わらない使い方をしていたのだと思います。

ここで気づいたことがあります。Kafkaの「ログ」という概念を、自分は全く活かしていなかったということです。

4. イベント駆動の2つの世界

実は、「イベント駆動」という言葉の中に、性質の異なる2つの世界が混在しています。

4.1 メッセージ指向(タスク実行)

「誰かに仕事を依頼する」というモデルです。

  • メッセージはConsumerに届いたら処理されて消える
  • 一度きりの仕事(一回配達したら終わり)
  • Consumerは「処理する人」
  • 例:MassTransit、RabbitMQ、SQS

ユースケースとしては、「注文が入ったので在庫を引き当てる」「メールを送信する」など、何かを実行させたい場面に向いています。

4.2 ログ指向(イベント記録)

「何かが起きたという事実を記録する」というモデルです。

  • メッセージ(イベント)は残り続ける
  • Consumerが何度読んでも、ログは消えない
  • Consumerは「読む人」
  • 例:Apache Kafka、Amazon Kinesis

ユースケースとしては、「ユーザーの行動を記録する」「システム間でデータを同期する」など、事実を共有したい場面に向いています。

5. 「Consumer」という言葉の罠

同じ「Consumer」という名前なのに、役割が全然違います。

MassTransit(メッセージ指向) Kafka(ログ指向)
Consumerの役割 処理する人 読む人
本質 Worker Reader
処理後のデータ 消える 残る
複数Consumerの場合 メッセージを分担して処理 それぞれが独立して読む

自分が混乱していた大きな原因のひとつがここにあります。

MassTransitのConsumerは「仕事をこなすワーカー」です。タスクを受け取り、処理し、完了させます。一方、KafkaのConsumerは「ログを読むリーダー」です。自分のペースでオフセットを進めながら、独立してイベントを読み取ります。

同じ言葉を使っているから同じものだと思っていましたが、役割は根本的に異なります。

6. Consumer分離の違い

複数のサービスが同じイベントを処理したいとき、両者のアプローチは全く違います。

Kafkaの場合:データを「共有」する

1つのTopicに存在する同じデータを、複数のConsumer Groupがそれぞれ独立して読みます。 データは複製されません。ただそこにあり、各サービスが自分のペースで読み進めます。

MassTransitの場合:データを「複製」する

サービスごとに別々のQueueにメッセージが複製されます。 各サービスは自分のQueueのメッセージを処理して、消化したら終わりです。

7. データ再構築という概念

Kafkaの最も本質的な強みは、ここにあります。

ログが残っているから、過去に遡って再処理できる。

具体的にどういうことか、3つのシナリオで見てみましょう。

バグ修正後の再処理

Consumerにバグがあって、過去1週間のイベントを誤って処理していたとします。

  • MassTransit:メッセージはすでに消えています。やり直すには元データを別途管理しておく必要があります。
  • Kafka:Topicにログが残っています。オフセットを1週間前に戻して再処理するだけです。

新機能の後付け

新しいサービスを追加して、「今まで発生したすべての注文イベントを元に初期データを構築したい」という要件が出たとします。

  • MassTransit:過去のメッセージは消えているので、DBから直接データを引っ張ってくる必要があります。
  • Kafka:Topicの先頭から読み始めればいいです。新サービスは「後から参加したリーダー」として、過去のログを全部読めます。

障害からの復元

Consumer側に障害が発生して、一定期間処理が止まっていたとします。

  • MassTransit:Queue溢れのリスクがあります。メッセージが消えてしまっている可能性もあります。
  • Kafka:ログは残っています。Consumerが復旧したら、止まっていたオフセットから再開するだけです。

「履歴がある」というのは、単なる記録ではなく、システムの信頼性と柔軟性の源泉です。

8. なぜ自分は理解できていなかったのか

今になって振り返ると、理由は明確です。

自分が解こうとしていた問題が、常に「タスク処理」だったからです。

「注文が入ったら在庫を引く」「ユーザーが登録したらメールを送る」——これらはすべてメッセージ指向で十分解ける問題です。KafkaをQueueとして使っても、動くコードは書けてしまいます。

でも、ログとしての価値を活かす問題——「データの再構築」「後付けの処理」「複数サービスへの独立した配信」——に直面したことがなかったので、Kafkaの設計思想の意味がわかりませんでした。

ツールは使い方次第で別物になります。Kafkaを「高スループットなQueue」として使うこともできますし、「再処理可能なイベントログ基盤」として使うこともできます。でも後者の価値を理解していなければ、前者としてしか使えません。

現職でMassTransitを触ったことで「あれ、Kafkaでやっていたことと構造は同じなのに何かが違う」という違和感が生まれ、初めてその差を正面から考えるきっかけになりました。


9. どう使い分けるべきか

ここまでの話を整理すると、判断基準はシンプルになります。

メッセージ指向(MassTransit / RabbitMQ / SQS)を選ぶとき

  • やりたいことが「タスクの実行」である
  • 処理したら終わり、再処理は不要
  • シンプルな1対1 or 1対多の配信で十分
  • インフラのシンプルさを優先したい

ログ指向(Kafka / Kinesis)を選ぶとき

  • やりたいことが「イベントの記録と共有」である
  • 過去のデータを再処理する可能性がある
  • 複数のサービスが独立して同じデータを消費したい
  • データパイプラインや分析基盤と連携したい

10. まとめ

イベント駆動アーキテクチャの本質は、「配送方式(Push/Pull)」ではなく「データをどう扱うか」 にあります。

前職でKafkaを使っていたとき違和感を感じていたのは、ログとしての価値を活かさずにQueueとして使っていたからでした。Kafkaの設計思想は「イベントは記録であり、誰でも何度でも読める」というものですが、それを活かす問題を解こうとしていなかったのです。

現職でMassTransitに触れて初めて「あれ、これ前にやってたことと何が違うんだろう」と自問したことで、ようやくその本質的な差が見えてきました。

「なんとなく動く」から「なぜそう設計されているのかわかる」に変わると、技術選定の精度が上がります。

もう少しこの辺りを深掘りしていけたら面白そうだなと思いました。

Discussion