PayPay Card では、さまざまなリアルタイム処理とバッチ処理を支えるマルチテナントのKafkaクラスターを運用していますが、最近、不可解な性能問題が発生しました。毎日、AWS Glueからの定期バッチ投入の時間帯になると、無関係なワークロードのKafkaプロデューサーのレイテンシーが10ms未満から800ms超へと急騰するという現象です。本記事では、調査の過程、根本原因、そしてスループットや信頼性を犠牲にすることなくクラスター全体の安定性を取り戻した最適化の経緯について解説します。

問題: バッチ投入中に発生するKafkaレイテンシーのスパイク

毎日、複数の2GB超のCSVデータセットがAWS GlueからAmazon MSK(Amazon Managed Streaming for Apache Kafka)に取り込まれます。データは30パーティションのKafkaトピックに書き込まれます。投入時には深刻な性能劣化が見られ、クラスター内のすべてのプロデューサーでProduceリクエストのレイテンシーがスパイクしました。総データ量自体はとりわけ大きいわけではありませんでした。

内部で何が起きていたのか?

  • Sparkの既定の挙動:
    • CSVファイルは分割可能かつ非圧縮のため、並列処理用にチャンクに分割される。
    • spark.sql.files.maxPartitionBytes = 128MBの場合、2GBのファイルは〜16個のタスクになり、それぞれ〜125MBがKafkaに書き込まれる。
  • Kafkaプロデューサーの既定値:
    • 圧縮なし。
    • batch.size = 128KB、linger.ms = 0。
    • Spark 側の高い並列度により、小さくて非効率なバッチが多数生じる(しばしば40〜80KB程度)。

つまり、各Sparkタスクが小さなリクエストを数千回発行することになります。ひとつの2GBファイルだけで、〜32,000件のProduceリクエストがおこなわれ、ブローカー側ではリクエストごとにネットワークとスレッドのオーバーヘッドが発生しました。

Kafkaブローカーへの影響

ブローカーのリクエスト処理スレッドが飽和し、アイドル率が低下、CPU 使用率が上昇し、リクエストキューが膨らみました。その結果、総データ量が特段大きくないにもかかわらず、無関係なプロデューサーまでレイテンシーが800ms超に跳ね上がりました。


解決策: より賢い並列化とプロデューサーのチューニング

SparkとKafkaプロデューサーの構成に対して、狙いを絞った変更を行いました。リクエスト数を減らし、1リクエスト当たりの効率を上げることを基本方針としました。

  • Spark の並列度を制御
    • df.repartition(60)を適用し、60個のタスク(Kafkaパーティション数の2倍)を作成。
    • 各タスクで〜33MBを処理することで、ひとつあたり128KBのバッチが〜267つに。
    • 総リクエスト数は〜32,000 から〜16,000 に減少し、バッチサイズのばらつきも縮小。
  • プロデューサー圧縮の有効化
    • kafka.compression.type=zstd(フォールバックとしてLZ4)を有効化。
    • JSONペイロードのネットワーク転送量を〜50% 削減。非圧縮データ2GBはネットワーク上では〜1GB に。
    • コピー、チェックサム、ディスク書き込みで処理すべきデータが減ったため、ブローカーのリクエスト当たりのCPU負荷が低下。
  • linger とバッファでバッチを平準化
    • linger.ms = 10を設定してレコードをためる時間を確保。
    • 効率を考慮し、batch.size = 128KBを維持。
    • Spark の出力速度がネットワークを上回っても停止しないよう、buffer.memory = 256MBに増加。
    • 結果として平均バッチサイズが2倍になり、リクエスト数が半減。
  • スループットを犠牲にしない信頼性
    • 厳密な配信保証のためにacks = allとenable.idempotence = trueを有効化。
    • スループット最適化はデータ整合性を犠牲にせず。

結果:レイテンシーの安定化とテナントの満足度向上

これらの変更以降、ピーク時のバッチ投入中でもすべてのワークロードで Kafkaプロデューサーのレイテンシーは安定しました。ブローカーのCPUとリクエスト処理メトリクスも正常化し、クラスターはバッチとリアルタイムの双方をスムーズに処理できるようになりました。

主な学び

  • バッチサイズとリクエスト数は重要。総データ量が控えめでも、小さなリクエストが多すぎるとブローカーは飽和する。
  • パーティション数に合わせて並列度を調整。タスクを増やせばよいわけではなく、ワークロードとクラスターに合った最適点を探す。
  • 圧縮とバッチングは味方。ネットワークとCPU負荷を抑えることにつながり、すべてのテナントに恩恵がある。
  • 監視、計測、改善。CloudWatchとブローカーメトリクスは、原因特定と対策検証に不可欠だった。

Product Blogをもっと見る

今すぐ購読し、続きを読んで、すべてのアーカイブにアクセスしましょう。

続きを読む