ストリーム処理を1からKappa Architectreまで学ぶ

エンジニアの @mkataigi です。ついにエンジニア定年説の定年を迎えましたが、まだまだバリバリのエンジニアをやってくつもりでおります。

KARTEをシステムの側面から見た場合、大量のイベントデータという、いわゆる「ストリームデータ」を処理するシステムになります。今回は、KARTEの成り立ちを理解するために、そもそもストリームデータとは何者なのか、ストリームデータはどのような特性を持っていて何が大変なのかを理解して、ストリーム処理ではその大変さについてどのようにアプローチしてきたのか、そのシステムの発展について解説してみたいと思います。

ストリームデータを理解する

まずシステムの説明に入る前に、そもそもストリームデータとは何なのかについて理解していきたいと思います。また、そのストリームデータのどのような特性がシステムに活かされているのかを見ていこうと思います。

そもそもストリームデータとは?

ストリームデータが何なのかを理解するには、まず「ストリーム処理エンジン」と呼ばれるシステムアーキテクチャの説明が必要になります。ストリーム処理エンジンとは以下のように定義されるシステムのことを指します。

a type of data processing engine that is designed with infinite data sets in mind
(無限のデータセットを念頭に置いてデザインされたデータ処理エンジンの一種)
https://www.oreilly.com/ideas/the-world-beyond-batch-streaming-101

その特徴としては、以下の3点が挙げられます。

  • データが常に発生し続けていること
  • 無限のデータ処理を続けていること
  • 低レイテンシ(リアルタイム)に結果を返していること

よく対比されるバッチ処理エンジンとは、以下のような違いがあります。

  • バッチ処理エンジン
    • 大量のデータを貯めておき、定期的に処理をするデータ処理エンジン
  • ストリーム処理エンジン
    • 大量のデータをリアルタイムに、常に処理し続けるデータ処理エンジン

やや逆説的になってしまいますが、ストリームデータは以下のように定義されます。

常に発生し続け、リアルタイムにデータ処理エンジンに処理される大規模データ

ポイントとしては、リアルタイムに処理されない貯めておかれた大規模データは、ただのビッグデータであり、ストリームデータとは呼ばれないというところです。ストリーム(小川)と言いつつ、かなりの激流ですね。

ストリームデータの特性は?

続いて、システムを作る上で重要となってくるストリームデータの特性について簡単に説明します。一般的にストリームデータは以下の性質を持っています。

  • 時間とともに記録される
    • NG: 太郎さんは東京に住んでいる
    • OK: 太郎さん2017年10月1日に東京に住んでいた
  • 変更されない
    • NG: 太郎さんは2017年8月10日に神奈川から東京に住所が変更された(引っ越した)
    • OK: 太郎さんは2017年8月9日に神奈川に住んでいた / 太郎さんは2017年8月10日に東京に住んでいた

ちょっとわかりにくいですが、いわゆる「ログデータ」だと思ってもらって大丈夫です。この性質から次の重要な特性が導かれます。

ログ

https://www.oreilly.com/ideas/questioning-the-lambda-architecture
ストリームデータに対する操作は作成と閲覧だけ考えれば良い

RDBなどの処理では、CRUDと呼ばれる、作成(Create)/閲覧(Read)/更新(Update)/削除(Delete)という4つの操作が基本になります。しかし、ストリームデータの場合、更新・削除については変更されないという特性のため、新しいデータもしくは無くなったというデータの作成で代用できます。CRUDの中でも特に基本的なCRのみに操作を限定できるため、ストリーム処理エンジンは非常にシンプルな操作のみで済ませることができるという特徴があります。

また、RDBのような従来の「テーブル」型のデータとの関係もここで述べておきます。

「テーブル」データはストリームデータのスナップショットである

実際にRDBが「リレーログ」から完全に再現できることからもわかる通り、ストリームデータがあれば「テーブル」データの任意のタイミングの状態が再現できます。この性質があるため、ストリーム処理の文脈では、「テーブル」データよりストリームデータがより基本的なデータとして扱われます。

ストリーム処理エンジンの発展

ストリームデータの特性について理解したところで、次はその特性を活かして作られたストリーム処理の発展について見ていきたいと思います。まずはシステムの中心となるストリーム処理エンジンについて、非ストリーム処理の発展であるLambdaArchitectureと、ストリーム処理をより直接的に実現するためのKappaArchitectureを紹介します。また、そのストリーム処理エンジンを使ったLog-structured data flowと、その発展であるKARTEのシステムアーキテクチャについて簡単に紹介します。

