
2025/12/08 0:08
Scala 3 slowed us down?
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
Summary:
Scala 2.13 で書かれたサービスを Scala 3.7.3 にアップグレードしました。すべてのユニットテストとステージングデプロイは合格しましたが、新しいビルドでは実際のトラフィック下で深刻なパフォーマンス低下が発生しました:数時間後に Kafka のレイテンシーが増加し、全体のスループットが約 50 % 減少しました。プロファイル解析によって、コード内でメタプログラミングに使用されている
quicklens ライブラリが Scala 3 上では CPU 負荷が大幅に増加していることが判明しました。この問題はこの依存関係に限定されており、他のライブラリは段階的なロールバックで除外されました。quicklens を新しい Scala 3 対応バージョンに更新すると、パフォーマンスは移行前と同等に回復しました。このケースは、小さなライブラリアップグレードでも本番ワークロードで重要な回帰を隠す可能性があることを示しており、言語移行時には詳細なプロファイリングの重要性を強調しています。
Summary Skeleton
What the text is mainly trying to say (main message)
Scala 2.13 のサービスを Scala 3.7.3 に移行しましたが、そのアップグレードは実際のワークロード下でのみ現れる深刻なパフォーマンス回帰を引き起こしました。
Evidence / reasoning (why this is said)
数時間運用後に Kafka のレイテンシーが上昇し、ロールバックでスループットが復旧、プロファイル解析で
quicklens が 50 % の CPU 負荷増加を示しました。この問題は Scala 3 上のライブラリの非効率に起因し、アップグレードで修正されました。
Related cases / background (context, past events, surrounding info)
移行には依存関係の更新、コンパイラオプション、構文変更が含まれ、すべてのテストはローカルとステージングで合格しました。他のライブラリを段階的にロールバックしても問題は解決せず、
quicklens に特有の問題であることが明らかになりました。
What may happen next (future developments / projections written in the text)
quicklens をアップグレードした後、Scala 3 のパフォーマンスは Scala 2.13 と同等になり、再デプロイ前にさらなるベンチマークが必要になることを示唆しており、将来の移行でも同様のプロファイル手順を含めるべきです。
What impacts this could have (users / companies / industry)
遅延は本番データ処理や顧客向けサービスに影響し、メタプログラミングライブラリを使用する組織にとってリスクがあることを示す教訓であり、慎重なパフォーマンス検証の重要性を強調しています。
本文
クリックベイトでしょうか? そうでもありません。
言語の問題なのかコンパイラの欠陥なのか? それも確実に違います。
むしろ、急ぎで行った移行作業の一環だったと言えます。そこで得た教訓を共有します。
あるサービスをリフレッシュしていたとき、そのプロセスの一部としてコードベースを Scala 2.13 から Scala 3 に移行しました。以前にも何度かやった経験があり、概ね良好な結果でした。ただし、マクロ魔法(macro wizardry)が絡むプロジェクトになると状況は変わります。
今回対象だったサービスにはマクロは一切ありませんでしたが、データ取り込みの中心に位置していたため、性能は後回しにならない重要項目です。
通常通り依存関係やコンパイラオプションを更新し、型・構文上の変更も行いました。そのうち数点の難しい暗黙的解決と設定派生(config derivation)を解消した結果、Scala 3.7.3 でビルドが通りました 🎉 テストはすべて合格し、ローカルではエンドツーエンドフローも完璧に動作。そこでテスト環境へ変更を展開しましたが、問題なく動きました。ログにも警戒すべき点はなく、インフラから JVM、アプリケーションレベルまでの指標も健全でした。
それを踏まえて段階的リリースを開始しましたが、状況は引き続き良好に見えました。サービスを監視し続けましたが、仕事は完了したように感じていました。
しかし、予想通りではありませんでした。
謎の遅延
5〜6時間後、いくつかの環境で Kafka のレイテンシ(lag)が増加し始めました。これは決して珍しい現象ではなく、多くの場合データスパイクが原因です。当社にはその対処に十分な機材があります。通常は手を打たずともレイテンシは自動的に解消されます。
今回だけは何かがおかしかったのです。上流側の負荷は比較的穏やかなものだったにも関わらず、サービスのインスタンス数が多く必要になりました ― すなわち1インスタンスあたりの処理速度が低下したということです。これには困惑しました:どうしてこれらの環境だけで処理速度が落ちるのでしょう?
結局、変更をロールバックすることで速さは戻りました。
より深く掘り下げて
テストに戻り、特にロードテストを行いました。しかし、本番環境と同様に回帰は確認できませんでした。そこで負荷パターンやメッセージの粒度を変えて試しましたが、より細かく異種なワークロードでは処理速度が大幅に低下することが判明。
それでも原因は分からず、依存関係が疑わしいと考え、一つずつシリアライゼーションライブラリ、データベース SDK、ベース Docker イメージ、設定ライブラリをロールバックしました。どれも影響はありませんでした。
そこで本格的に調査を進めるため、async-profiler を用いてプロファイルを取得しました。すると次のような事実が明らかになりました:
- Scala 3 の CPU プロファイルは Scala 2.13 と比べて大きく異なる
- JVM レベルでは JIT コンパイラが CPU 時間を支配し、アプリケーションレベルではデコード処理が主
- Scala 3 のフレームグラフ上で長い quicklens 呼び出しが目立つ
以前は透明に機能していた(実際使っていること自体気づいていなかった) quicklens が、Scala 3 では総 CPU 時間のほぼ半分を占めるようになりました。Scala 2.13 ではわずか 0.5 % 程度しかサンプルに現れませんでした。
実際には Scala 3 におけるチェーン評価が非効率になる微妙なバグが存在し、これが JIT が多く時間を費やす原因でもありました。ライブラリのアップデート後、Scala 3 での性能と CPU 特性は Scala 2.13 とほぼ差異なくなりました。
学び
バグ自体の詳細は興味深いものですが(SoftwareMill チームが発見してくれたことに敬意を表します)、ここで強調したいポイントは別です。マクロやメタプログラミングを利用するライブラリは、Scala のバージョン間で極端に振る舞いが変わる可能性があります。
移行がスムーズでサービスが Scala 3 でも正常に動作している場合でも、性能が「あればいい」ではなく「必須」のケースでは仮定せずに自らホットスポットを把握しベンチマークするべきです。さもないと、コードはあなたの代わりにベンチマークを行い、知らなかった場所でボトルネックが明らかになることがあります。