Kafka の勉強 (2日目)

昨日の記事に引き続き、Kafka の設計についてドキュメント を読んでいく。

4.3 Efficiency

4.2 節ではディスクの効率について議論した。 ディスクの効率以外で、この種のシステムでよくある非効率性は、次の2つだ。

  • 小さな多数の I/O オペレーション
  • 過剰なバイトコピー

小さな I/O オペレーションはサーバ側、クライアント側の両方で発生しうる。

この問題を避けるために、Kafka のプロトコルは「メッセージセット」というメッセージをグループ化するための概念を持つ。 これにより、ネットワークのラウンドトリップに伴うオーバーヘッドを償却できる。 また、サーバは複数のメッセージを一度にログファイルに追記でき、consumer は連続する多数のメッセージを一度に取得できる。 このシンプルな最適化により、速度が10倍になる。

もう一つの非効率性はバイトコピーである。バイトコピーはメッセージの量が少ないうちは問題にならないが、負荷が高まってくると非効率性が顕在化する。これを解消するために、Kafka は producer, broker, consumer で共通のバイナリメッセージフォーマットを使用する。 したがって、この三者の間ではデータチャンクを修正なしで転送できる。

broker が保持するメッセージログはそれ自体単なるファイルのディレクトリで、各ファイルはメッセージセットの列を追記していったものである。 メッセージセットのフォーマットは producer や consumer が使うものと同じフォーマットを利用する。 共通のフォーマットをディスク上のフォーマットとしても使うことで、ログチャンクのネットワーク転送を最適化することができる。 最近の Unix にはページキャッシュからソケットへの極めて高度に最適化された転送の仕組みが用意されている。 Linux では sendfile(2) システムコールがそれである。

sendfile について理解するために、データをファイルからソケットに転送する一般的な方法について考える。

  1. OS がディスクからデータを読んでカーネル空間のページキャッシュに入れる
  2. アプリケーションはカーネル空間からデータを読んでユーザ空間のバッファに入れる。
  3. アプリケーションはカーネル空間に存在するソケットバッファにそのデータを書く。
  4. OS はソケットバッファのデータを NIC のバッファに書く。

これは明らかに非効率で、4回のコピーが行われている。 sendfile を利用すれば、ユーザ空間へのコピーをすることなしにページキャッシュから直接ネットワークにデータを送ることができる。

所感

sendfile 便利(小並感

ゼロコピーを達成するために producer, broker, consumer の間でバイナリフォーマットを揃えるのは力強い感じで好感がもてる。 Kafka のレイヤーで一切メッセージをいじれなくなるので、相当強い意思がないとできない最適化だと思った。

Kafka の通信を TLS 化する仕組みとかあるけど、sendfile にこだわるなら平文じゃないと行けないのかなぁ。