4 puntos por GN⁺ 2025-12-09 | 1 comentarios | Compartir por WhatsApp
  • 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

 
GN⁺ 2025-12-09
Opiniones en Hacker News
  • No soy fan de Scala, pero me impresionó el análisis profundo y el proceso de depuración del problema.
    Así es como deberían escribirse los blogs técnicos. Es difícil que la IA reemplace un proceso de razonamiento de este nivel.
    • Al refrescar uno de nuestros servicios, migramos de Scala 2.13 a Scala 3.
      La primera pregunta era simple: “¿por qué tendríamos que actualizar?”
  • En Scala 3, la palabra clave inline funciona como parte del sistema de macros.
    Si usas inline en 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, @inline era solo una sugerencia, pero en 3 se aplica de forma obligatoria.
    Por eso, cambiar simplemente @inline por inline es un gran error.
    • Esta diferencia se parece a lo que pasó con la palabra clave register en 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 inline de C++ siguió un proceso parecido.
    • Kotlin usa inline de 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.
  • Al actualizar las librerías, el rendimiento de Scala 3 quedó casi igual al de Scala 2.13.
    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.
  • El problema de Scala 3 es que nadie lo quería.
    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.
    • Me da tristeza, pero estoy de acuerdo. Scala parecía prometedor por allá de 2010~15.
      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.
    • Intenté aprender Scala varias veces, pero siempre terminé volviendo a Clojure.
      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.
    • Dio justo en el clavo.
      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 return hay mucha discusión, como en el texto de tpolecat, mientras que Kotlin lo soporta sin problema.
    • Decir que “no hay pruebas” es desinformación.
      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.
    • Parece que no leyó bien el artículo. El punto central le pasó por completo de largo.
  • Lo más interesante de este artículo fue que deberíamos tener por defecto pruebas de rendimiento automatizadas y análisis con flamegraphs.
    Sobre todo en cambios grandes, como una actualización de versión del lenguaje, eso es indispensable.
    • Las pruebas de benchmark, a diferencia de las pruebas comunes, requieren una configuración muy fina.
      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.
    • Me da curiosidad qué herramientas de pruebas de rendimiento se usan hoy en día en la JVM.
  • El único problema de Scala 3 es la envidia hacia Python.
    El error fue crear una segunda sintaxis y empujarla como si fuera el futuro.
    Eso también ralentizó el ecosistema de tooling.
    • Hoy en día la mayoría de las herramientas ya soportan la sintaxis nueva.
      Incluso puedes convertir el estilo automáticamente con el compilador o con scalafmt.
    • Muy al estilo Scala, ahora hay varias formas de hacer lo mismo.
      Ya no solo existe la sintaxis con llaves: ahora también está la sintaxis por indentación, o sea, el doble.
    • Habría sido más atractivo si mejor se hubieran comparado con Haskell.
    • Como exfan de Scala, ver ejemplos de la nueva sintaxis me desconcertó.
      La sintaxis de match se siente demasiado verbosa y como una imitación de Python.
  • Yo antes hice una migración de Scala 2.x, y lo más difícil fue esperar las dependencias.
    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.
    • Pero Scala todavía se usa bastante en grandes empresas.
      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
    • No estoy seguro de que Kotlin realmente haya conquistado la JVM.
      Java absorbió parte del atractivo de Scala al agregar características funcionales.
    • En el lado server-side de la JVM, Kotlin casi no tiene presencia.
      En mi experiencia, la demanda del mercado también es mínima.
    • Kotlin es, en la práctica, un lenguaje casi exclusivo de Android.
      Solo quedó así porque Google limitó el soporte a Java.
      En el mercado total de la JVM apenas ronda el 10% de participación.
  • Me pareció raro que se hayan pasado a Scala 3 sin actualizar las versiones de las librerías.
    Si pasas auditorías de seguridad (PCI-DSS, etc.), es obligatorio mantener librerías recientes.
    • Decir que “mantener todo al día es una buena práctica” es una forma de cerrar la discusión.
      Yo más bien suelo mantener viejas las dependencias.
      Las versiones nuevas traen bugs nuevos, cambios de mantenedores y también riesgos de seguridad.
    • A mí también me confundió. En el artículo dicen “actualizamos dependencias”, pero después dicen “tras actualizarlas el rendimiento quedó igual”.
      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.
    • Una vez que entendí la causa del bug, me impresionó menos.
      El problema no era Scala 3 en sí, sino la interacción de varios factores.
    • Mientras más grande sea el cambio, como una actualización de versión del lenguaje, más conviene cambiar una sola cosa a la vez.
    • Si pones restricciones de versión en Maven/Gradle/SBT, eso se mantiene aparte de la versión del lenguaje.
      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.
  • El reporte de bug de SoftwareMill y el de Scala en GitHub fueron una corrección pequeña pero precisa.
    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.
  • La gran lección es que, en una actualización mayor de lenguaje o framework, también hay que actualizar las librerías al mismo tiempo.
    Eso es todavía más importante cuando abundan las librerías que abusan de funciones mágicas.