Cuando las mejoras de rendimiento impresionantes no importan
(blog.colinbreck.com)- La optimización del rendimiento es una herramienta poderosa para entender sistemas complejos y mejorar productos, pero incluso un resultado 10 veces más rápido puede no cambiar la forma real de trabajar ni el throughput
- Aunque se reduzca la respuesta de una consulta de 5~10 minutos a 30 segundos~1 minuto, si supera el umbral de unos 10 segundos en el que una persona mantiene la atención mientras espera, el efecto percibido es limitado
- Si el trabajo se agrupa en unidades enteras, como 1 o 2 tareas por día, una mejora del 25~50% no alcanza; incluyendo el tiempo de traslado, cada tarea debe quedar en 4 horas o menos para que sea posible hacer 2 por día
- En los pipelines de datos, una etapa lenta aplica backpressure hacia upstream, por lo que, aunque una sola etapa se acelere mucho, el throughput end-to-end puede no aumentar hasta que se eliminen todos los cuellos de botella
- Las mejoras de rendimiento deben evaluarse no por benchmarks individuales, sino según el resultado deseado; si no superan restricciones como mantener la atención, aumentar las unidades de trabajo o mejorar el throughput total, incluso una gran mejora puede tener poco efecto práctico
Por qué las cifras de rendimiento y los resultados reales no coinciden
- El trabajo de rendimiento es gratificante porque puede hacer que los sistemas sean más eficientes y abrir nuevas posibilidades para los clientes
- También ayuda a entender empíricamente cómo interactúan sistemas complejos sometidos a escala y carga
- Al trabajar de cerca con el sistema surgen ideas para mejorar productos y servicios, y algunas de ellas no están directamente relacionadas con la optimización del rendimiento
- Sin embargo, incluso logros atractivos como “10 veces más rápido” o “50% menos recursos” pueden no producir el cambio esperado si no superan restricciones ocultas
Si supera los 10 segundos, se pierde la atención del usuario
- Hay un caso en el que se mejoró el rendimiento de consultas de una nueva base de datos, reduciendo la consulta más costosa de la base de datos anterior de 5~10 minutos a 30 segundos~1 minuto
- El resultado fue una mejora del orden de 10 veces, pero para cambiar la experiencia de usuario hacía falta otra gran mejora más
- En la investigación sobre interacción humano-computadora, el límite para que una persona mantenga la atención en toda una tarea se considera de aproximadamente 10 segundos
- 0,1 segundos es el umbral percibido como feedback inmediato
- Alrededor de 1 segundo es el umbral en el que se mantiene el flujo de trabajo
- Alrededor de 10 segundos es el umbral para mantener la atención en toda la tarea
- Feedback como indicadores de progreso o tiempo estimado puede ayudar a mantener la atención
- Tanto 30 segundos como 5 minutos superan los 10 segundos, por lo que el usuario revisa mensajes, va por un café, inicia una conversación o cambia a otra tarea
- Si el usuario vuelve unos minutos u horas después y la UI ya está cargada, que el tiempo real de espera haya sido de 30 segundos o 5 minutos no genera una gran diferencia en la forma de trabajar
- En ese proyecto, finalmente muchas consultas se redujeron a 10 segundos o menos, y algunas consultas que antes eran imposibles por timeouts también se volvieron posibles
- Además de la latencia de las consultas de datos, la latencia de consultas de metadatos y el tiempo de renderizado de la página web también fueron importantes para la mejora del rendimiento total
- Con mejoras en IO asíncrono y agregación de datos queda la posibilidad de otra mejora de 10 veces, lo que podría hacer que consultas que antes tardaban varios minutos respondan en menos de 1 segundo
El umbral para pasar de 1 tarea diaria a 2
- En un proyecto, mediante automatización de trabajo manual, eliminación de pasos innecesarios, cierta paralelización y postergación de pasos que podían procesarse de forma asíncrona más tarde, el proceso completo se redujo de varias horas a un valor estable de menos de 1 hora
- La mejora fue de aproximadamente 25~50%, pero el proceso completo no cambió por restricciones logísticas
- Puede pensarse en casos como un plomero, electricista o carpintero que debe reservar un lugar, trasladarse y completar el trabajo
- Si se trabaja 8 horas al día y el trabajo en un lugar toma 8 horas, ahorrar 2~3 horas no deja tiempo para trasladarse a un lugar nuevo y terminar otra tarea
- Si cada tarea, incluido el traslado, no queda en 4 horas o menos, no se pueden completar 2 tareas en un día
- Hasta superar ese umbral, las mejoras de eficiencia en pasos intermedios no se traducen en mayor producción
- Gracias al foco en rendimiento, también pueden lograrse mejoras de calidad y estabilidad que impactan directamente en la experiencia del cliente
- Incluso pequeñas mejoras de rendimiento que no fueron un punto de inflexión en producción pueden acelerar la iteración en entornos de prueba y hacer más rápido el desarrollo de funcionalidades y la resolución de defectos
Cuellos de botella en pipelines con backpressure
- Mucha infraestructura de software empresarial incluye pipelines de datos que procesan eventos de múltiples fuentes, como vehículos, equipos de fábrica, teléfonos móviles o transacciones financieras
- Los eventos normalmente se almacenan en logs durables, y servicios downstream los consumen y procesan
- Para alcanzar un alto throughput a gran escala, los logs deben particionarse, y los servicios downstream usan técnicas como batches, pipelining, paralelismo, asignación eficiente de memoria y escalamiento dinámico
- Los cuellos de botella en pipelines de datos son difíciles de encontrar porque los comportamientos del sistema están interrelacionados
- Una etapa lenta aplica intencionalmente backpressure a las etapas upstream
- Si hay varios cuellos de botella, el throughput total no aumenta hasta que se elimina el último
- Dividir un pipeline en etapas y entender las características y límites de rendimiento de cada una es una buena práctica de ingeniería
- Pero mejorar una sola etapa por varios órdenes de magnitud puede no tener efecto en el throughput total
- La cifra importante para mejorar el throughput no es el benchmark de una etapa individual, sino el throughput end-to-end
Un enfoque empírico para encontrar cuellos de botella
- Para entender la dinámica y los cuellos de botella de estos sistemas, resulta útil verificarlos empíricamente desde el inicio del pipeline
- Por ejemplo, empezar por la etapa que lee eventos de un log distribuido y los descarta
- Si solo con esa etapa no se alcanza el throughput objetivo, optimizar las etapas downstream sería una pérdida de tiempo
- Los benchmarks downstream, como cuántas filas por segundo se pueden insertar en una base de datos, también pueden ser importantes, pero el análisis debe empezar desde upstream
- La simulación también es un método valioso para entender sistemas complejos y su rendimiento
El criterio de una mejora de rendimiento es el resultado deseado
- El trabajo de rendimiento es difícil, pero también es un entrenamiento para entender en profundidad sistemas complejos y crear mejores productos
- Si hay que mantener la atención de una persona, la respuesta debe llegar en unos 10 segundos
- Si la unidad completa de trabajo es la restricción, no basta con una mejora porcentual: debe ser posible pasar de 1 tarea diaria a 2
- Para maximizar el throughput de un pipeline con backpressure, a menudo hay que resolver todos los cuellos de botella, no solo uno o dos
- Si no se superan estas restricciones, incluso una mejora de rendimiento del orden de 10 veces puede no producir el resultado deseado
1 comentarios
Comentarios en Lobste.rs
Si se trata de mejorar decenas de veces una etapa que no afecta el throughput total y luego decepcionarse, aquí vale la pena mencionar la ley de Amdahl
Sigo escuchando leyes nuevas, pero como son lo bastante de nicho como para no usarse con frecuencia, termino olvidándolas otra vez. Eso no significa que esas leyes no sean válidas o útiles como conocimiento generacional
Para empezar, me pregunto por qué estaban optimizando una parte que representaba solo una fracción muy pequeña del tiempo total.
No sé si fue por mala orientación o por falta de herramientas de performance. Poca gente intenta conscientemente trabajar en algo con casi ningún impacto; normalmente hay un problema mayor oculto
Estás viendo algún problema, en los resultados de muestreo una función específica aparece como importante y, al mirarla un momento, parece que la implementación se puede mejorar con bastante facilidad. Pero en ese momento estás haciendo otra cosa, así que lo dejas para “después”.
Más tarde recuerdas esa mejora fácil y empiezas, pero aunque el cambio resulta más complicado de lo esperado, aparece la visión de túnel y te dan ganas de resolver el rompecabezas, así que le dedicas mucho tiempo.
En realidad, ese problema de performance era menor. Puede que se viera grande en proporción dentro del contexto que estabas observando en ese momento, que el tiempo absoluto no importara mucho o que el bloqueo estuviera en E/S y no en CPU. Es una especie de nerd sniping contra uno mismo
Aun así lo ignoraron y siguieron con la optimización, y se sorprendieron cuando la mejora total resultó ser menor al 0.1%. Con conocimiento del dominio, era intuitivo que no era una parte cara, pero incluso ayudamos a medir el costo real de performance para evitar que perdieran tiempo
O quizá el benchmark fue engañoso. No todos los sistemas muestran fácilmente el costo de todos los métodos o procesos en el entorno de producción.
Todas estas explicaciones, en distintos grados, se parecen a una disfunción; algunas se acercan más a un problema individual y otras más a un problema del entorno
3.1. Si trabajas en un hyperscaler, tan solo una reducción del 0.1% en cómputo que no es visible en absoluto para los usuarios puede impactar las ganancias lo suficiente como para pagar una casa frente a la playa.
Muchas veces, las prácticas de programación comunes son lentas en general. Por ejemplo, me viene a la mente código orientado a objetos con montones de punteros y muchísimas asignaciones en heap mediante allocators generales. Cualquier cosa que arregles es una parte pequeña del tiempo total, así que en el peor caso no hay solución salvo una reescritura completa. Con suerte, se puede reescribir gradualmente.
Incluso si solo hay dos cuellos de botella, si ambos están dentro de un factor de un solo dígito entre sí, arreglar perfectamente el peor cuello de botella ni siquiera produce una mejora de un factor de un solo dígito. En muchos casos, como dice el texto, hay que superarlo por mucho más para obtener una ganancia significativa.
Además, una computadora se parece más a un sistema distribuido que corre en paralelo. Hay varios CPU, GPU, discos, Ethernet, etc., y la velocidad de un proceso queda limitada por la etapa más lenta del pipeline. Si arreglas esa etapa, limita la siguiente más lenta; y, en el peor caso, hay varias etapas igualmente lentas, por lo que arreglar solo una no aporta ningún beneficio.
Aun así, esta es una interpretación de buena fe; a veces la gente simplemente se engancha con el juego de optimizar y pierde las prioridades o comete errores
Aunque los usuarios no lo noten, reducir el tiempo de cómputo del software sigue siendo algo bueno.
Porque puede reducir costos y hacer más fácil escalar
Si hacer el código más rápido introduce demasiada complejidad, incluso dejando de lado la mantenibilidad, a largo plazo puede generar consecuencias que perjudiquen la performance. Cuando cambia la arquitectura, queda código que ya debería ser innecesario, pero para entenderlo o eliminarlo hay que comprender por completo toda la complejidad.
Que algo “se vuelva más rápido” no debería ser una carta blanca para ignorar preocupaciones de complejidad o mantenibilidad
Estoy en una situación parecida ahora, reduciendo con varios proyectos pequeños una consulta que tardaba 5 minutos a menos de 30 segundos.
Le digo al equipo que a largo plazo no es suficiente, pero sin duda es una mejora y el impacto es grande.
Desde el punto de vista del cliente, la espera baja de un nivel exasperante a uno simplemente molesto.
Ahora el foco no está en la performance por usuario, sino en la performance general. Si optimizas decenas de procesos de 5, 10 o 30 minutos, se reduce mucho la contención con otras partes del sistema. Golpear la base de datos durante 10 minutos es muchísimo tiempo y, al final, todo se vuelve más rápido y todos se benefician