Hat Scala 3 uns verlangsamt?
(kmaliszewski9.github.io)- Während der Migration der Codebasis von Scala 2.13 auf Scala 3 kam es zu unerwartetem Performance-Verlust
- In Test- und Deployment-Umgebungen waren anfangs alle Metriken normal, aber nach einigen Stunden stieg der Kafka-Lag
- Bei den Load Tests wurde ein Durchsatzabfall bei fein granularer Nachrichtenverarbeitung festgestellt
- Durch die async-profiler-Analyse zeigte sich, dass ein Bug in der Kettenauswertung der Quicklens-Bibliothek die Ursache war
- Nach dem Update der Bibliothek erholte sich die Leistung, was die Notwendigkeit unterstreicht, Leistungsunterschiede zwischen Scala-Versionen in Bibliotheken zu beachten
Migrationsprozess des Services
- Der bestehende Service wurde von Scala 2.13 auf Scala 3.7.3 migriert
- Es handelte sich um einen datensammelnden Service ohne Einsatz von Makros, bei dem Leistung eine kritische Komponente ist
- Nach dem Anwenden von Abhängigkeits-, Compiler-Optionen sowie Typ- und Syntaxänderungen war der Compile erfolgreich
- In Testumgebung und gestuftem Deployment wurden Logs und Metriken ebenfalls als normal gemeldet
- Infrastruktur-, JVM- und Anwendungsebenen-Metriken wurden durchgehend als gesund eingestuft
Unklare Ursache der Leistungsabnahme
- Etwa 5 bis 6 Stunden nach dem Rollout trat ein Anstieg des Kafka-Lags auf
- Auch ohne Daten-Spikes sank die Verarbeitungsrate pro Instanz
- Nach einem Rollback stieg die Durchsatzrate sofort wieder an, wodurch bestätigt wurde, dass die Codeänderung die Ursache war
Leistungsanalyse und Ursachenforschung
- In den Lasttests ließ sich die Performance-Regressions initial nicht reproduzieren
- Der Durchsatzabfall trat nur bei feingranularer Verarbeitung und heterogenen Payloads auf
- Durch sequentielles Zurücksetzen abhängiger Bibliotheken (Serialization, DB SDK, Docker-Image, Konfigurationsbibliotheken usw.) ergaben sich keine Änderungen
- Die CPU-Profilsanalyse mit async-profiler ergab, dass
- in Scala 3 die CPU-Auslastung von JIT-Compiler und Decoding-Phase stark anstieg
- im Flamegraph belegt ein Quicklens-Aufruf die Hälfte der gesamten CPU-Zeit
- in Scala 2.13 machte derselbe Aufruf nur rund 0,5 % aus
Grundursache
- In der Quicklens-Bibliothek trat in Scala 3 ein Bug mit ineffizienter Kettenauswertung auf
- Die zugehörige Korrektur ist in GitHub PR #115 enthalten
- Nach dem Update der Bibliothek wurde der Performance-Unterschied zwischen Scala 3 und 2.13 behoben
Lektionen und Empfehlungen
- Eine metaprogrammierende Abhängigkeit von Bibliotheken kann zu Performance-Unterschieden zwischen Scala-Versionen führen
- Auch wenn eine Migration erfolgreich abgeschlossen ist, sollten Hotspots und Engpässe benchmarked werden
- In Leistungs-kritischen Services ist eine Validierung auf Basis realer Messdaten zwingend erforderlich, statt nur zu unterstellen, dass „es funktioniert“
- Vorbeugende Checks sind erforderlich, um Situationen zu vermeiden, in denen der Benchmark statt des Codes den Engpass offenlegt
1 Kommentare
Hacker-News-Kommentare
So sollten technische Blogposts geschrieben sein. Es ist schwer vorstellbar, dass AI solche Denkprozesse auf diesem Niveau ersetzt.
Die erste Frage war einfach: „Warum sollten wir überhaupt upgraden?“
inlineTeil des Makro-Systems.Wenn man
inlinebei Parametern verwendet, weist man den Compiler an, den Ausdruck am Aufrufort zu inlinen.Wenn das groß wird, belastet das den JIT-Compiler erheblich.
In Scala 2 war
@inlinenur ein Hinweis, aber in 3 wird es zwingend angewendet.Daher ist es ein großer Fehler, einfach
@inlinedurchinlinezu ersetzen.register-Schlüsselwort in C/C++ passiert ist.Anfangs war es zwingend, aber mit besseren Optimierungen wurde es nur noch eine Empfehlung und schließlich ignoriert.
inlinein C++ hat einen ähnlichen Weg genommen.inlinefast überall aktiv.Bei Funktionen wie
map, um den Lambda-Overhead zu vermeiden.Leistungsprobleme gab es kaum, aber in Kombination mit Scalas Makro-System könnten komplexe Ausdrücke Probleme verursachen.
Ich hatte eine ähnliche Erfahrung beim Upgrade von Ruby 2 auf 3.
Man sollte nicht nur die Sprache selbst anheben, sondern den gesamten Dependency-Stand aktualisieren, damit das System stabil bleibt.
Die Probleme der Typinferenz aus Scala 2 sind noch immer nicht gelöst, stattdessen wurde nur die Sprache verändert.
Es ist, als hätte man die Marktanforderungen ignoriert und ein Produkt gebaut, das niemand wollte.
PS: Man sollte wirklich eine echte Unit-Test-Suite für den Compiler bauen.
Aber der Scala-3-Rewrite hat die Kompiliergeschwindigkeit und Tooling-Probleme nicht gelöst und dem Projekt seinen Schwung komplett genommen.
Ich frage mich, ob 2025 noch jemand ein neues Projekt mit Scala anfangen würde.
Scala wirkt wie eine von Akademikern gebaute Sprache, und dass sie in der Industrie zeitweise populär war, erscheint eher ungewöhnlich.
Jetzt müssen alle Tools auf Scala 3 angepasst werden, und selbst IntelliJ unterstützt es noch nicht vollständig.
Es wäre besser gewesen, Scala 2 schrittweise zu verbessern; stattdessen scheint man sich nur auf den akademischen Erfolg konzentriert zu haben.
Zum Beispiel gibt es selbst über
early returnviel Streit, wie in tpolecats Artikel, während Kotlin das problemlos unterstützt.Der Scala-Compiler hat Tausende, Zehntausende Tests, und
das offizielle Testverzeichnis sowie das
Community-Build-System prüfen Millionen LOC.
Gerade bei großen Änderungen wie einem Sprachversions-Upgrade ist das unverzichtbar.
Wir benchmarken kontinuierlich ein in C++ geschriebenes Tool, aber wegen Umgebungsrauschen ist es schwer, konsistente Ergebnisse zu bekommen.
Wir überlegen, es mehrfach auf derselben Maschine auszuführen und dann zu vergleichen.
Das Problem war, eine zweite Syntax zu schaffen und sie als Zukunft zu verkaufen.
Dadurch wurde auch das Tooling-Ökosystem langsamer.
Mit dem Compiler oder
scalafmtkann man den Stil auch automatisch umwandeln.Jetzt gibt es doppelt so viele Varianten: Klammer-Syntax und Einrückungs-Syntax.
Die
match-Syntax wirkt zu weitschweifig und wie eine Python-Kopie.Damals bekam Scala dank Spark viel Aufmerksamkeit, verpasste aber die Chance, sich als kommerzielle Sprache zu etablieren.
Heute ist Spark bei Python gelandet, und den Platz der modernen JVM-Sprache hat Kotlin eingenommen.
Am Ende wirkt es, als sei Scala wieder zu einer akademischen Sprache geworden.
Das zeigt auch der Scala Adoption Tracker.
Die neuen Funktionen von Scala 3 haben das Potenzial, das Sprachökosystem erneut zu innovieren.
Zum Beispiel: Erklärung zu Capture Checking
Java hat durch funktionale Features einen Teil von Scalas Attraktivität absorbiert.
Meiner Erfahrung nach ist auch die Marktnachfrage minimal.
Das liegt nur daran, dass Google die Java-Unterstützung eingeschränkt hat.
Im gesamten JVM-Markt kommt Kotlin nur auf etwa 10 % Anteil.
Wenn man Security-Audits (PIC-DSS usw.) durchläuft, ist das Aktuellhalten von Bibliotheken zwingend.
Ich neige eher dazu, Dependencies lange in älteren Ständen zu halten.
Neue Versionen bringen neue Bugs mit, dazu Wechsel bei Maintainers oder Sicherheitsrisiken.
Anfangs wurde wohl nur ein Teil aktualisiert. In kleine Schritte zu zerlegen ist üblich, aber hier hatten sie wohl Pech.
Das Problem war nicht Scala 3 selbst, sondern das Zusammenspiel mehrerer Faktoren.
Man muss nur aufpassen, weil Scala-spezifische Bibliotheken die Scala-Version oft im Versionsschema enthalten.
Scalas Ausdruckskraft und Typsicherheit kamen gut zur Geltung.
Wie in Li Haoyis Artikel gezeigt, ist Scala auch als Python-Alternative attraktiv genug.
Das ist besonders wichtig, je mehr Bibliotheken mit magischen Features überladen sind.