この記事はAWS re:inventにて聴講した「Optimizing Kafka workloads with Amazon Lambda」というセッション内容にプラスアルファでまとめたものです。
現地参加組ですが、楽しいセッションだったのでご共有します。
Apache Kafkaとは
AWSでの資格勉強の中でもあまり登場をすることはないKafkaは、実際に利用をしている人以外からするとあまり馴染みのないプラットフォームかもしれません。
そのため、まずは簡単にKafkaに関してまとめます。
Apache KafkaについてAWSでは下記のように説明されています。
Apache Kafka は、ストリーミングデータをリアルタイムで取り込んで処理するために最適化された分散データストアです。ストリーミングデータは、通常、データレコードを同時に送信する数千のデータソースによって継続的に生成されるデータです。ストリーミングプラットフォームは、この絶え間ないデータの流入を処理し、データを順次および段階的に処理する必要があります。
引用:https://aws.amazon.com/jp/what-is/apache-kafka/
ストリーミングデータという言葉からすると、AWSサービスで言うところのAmazon Kinesis Data Streams(KDS)に類似しているように思えます。
実際にその通りで、KDSやSQSと比較されることの多いプラットフォームサービスです。
いずれもpub/sub方式の分散型キューイングとして動作をされるために、金融システムのカード決済やショップの注文システム、飛行機の予約のといったようなトランザクションを非同期処理させる際のメッセージキューとして利用することが主要な利用用途の一つになります。
Apache Kafkaは2011年にLinkedIn社で開発されて、その後にOSS化され現在はApacheソフトウェア財団によって管理をされています。
AWSではAmazon Managed Streaming for Apache Kafka(MSK)としてマネージドサービス展開されており、面倒なZookeeperなど作り込まなくてもPaaSのように使うことができます。また、Amazon MSK ServerlessもGAされており、サーバレス版であれば煩わしいスケーリングなどに悩まされることも減ります。
Kafkaの最適化を考える
ここからセッションで扱った内容を中心に記載をしていきます。
メッセージの処理失敗の影響を最小限に
KafkaやSQSをメッセージキューイングサービスとして、オーダリング(順番)を重視するシステムに使うことがあります。
例えばイベントや航空機のチケットがそれで、先着要素のあるものは「キューにメッセージが含まれた順に処理される(FIFO)」ことが求められます。
特に問題なくメッセージが処理されるような場合であれば、KafkaやSQSのFIFO機能を利用することでこの要件は満たされるのですが、問題はメッセージの処理に失敗した場合です。
チケットの予約トランザクションが走って席を確保、あとは決済処理を外部のクレジットカード決済代行やアクワイアリングシステムに処理してもらうだけというところで、APIを叩いた先のシステムが過負荷によって決済がタイムアウトしてしまうといったパターンが考えられます。
この場合に、例えば特定のカードブランドだけがその状況であれば、そのカードブランド利用者は仕方がないねとなるのですが、順序性を厳守とした場合には、カードの利用を待たずキューが詰まったような状態になってしまうことが想定されます。
これはエラーメッセージのハンドリングに失敗したパターンとして想定されるものです。
後続メッセージを失敗させないためにどうすれば良いでしょうか。
上記の例以外でも考えられつ対策としてセッションでは下記のような方法が紹介をされました。
-
リトライポリシーの適切な設定
再試行回数を限定し、連続的な再試行によるシステムブロックを防止 -
エラー送信先の活用
失敗したメッセージを自動的に別のキュー(SQS、別のKafkaトピック、S3バケットなど)に移動 -
パーティション戦略
パーティションの並列処理や分散最適化、パーティション内部での順序性の維持 -
イベントソースマッピングの活用
重要でないメッセージを事前にフィルタし、不要な処理を防ぐ -
バッチ処理の活用
複数メッセージの一括処理やバーチャルバッチレスポンスのサポート
基本的には「失敗した後にキューの先頭で延々と待たせないこと」「失敗しても全体に影響が出ないように分散させること」「想定外のメッセージはフィルタすること」といった方針となります。
これらの手法を組み合わせることで、失敗時にも他のメッセージへの影響を最小限に抑えることができます。
トランザクションを高速化したい(Kafka)のみ
Apache Kafkaのみを用いる方法でトランザクションの処理を高速化するにはどうしたら良いでしょうか。
今回はセッションで取り上げられたような構成である、サーバレスなMSK→Lambdaなアーキテクチャを題材として考えてみます。

