- Durante la migración de la base de código de Scala 2.13 a Scala 3 se produjo una degradación de rendimiento inesperada
- En los entornos iniciales de prueba y despliegue, todas las métricas estaban normales, pero después de unas horas el lag de Kafka aumentó
- En las pruebas de carga, se confirmó una caída brusca del rendimiento al procesar mensajes de manera granular
- A través del análisis con async-profiler se reveló que el origen era un bug de evaluación ineficiente de cadenas en la librería Quicklens
- Tras actualizar la librería, el rendimiento se recuperó, y se destacó la necesidad de vigilar las diferencias de comportamiento de librerías entre versiones de Scala
Proceso de migración del servicio
- El servicio existente se migró de Scala 2.13 a Scala 3.7.3
- Era un servicio centrado en recolección de datos sin uso de macros, donde el rendimiento era un componente crítico
- Luego de aplicar cambios en dependencias, opciones del compilador, tipos y sintaxis, la compilación fue exitosa
- En el entorno de pruebas y en el despliegue por fases, los logs y métricas también se mostraron normales
- Se verificó que los indicadores de infraestructura, JVM y aplicación también estaban en buen estado
Degradación de rendimiento sin causa aparente
- Aprox. 5-6 horas después del despliegue, se observó un aumento del Kafka lag
- Aun sin un pico de datos, hubo disminución del throughput por instancia
- Tras el rollback, el throughput se recuperó de inmediato, confirmando que el cambio de código era la causa
Análisis de rendimiento y rastreo de la causa
- En pruebas de carga, al inicio no se reprodujo la regresión de rendimiento
- La caída brusca del throughput ocurrió solo con segregación de mensajes y payloads heterogéneos
- Se realizaron pruebas de volver atrás una a una las librerías de dependencia (serialización, SDK de DB, imagen de Docker, librería de configuración, etc.) sin cambios
- Como resultado del perfilado de CPU con async-profiler,
- En Scala 3 hubo una fuerte alza de uso de CPU en el compilador JIT y la etapa de decodificación
- En la parte superior de la Flamegraph, las llamadas a Quicklens ocupaban la mitad del tiempo total de CPU
- En Scala 2.13, esa misma llamada apenas llegaba a un 0,5%
Causa raíz del problema
- Ocurrió en Scala 3 un bug de evaluación de cadenas ineficiente en la librería Quicklens
- La corrección correspondiente se incluyó en GitHub PR #115
- Después de actualizar la librería, se eliminó la diferencia de rendimiento entre Scala 3 y 2.13
Lecciones y recomendaciones
- La dependencia de metaprogramación en una librería puede causar diferencias de rendimiento entre versiones de Scala
- Aun cuando la migración se complete correctamente, hay que realizar benchmark de hotspots y cuellos de botella
- En servicios con requisitos de alto rendimiento, en lugar de asumir que “funciona bien”, es esencial una validación basada en medidas reales
- Se requiere una verificación previa para prevenir situaciones donde el benchmark revele cuellos de botella que el código no muestra
1 comentarios
Opiniones en Hacker News
Así es como deberían escribirse los blogs técnicos. Es difícil que la IA reemplace un proceso de razonamiento de este nivel.
La primera pregunta era simple: “¿por qué tendríamos que actualizar?”
inlinefunciona como parte del sistema de macros.Si usas
inlineen un parámetro, le indicas al compilador que inserte la expresión en el punto de llamada.Pero si eso crece mucho, le pone una gran carga al compilador JIT.
En Scala 2,
@inlineera solo una sugerencia, pero en 3 se aplica de forma obligatoria.Por eso, cambiar simplemente
@inlineporinlinees un gran error.registeren el viejo C/C++.Al principio era obligatoria, pero conforme mejoró la optimización pasó a ser solo una recomendación y al final se ignoró.
El
inlinede C++ siguió un proceso parecido.inlinede forma agresiva casi en todas partes.Lo hace para eliminar la sobrecarga de lambdas en funciones como
map.Casi no hubo problemas de rendimiento, pero al combinarse con el sistema de macros de Scala puede generar expresiones complejas y ahí sí causar problemas.
Tuve una experiencia parecida al actualizar Ruby 2→3.
No basta con subir solo el lenguaje: hay que poner al día todas las dependencias para que el sistema se estabilice.
Los problemas de inferencia de tipos de Scala 2 siguen sin resolverse, y en cambio lo que cambió fue el lenguaje.
En otras palabras, ignoraron lo que pedía el mercado e hicieron un producto que nadie quería.
PS: de verdad hace falta crear una suite real de pruebas unitarias para el compilador.
Pero la reescritura de Scala 3 no resolvió los problemas de velocidad de compilación ni de tooling, y el proyecto perdió por completo su impulso.
No sé si en pleno 2025 alguien arrancaría un proyecto nuevo en Scala.
Scala se siente como un lenguaje hecho por académicos, y que se haya puesto de moda un rato en la industria hasta parece raro.
Ahora todas las herramientas tienen que adaptarse a Scala 3, e incluso IntelliJ todavía no lo soporta del todo bien.
Habría sido mejor mejorar Scala 2 de forma gradual, pero parece que se enfocaron solo en el éxito académico.
Por ejemplo, incluso con algo como
early returnhay mucha discusión, como en el texto de tpolecat, mientras que Kotlin lo soporta sin problema.El compilador de Scala tiene miles y decenas de miles de pruebas, y
el directorio oficial de pruebas junto con el sistema de community build validan millones de LOC.
Sobre todo en cambios grandes, como una actualización de versión del lenguaje, eso es indispensable.
Nosotros hacemos benchmarks continuos de herramientas escritas en C++, pero por el ruido del entorno es difícil lograr consistencia en los resultados.
Estamos pensando en compararlas ejecutándolas varias veces sobre la misma máquina.
El error fue crear una segunda sintaxis y empujarla como si fuera el futuro.
Eso también ralentizó el ecosistema de tooling.
Incluso puedes convertir el estilo automáticamente con el compilador o con scalafmt.
Ya no solo existe la sintaxis con llaves: ahora también está la sintaxis por indentación, o sea, el doble.
La sintaxis de
matchse siente demasiado verbosa y como una imitación de Python.En ese entonces Scala recibió atención gracias a Spark, pero dejó pasar la oportunidad de crecer como lenguaje comercial.
Ahora Spark se usa con Python, y en el lugar de lenguaje moderno de la JVM quedó Kotlin.
Al final, Scala da la impresión de haber vuelto a ser un lenguaje académico.
Basta ver el Scala Adoption Tracker.
Las nuevas funciones de Scala 3 todavía tienen el potencial de innovar otra vez el ecosistema del lenguaje.
Por ejemplo: explicación de Capture Checking
Java absorbió parte del atractivo de Scala al agregar características funcionales.
En mi experiencia, la demanda del mercado también es mínima.
Solo quedó así porque Google limitó el soporte a Java.
En el mercado total de la JVM apenas ronda el 10% de participación.
Si pasas auditorías de seguridad (PCI-DSS, etc.), es obligatorio mantener librerías recientes.
Yo más bien suelo mantener viejas las dependencias.
Las versiones nuevas traen bugs nuevos, cambios de mantenedores y también riesgos de seguridad.
Da la impresión de que al principio solo habían subido algunas. Separarlo en pasos pequeños es lo normal, pero parece que tuvieron mala suerte.
El problema no era Scala 3 en sí, sino la interacción de varios factores.
Aun así, hay que tener cuidado porque las librerías específicas de Scala incluyen la versión de Scala dentro de su versión.
Quedó muy clara la expresividad y la seguridad de tipos de Scala.
Como en el texto de Li Haoyi, también resulta bastante atractivo como lenguaje sustituto de Python.
Eso es todavía más importante cuando abundan las librerías que abusan de funciones mágicas.