Lambda Architecture:ストリーム処理とバッチ処理を並行活用する処理エンジン

まずはLambda Architectureと呼ばれるアーキテクチャについて説明します。このアーキテクチャの肝は、従来のバッチ処理ベースのシステムをいかにリアルタイム化させるか、という点になります。すでにバッチ処理が動いているシステムをリアルタイム化させる際に参考になるかもしれません。

バッチ処理はスループットが非常に高いというメリットを持つものの、レイテンシ(この場合は、最新のログが届いてからそのログが集計結果に反映されるまでの時間)が非常に遅くなってしまうというデメリットがありました。そのためバッチ処理とストリーム処理を組み合わせることで、このデメリットに対抗しようとしたのがLambda Architectureになります。

バッチ処理1回に数時間かかるとすると、今あるデータに対してバッチ処理で集計をすると数時間後に結果が出てきます。つまり今現在は、数時間前のバッチ処理の結果ができていることになります。そのため、バッチ処理で事前計算を行っておけば、ストリーム処理をしないといけないのは残り数時間分のデータになります。数年分のデータの処理をリアルタイムにするのは現実的ではないものの、数時間分のデータの処理であれば現実的ですね。

lambda architecture

このように、バッチ処理のメリットとストリーム処理のメリットを合わせたのがLambda Architectureになります。ただ、その一方で「バッチ処理とストリーム処理の2つのシステムを維持しないといけない」というデメリットもあります。次はこのデメリットを解決するアーキテクチャを見てみたいと思います。

Kappa Architecture:全てストリームで処理する処理エンジン

Lambda Architectureのバッチ処理とストリーム処理の2つのシステムを維持しないといけないというデメリットに対応したのが、次のKappa Architectureになります。とは言ってもそのアイデアは非常にシンプルで、「そもそもストリーム処理だけで全て計算できるのではないか」という点です。このアイデアについて検証するために、Lambda Architectureとストリーム処理の違いを見て行きます。

まずは計算できる内容ですが、Lambda Architecutreの場合でも、最終的にはストリーム処理の結果とのマージが必要になってくるため、計算できる内容はストリーム処理で全て処理したものと同じになります。システム上の違いとしては、バッチ処理の方が安定で高スループットだったのですが、ストリーム処理エンジンがソフトウェア的に改良されているため、その違いもなくなりつつあります。

kappa architecture

つまり、Lambda Architectureはデメリットがある割に、メリットはなくなりつつあるというのが主張です。そのため全てストリーム処理としてシステムを組み上げるのがこのKappa Architectureの特徴になります。一周回って一番シンプルなものに戻ってきた、という感じのシステムになりますね。

ストリーム処理を実現するデータフローアーキテクチャ

ここまでは主にストリーム処理エンジンをどう構成するか、という話でしたが、最後にこのストリーム処理エンジンを用いた時の、システム全体のデータフロー構成がどのようになるかを見て見たいと思います。その一つがLog-structured data flowで、もう一つが我々が開発しているKARTEです。

Log-structured data flow:ログを1つに集約する

まずはLog-structured data flowというデータフローアーキテクチャを見ていきたいと思います。このデータフローは「ストリームデータは変更されない」という特性を存分に活かしたシステム構成になります。

Log-structured data flow

https://engineering.linkedin.com/distributed-systems/log-what-every-software-engineer-should-know-about-real-time-datas-unifying

Log-structured data flowの特徴は、中央に一つの高性能なストリーム処理エンジンを置き、ストリームデータを集約していることです。ストリームデータが不変であるため、RDBのトランザクションのような仕組みがなくても複数のデータ作成システム(Data Souce)とデータ利用システム(Destination System)を気軽に接続することができます。この構成なら新しいシステムの追加も容易です。

Data Source & Destination System

https://engineering.linkedin.com/distributed-systems/log-what-every-software-engineer-should-know-about-real-time-datas-unifying

Log-structured data flowではログデータを各データ利用システムがSubscribeする仕組みになっており、複数のデータ利用システムがそれぞれお互いに独立に動作することができます。この構成を取ることで、ストリーム処理で面倒な以下の機能に対応することができます。

  • データ利用システムの入力のバッファになる

