- Scala 2.13からScala 3へのコードベース移行の過程で、想定外のパフォーマンス低下が発生
- 初期テストとデプロイ環境ではすべての指標が正常だったが、数時間後にKafka lagが増加
- 負荷テストの結果、細分化したメッセージ処理時にスループットが急低下する現象が確認された
- async-profiler解析により、Quicklensライブラリのチェーン評価の非効率バグが原因であることが判明
- ライブラリを更新した後、性能は回復し、Scalaバージョン間のライブラリ挙動差に注意する必要性が強調された
サービス移行プロセス
- 既存サービスはScala 2.13からScala 3.7.3に移行
- マクロを使用しないデータ収集中心のサービスで、性能が重要な構成だった
- 依存関係、コンパイラオプション、型、および構文変更を適用した後にコンパイル成功
- テスト環境と段階的デプロイでも、ログとメトリクスの両方が正常と表示
- インフラ、JVM、アプリケーションレベルの指標もすべて健全な状態を確認
原因不明の性能低下
- デプロイ後、約5~6時間後にKafka lag増加現象が発生
- データスパイクがない状況でもインスタンスあたりのスループット低下
- ロールバック後にスループットが即時回復し、コード変更が原因であることを確認
性能分析と原因追跡
- 負荷テストでは初期には性能退行(リグレッション)が再現されなかった
- メッセージの細分化と異種ペイロードでのみ処理率の急減が発生
- 依存ライブラリ(シリアライズ、DB SDK、Dockerイメージ、設定ライブラリなど)を1つずつロールバックしてテストしたが変化なし
- async-profilerでCPUプロファイリングを実施した結果、
- Scala 3ではJITコンパイラとデコード工程のCPU使用率が急増
- Flamegraph上部でQuicklens呼び出しが全CPU時間の半分を占める
- Scala 2.13では同一の呼び出しが0.5%程度にとどまる
問題の根本原因
- Quicklensライブラリのチェーン評価の非効率バグがScala 3で発生
教訓と推奨事項
- ライブラリのメタプログラミング依存がScalaバージョン間で性能差を引き起こす可能性がある
- 移行が正常に完了していても、ホットスポットとボトルネックをベンチマークする必要がある
- 性能が重要なサービスでは「正常に動作している」という前提ではなく、実測ベースの検証が必須
- コードではなくベンチマークがボトルネックを露わにする状況を防ぐための事前点検が必要
1件のコメント
Hacker Newsの意見
技術ブログはこういうふうに書かれるべきだ。AIがこのレベルの思考過程を代替するのは難しい
最初の問いは単純で、「なぜわざわざアップグレードしなければならないのか?」だった
inlineキーワードがマクロシステムの一部として動作するパラメータに
inlineを使うと、呼び出し箇所で式をインライン化するようコンパイラに指示するしかしこれが大きいと、JITコンパイラに大きな負担を与える
Scala 2では
@inlineは単なる提案だったが、3では必ず適用されるしたがって、単に
@inlineをinlineに置き換えるのは大きなミスだregisterキーワードがたどった経緯に似ている初期には強制だったが、最適化が発達するにつれて単なる推奨事項になり、最終的には無視されるようになった
C++の
inlineも似たような過程をたどったinlineを積極的に使っているmapのような関数でラムダのオーバーヘッド削減を行うためだパフォーマンス上の問題はほとんどなかったが、Scalaのマクロシステムと結び付くと複雑な式が生まれ、問題になる可能性がありそうだ
Ruby 2→3のアップグレード時にも似た経験があった
単に言語だけを上げるのではなく、依存関係全体を最新化してこそシステムは安定する
Scala 2の型推論の問題は依然として解決されておらず、その代わりに言語だけが変わった
市場の要求を無視して、誰も望まない製品を作ったようなものだ
PS: コンパイラには本物のユニットテストスイートを作るべきだ
しかしScala 3のリライトはコンパイル速度とツーリングの問題を解決できず、プロジェクトの勢いを完全に失わせた
今の2025年に新規プロジェクトをScalaで始める人がいるだろうかと思う
Scalaは学者が作った言語のようで、産業界で一時流行したことのほうがむしろ不思議だった
今ではすべてのツールがScala 3に対応しなければならず、IntelliJですらまだ完全にはサポートできていない
Scala 2を段階的に改善していけばよかったのに、学術的成功にしか関心がなかったように見える
たとえばtpolecatの記事のように
early returnをめぐっても議論が多いが、Kotlinは何の問題もなくサポートしているScalaコンパイラには数千、数万のテストがあり、
公式テストディレクトリと
コミュニティビルドシステムによって数百万LOCを検証している
特に言語バージョンのアップグレードのような大きな変更では必須だ
私たちはC++で書かれたツールを継続的にベンチマークしているが、環境ノイズのせいで結果の一貫性を保つのが難しい
同じマシンで複数回実行して比較する方法を検討している
第2の構文を作り、それを未来だと押し出したのが問題だった
そのせいでツーリングのエコシステムも遅くなった
コンパイラやscalafmtでスタイルを自動変換することもできる
今や中括弧構文とインデント構文の2通りに増えた
match構文が冗長すぎて、Pythonの真似のように見える当時はSparkのおかげでScalaが注目を集めたが、商用言語として発展する機会を逃した
今ではSparkはPythonへ、JVMのモダン言語の座はKotlinが占めている
結局Scalaは再び学術的な言語に戻ったように感じる
Scala Adoption Trackerを見ればわかる
Scala 3の新機能には、言語エコシステムを再び革新する潜在力がある
例: Capture Checkingの解説
Javaが関数型機能を追加したことで、Scalaの魅力の一部を吸収した
私の経験では市場の需要もごく小さい
GoogleがJavaサポートを制限したことでそうなったにすぎない
JVM全体の市場では10%程度のシェアにすぎない
セキュリティ監査(PCI-DSSなど)を受けるなら、最新ライブラリの維持は必須だ
私はむしろ依存関係を古いまま保つほうだ
新バージョンは新しいバグを持ち込み、メンテナ変更やセキュリティリスクもある
最初は一部だけ上げたようだ。小さな段階に分けるのは一般的だが、運が悪かったのだろう
問題はScala 3自体ではなく、複数要因の相互作用だった
ただしScala専用ライブラリはバージョンにScalaバージョンが含まれているので注意が必要だ
Scalaの表現力と型安全性が際立っていた
Li Haoyiの記事のように、Pythonの代替言語としても十分魅力的だ
特にマジック機能を多用するライブラリが多いほど重要だ