KafkaがUnix哲学をどのように取り込んだのかを紹介しましょう。あと、LinkedIn?での事例も。
Unixとデータベースが、たどってきた道のりは違うにせよ、それぞれソフトウェア開発のよい設計指針に沿って成長してきたことがわかった。 これまでたどってきた道のりから、今後の展望を見ていこう。
図4-17:Can we improve contemporary data systems by borrowing the best ideas from Unix but avoiding its mistakes?
ここでは、Unix哲学をデータベースの世界にあてはめるとどうなるかを考えよう。
最初にいっておきたいのは、Unixといえども完璧ではないということ。
図4-18:Pros and cons of Unix pipes.
シンプルで統一されたバイトストリームのインターフェイスという考えかたはすばらしいと思うが、Unixにも限界はある。
でも私たちは、こういった弱点を克服したうえでUnix哲学のいいとこ取りをできる手段を知っている。そう、Kafkaとストリームプロセッシングだね。
図4-19:The data flow between stream processing jobs, using Kafka for message transport, resembles a pipeline of Unix tools.
Unix的に見ると、Kafkaはプロセスとプロセスをつなぐパイプのようなもの。そしてSamzaみたいなフレームワークは、標準入出力を読み書きするための標準ライブラリみたいなものだ。*5
Kafka StreamとSamzaは、他のストリームプロセッシングフレームワークよりもUnix的な視点に近い。 StormやSpark StreamingやFlinkなら、ストリームオペレータ(ボルト)のトポロジー(プロセッシンググラフ)を作って、それをメッセージ転送用の自前のフレームワークにつなぐ必要がある。 Kafka StreamsとSamzaには、独自のメッセージ転送プロトコルなどない、Unixツールが常に標準入出力を使うのと同様、オペレータ間の通信はすべてKafka経由で行う。
Kafka Streamsには低レベルのAPIもあるし、DSLを使って処理を定義することもできる。 Kafka StreamsとSamzaの低レベルプログラミングモデルはとても柔軟。各オペレータを個別にデプロイできるし、新しいアプリケーションが増えたときにグラフを拡張させることもできる。 また、グラフ内のすきなところに新しいコンシューマを追加できる。
しかし、Unixのパイプにも問題があった。お手軽にさっとつないでみるのにはいいけど、長期間稼働し続ける大規模なアプリケーションに使うモデルとしてはよくない。 Unix哲学を参考にしてシステムを作るのであれば、この問題を何とかしないといけない。
Kafkaでは、Unixパイプの弱点をこんなふうに克服した。
図4-20:How Kafka addresses the problems with Unix pipes.
KafkaがUnixのパイプと異なるところを簡単にまとめる。
図4-21:Side-by-side comparison of Apache Kafka and Unix pipes.
こういった違いはあるものの、Kafkaを分散データ向けUnixパイプと考えるのには意味があると思う。たとえば、Kafkaのメッセージが流れる順序は固定で、これはUnixパイプのバイトストリームと同じだ。 イベントログのデータを扱うにあたって、これは重要な特性である。これは、AMQPやJMSにはないものだ。
図4-22:Unix tools, stream processors and functional programming share a common trait: inputs are immutable, processing has no global side-effects, and the output is explicit.
Unixツール群とストリームプロセッサーはとても似ていることがわかった。どちらも、なんらかの入力ストリームを読んでそれを処理して、その結果の出力ストリームを作っている。
重要なのは、何らかの処理をしても入力そのものには変化がない(イミュータブルである)こと。 sedやawkで何かのファイルを処理しても、明示的に上書きしない限り入力ファイル自体はそのままである。 また、Unixツールの大半は宣言的である。つまり、入力が同じなら、常に同じ結果が得られる。
このふたつの性質を見ると、何となく関数プログラミングに似ているようにも見える。 必ずしもHaskellなどの関数型言語を使わないといけないというわけではないけれど、 関数型のコードからは多くのメリットが得られる。
図4-23:Loosely coupled stream processors are good for organizational scalability: Kafka topics can transport data from one team to another, and each team can maintain its own stream processing jobs.
Unixライクな設計指針で作られたKafkaは、大規模システムの構築にも対応する。 大規模な組織では、さまざまなチームがそれぞれのデータをKafkaにパブリッシュする。 各チームが独自にストリーム処理ジョブを作ることもできる。 ストリームは複数のコンシューマーに対応しているので、新しいコンシューマーを追加するときにも他チームとの調整などは不要。
私たちはこのアイデアを「ストリームデータプラットフォーム」と呼んでいる。 このアーキテクチャでは、Kafkaのデータストリームがシステム間の通信チャンネルとして機能する。 各チームは、自分たちが受け持つパートがうまく機能することに注力する。 Unixツールを組み合わせて行うのがデータを処理するタスクであるのに対して、 分散ストリーミングシステムを組み合わせて行うのは大規模組織全体の運営だ。*10
Unixライクな手法では、疎結合にすることで大規模システムに立ち向かう。 ストリームのインターフェイスが統一されているおかげで、各コンポーネントを独自に開発・デプロイできる。 Kafkaのバッファ機能や耐障害性のおかげで、システムのどこかの部分で問題が発生したとしても、それが他の部分に派生することはない。 スキーマ管理のおかげで、データ構造の変更を安全に行える。他のチームに迷惑をかけることなく、どんどん開発を進めていけるというわけだ。*11
最後に、LinkedIn?での事例を紹介する。
図4-24:What happens when someone views a job posting on LinkedIn??
LinkedIn?では、企業が求人情報を投稿できて、求職者はそれを見て応募することができる。 LinkedIn?のユーザーが求人の投稿を見たときに、何が起こるのだろう?
求人画面の処理をするサービスが、「member 123 viewed job 456 at time 789」みたいなイベントをKafkaに発行する。これで、この情報がKafkaに格納されたのでいろいろ活用できるようになる。*12
これらのシステムはそれぞれ複雑なもので、それぞれ別のチームがメンテナンスしている。 Kafkaは、耐障害性に優れたスケーラブルなパイプの実装を提供する。 Kafkaを基盤とするストリームデータプラットフォームのおかげで、これらのシステムを個別に開発できるようになり、 より堅牢な方法でそれらを組み合わせられるようになる。