Confluent Parellel Consumer - KafkaネイティブなアプローチでConsumerを極限までスケールする

# はじめに
一般的なストリーム処理では、アプリケーションはProducer API経由でTopicからイベントを取り込み、処理後に別のTopicに加工したイベントを送ることによりデータ処理をします。しかしKafkaと通信するアプリケーションにはそれ以外の利用パターンも多く存在し、場合によってはアプリケーションのスループットが充分引き出せない場合もあります。。Partitionによる並列処理能力を超えるスループットが必要な場合、DBやその他ストレージにデータを書き込んだり、処理内で別の同期通信処理 (e.g. REST API通信) を行う場合です。

この課題への解法には、[Goで書いたり](https://medium.com/swlh/how-to-consume-kafka-efficiently-in-golang-264f7fe2155b)、[Reactiveプログラミングモデルのアプローチを取ったり](https://www.youtube.com/watch?v=-ioxYn9Vlao)様々ありますが、それなりに抜本的な解法の変更となるのが一般的です。

本エントリではまた別のアプローチとして[Confluentがオープン化したConfluent Parallel Consumer](https://github.com/confluentinc/parallel-consumer)について説明します。

# Kafka ConsumerとPartitionと並列処理能力
Kafka Consumerを利用したアプリケーションは同じID (`group.id`) を指定することによりConsumer Groupとして並列処理をする事が可能です。しかしこの並列処理能力は無尽蔵に増加させる事は出来ず、対象TopicのPartition数がその並列処理能力の限界値となります。それ以上のConsumerを追加しても処理には参加できません。
![kafka parallelism.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213933/b41273fa-4ec1-a1c8-a966-e2cd8521881d.png)
これは技術的なという理由ではなく論理的な理由で制限が設けられています。KafkaはStreamにおけるイベント順序を保証しますが、これはStream (より具体的にはTopic) 全体ではなく分割されたPartition単位での保証です。複数のConsumerに同一Partitionへのアクセスを許容するとイベントの処理順序の保証ができません。

Partitionの数を増やし、Consumer Groupのサイズをそれに合わせて増やせば処理能力はほぼ線形に延びますが、KafkaがPartitionを管理する上での制約や負荷もあり無尽蔵に増やす事は出来ません。また、本来欲しいのはPartition単位の順序保証ではなくKey単位の順序保証であるケースがほとんどですが、KafkaはOFFSET (ストリームのどの地点までConsumer Groupが処理を終えたのか) 情報をPartition単位で管理する仕組みでありKey単位のOFFSETトラッキングができません。

# Confluent Parallel Consumerのアプローチ
Confluent Parallel Consumerのアプローチは、PartitionではなくKey単位の順序保証をする事による並列処理能力の向上です。Consumerというだけあってクライアント側で作動するライブラリですが、Consumer Groupベースでの並列処理に加えて1 Consumerが複数のスレッドを扱うことにより並列処理をするモデルです。 (ここでのスレッドはJVMが管理する論理的なスレッド) 内部で処理管理用のキューを利用します。
![parallel-consumer-key-level.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213933/1878c5c9-b9d9-e73e-58ee-5b7d63a51765.png)
* [「Introducing the Confluent Parallel Consumer」](https://www.confluent.io/ja-jp/blog/introducing-confluent-parallel-message-processing-client/)より

Keyの順序保証が不要なモードもあります。最大スレッド数は指定できるので、処理順序保証が不要な場合にはスレッド数の指定で並列処理能力を制御できます。Keyベースでの順序保証をするモードではKeyの数が並列処理能力の上限とはなりますが、Keyの数が多ければ多いだけ高い並列能力が見込めます。
![local threads.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213933/8dac016d-6fb5-71eb-9c6c-9c64fa259a1a.png)

# Key単位のOffset管理 - Offset Encoding
Confluent Parallel Consumerは通常のKafka Consumer同様にTransactionやExactly Onceセマンティックスをサポートしますが、その為にはKey単位のOffset管理をする必要があります。Confluent Parallel Consumerでは、PartitionとしてCommit可能 (それ以前の全てのOffsetが処理済み) なOffsetをコミットし、以降の不完全な状態のオフセットをシリアライズ&圧縮&BASE64エンコードした上で、Offsetコミット時にメタデータの一部として合わせて送ります。

このOffset Encodingの処理ではさらに、データによって圧縮効率の異なる2つの方法でシリアライズ&圧縮し、サイズの小さい方を採用して保存しています。
![offset encoding.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213933/1524a426-2e2e-913d-52a6-031463b19cff.png)
* [「Introducing Confluent labs Parallel Consumer client」](https://www.confluent.io/ja-jp/events/kafka-summit-europe-2021/introducing-confluent-labs-parallel-consumer-client/)より

# おわりに
Confluent Parallel Consumerは[CSID Accelerator (Customer Solutions & Innovations Division)](https://www.confluent.io/confluent-accelerators/)というプロジェクトの一環で、Confluentがプロフェッショナルサポートを別途提供しているものです。実際のお客様エンゲージメントから派生した要望であり、かつConfluent Platformとして機能提供が出来ない領域を補完する役割を果たしています。

中でもこのParallel ConsumerのソースはApache Licence 2.0で[オープン化されており、どなたでもご利用する事が可能です](https://github.com/confluentinc/parallel-consumer)。