向上性能
恒常の処理性能を上げたいのであれば次のような手法が考えられます。
- コンシューマであるLambda側の処理性能向上
Lambdaの割り当てメモリを増やす - 並列数の増加
KafkaのTopic数を増やす。
一般的にはラグを感じない処理時間は1メッセージ300ミリ秒のようで、Kafkaを5Topicに並列化したシステムであると仮定をすると
(1000[ms]/メッセージあたり処理時間[ms]) * Topic数 = 3.33 * 5 = 16.7[1/s]
と計算できます。実際の処理では多少の待ち時間なども発生するため15 ~ 16 TPS(Transoction Per Second)程度に落ち着きます。
スパイク追従性
チケットや新商品の発売開始直後に突如として通常のトランザクション量が何倍、何十倍、何千倍に跳ね上がるようなことが発生することがあります。
Webサービスを構築する際にも大きな課題となることがある、スパイク現象は当然Kafkaにおいても重要になります。
MSK Serverlessでは実際のトラフィックに応じて、その処理性能をスケーリングしてくれます。
そして例によってオンデマンドモードとプロビジョニングモードが存在します。
多くの資格試験などでは「予測できないスパイクにはオンデマンドモード」というのが一般的な考え方として学ぶ対象になりますが、実際な実務環境ではプロビジョニングモードの方が実はスパイクへの追従性が優れています。
セッションでは以下の画像のように急激なトランザクションの増加への追従性の一例が紹介され、プロビジョンドモードの方が5分ほど早く追従されることが紹介されました。


これはスタンバイ状態から上がってくるまでの時間に起因するもので、プロビジョンドモードに関しては"always running and available for you"なホットスタンバイ状態であるために、すぐにスケーリングが可能となります。
一方でオンデマンドモードは名言されませんでしたが、ウォームスタンバイまたはコールドスタンバイのいずれかに該当するために、スケーリングが遅く、追従性が悪化するという問題があります。
(このケースはプロビジョニングが適切な場合に有効であり、あまりにもかけ離れたスパイクやトランザクション増加であればこの限りではありません)
さらなる高速化
ここまではよくある高速化の例ですが、セッションの最後に更なる高速化の方法が紹介されました。
それは、Kafkaとコンシューマーの間にSQSを挟むというものです。
もう一度言います、Kafkaとコンシューマーの間にSQSを挟むことでスループットを大きく向上できます。

サービスを直列追加しただけなのになぜ高速化できるか、一つずつ考えていきましょう。
そもそも、先ほどまでの構成で高速化する際の障壁となる主な要素はなんだったでしょうか。
最大の要因は「最も処理時間がかかる過程で、独立した処理の並列数が大きく制限をされていること」です。
具体的に表現をすれば、Lambdaのバックエンド処理の並列数がKafkaのTopic数だけしか並列化できないことにあります。
せっかくのサーバレスコンピューティングであるLambdaをうまく活用できないことが問題でした。
そこで、バックエンド処理のパブリッシャーをSQSとすることで、SQSの分散能力の高さを生かしてLambda処理を並列化することができます。
Kafka→Lambda→SQSは時間がかかる処理が少ないため、並列数が少なくても高速です。
一方で直列で処理時間のかかるバックエンド処理は、SQSで並列数を増やせます。
結果として、ボトルネックが解消されます。
これが、SQS追加でスループットが向上される仕組みとなります。
セッションでは下記画像のようなデモで、非常に高速な処理性能が紹介されました。

※1000TPSを急に流した場合の追従性
さて、ここで次のようなことを思ったのではないでしょうか。
「Kafkaいらなくね???」
この疑問について考えていきましょう。
イベントデータ基点としてのKafka
今回のセッションでは論じられなかったKafka採用のメリットを最後に記載をします。
- データの再処理
Kafkaではoffsetによってメッセージの処理状態を記憶しており、このoffsetを適切に戻すことで、比較的容易に再処理を行うことができます。SQSはDLQなどで失敗した処理を再実行させることは得意ですが、単純な再処理ではKafkaに軍配が上がります。 - データ/ログ保管
Kafkaの設定に応じてデータ保管を長期間にすることも可能で、offsetを用いて長期間の再処理受付も可能となります。 - 複数コンシューマ対応
KafkaはFlinkやSparkなどと連携させて処理をしたり、さらにデータ分析も行わせるなど、コンシューマーが複数存在することが前提です。SQSではあらかじめメッセージを複製し、SNSと組みあわせる必要があるため、比較してKafkaの大きな利点となります。
冒頭でKafkaはストリームサービスの分散データストアだと引用をしましたが、ストリームデータの基点としてKafkaを使い、スケーラブルなデータ処理部分はSQSで分散処理性能を確保することで、最適化されるシステムが構築可能です。