ストリーム処理ではアクセスの変化などに伴い入力データが急増することがよくあります。この構成の場合、ストリーム処理エンジンがバッファとして作用するため、データ利用システム側ではバッファリングを意識せずに、それぞれのシステムのペースで処理をすることができます。

  • データ利用システムのレプリケーションが簡単に作成できる

例えば、あるETL処理の結果をレプリケーションしようとした場合、従来のバッチ処理システムだと、ETL処理が終わった後に新たにその結果をコピーするという別のETL処理が必要になりました。Log-structured data flowの場合、全く同じETL処理を2つ稼働させることで、同じ集計結果を作成できるため、必要な処理が減ります。

また、たとえレプリカが破損したとしても、再度同じETL処理を稼働すればすぐレプリカを復活させることができます。結果的にシステム全体での可用性や耐障害性も向上されます。

このレプリケーションの応用で、ETL処理をアップデートした場合、古いETL処理を動かしたまま新しいETL処理を同時に稼働させることができるため、古いETL処理を稼働させつつ新しいETL処理が追いついたらETL処理を入れ替えるという運用もできるようになります。

  • ステートフルな集計も効率的に対応できる

一般的にステートフルな集計(例えば合計値を求めるなど)の場合、その中間集計結果をどのように保持しておくかが一つのポイントとなります。特に、マシンが故障した際の復旧方法が問題となります。

もちろん1から再集計しても良いのですが、Log-structured data flowの場合はさらに効率的に処理ができます。工夫としてはストリームデータの特性で書いた「テーブル」データの特性を用います。中間集計結果を「テーブル」として保持しておけば、その中間集計結果の変更は「ログデータ」として記載できます。そのログデータ自身を再度このストリーム処理エンジンに登録してしまうことで、仮にそのマシンが故障した際も、ログデータから即座に中間集計結果が再現できます。

  • ストリームデータを特定のキー(例えばuser_idなど)でパーティショニングできる

ストリーム処理エンジンにパーティションの機能を持たせ、それぞれのパーティションを別のデータ利用システムが処理することで、データの”局所性”を高めることができるようになります。これを利用するとユニークユーザー数などのステートフルな計算がより効率的にできるようになります。

KARTEにおけるストリーム処理

冒頭で言ったように、KARTEもストリーム処理エンジンの一種になります。最後にKARTEでどのようなアーキテクチャでストリームデータを処理しているか紹介します。

KARTE

https://codezine.jp/article/detail/10401

KARTEのアーキテクチャを簡略化して見ると、Log-structured data flowと似たシステム構成になります。trackerと呼ばれるユーザーからのイベントデータを受信するデータ作成システム、反対側にAnalyzeと呼ばれるイベントデータを解析するデータ利用システムがあり、その間にストリーム処理エンジンがあるという形になります。

さらに詳細に見ると、ストリーム処理エンジンはGoogle Cloud PubSubとRedisという二つのシステムに別れています。2つに別れているのは、それぞれのシステムのレイテンシの違いに理由があります。Google Cloud PubSubはLog-structured data flowを意識しているようで、高信頼な反面、KARTEの一部の処理で必要となるレイテンシを満たせませんでした。例えば、接客のためにユーザのイベントを0.x秒以内に解析に行う、という処理があります。そのため信頼性はやや落ちるものの非常に高速なRedisを使い分けることで、要求されるレイテンシに応じたストリーム処理を行う構成になっています。

最後に

ストリーム処理の実現方法について、一通り学んできました。ポイントとしては、どのアーキテクチャでも「データが変更されない」というストリームデータの特性をうまく利用していることです。この特性により、ストリーム処理やリアルタイム解析のデータを高速に処理し続けることが可能になっています。データのこの特性を使うことで、従来のバッチ処理システムとは大きく変わったアーキテクチャになるのは非常に面白いですねよね。

KARTEのより詳細なシステムアーキテクチャを知りたい方は、弊社エンジニアによるCodeZineの連載も合わせてご覧いただけると、より深く理解いただけるかと思いますので、こちらもよろしくお願いします。

ウェブ接客プラットフォーム「KARTE」を運営するプレイドでは、KARTEを使ってこんなアプリケーションが作りたい! KARTE自体の開発に興味がある!というエンジニア(インターンも!)を募集しています。
詳しくは弊社採用ページ
またはWantedly
をご覧ください。 もしくはお気軽に、下記の「話を聞きに行きたい」ボタンを押してください!

参考文献