Optimización de Código
Mejora el rendimiento y la confiabilidad visibles para el usuario mientras mantienes el código comprensible y el costo operativo proporcionado. Optimiza la carga de trabajo que existe; no agregues complejidad para una escala futura hipotética.
Flujo de trabajo
- Define el objetivo y las restricciones. Identifica el endpoint, trabajo, consulta, carga de trabajo y resultado deseado relevantes. Elige solo las métricas que importan: por ejemplo, latencia p95/p99, rendimiento, CPU, memoria, tasa de asignación, cantidad de consultas a la base de datos, viajes de ida y vuelta de dependencias, tasa de errores o saturación. Preserva el comportamiento, la consistencia, la seguridad y los contratos de error existentes. Haz una pregunta enfocada solo cuando falte un objetivo o carga de trabajo que impida una elección sólida; de lo contrario, infiere lo que puedas de la solicitud y el código.
- Establece una línea base. Usa trazas, perfiles, planes de consulta, métricas, pruebas existentes o un benchmark representativo. Mantén la entrada, el entorno y la concurrencia comparables antes y después. No afirmes una aceleración basada en la intuición o en una única medición ruidosa. Evita ejecutar pruebas de carga o análisis costosos de consultas en producción sin autorización.
- Encuentra el cuello de botella. Traza la ruta crítica de extremo a extremo y usa perfilado o instrumentación para distinguir trabajo de CPU, bloqueo, tiempo de base de datos, esperas de red, serialización, contención de bloqueos y encolamiento. Inspecciona el patrón real de consultas/llamadas y los datos representativos. Prefiere la evidencia sobre la limpieza general o la micro-optimización. Si el cuello de botella no está establecido, informa la incertidumbre y propone la medición más pequeña y útil antes de realizar cambios invasivos.
- Elige el cambio efectivo más pequeño. Optimiza un cuello de botella material a la vez cuando sea práctico. Preserva la salida, el orden, el comportamiento de fallo, la semántica de transacciones y las comprobaciones de seguridad. No intercambies corrección o confiabilidad por una victoria en el benchmark. Sigue las convenciones del repositorio y evita refactorizaciones no relacionadas.
- Verifica ambos resultados. Ejecuta pruebas de corrección relevantes y compara la misma carga de trabajo representativa antes y después. Verifica que la métrica objetivo haya mejorado y que las restricciones—especialmente la latencia de cola, errores, memoria y saturación de dependencias—no hayan retrocedido. Si no hay una comparación significativa disponible, dila y evita presentar un cambio no medido como una optimización probada.
- Informa el resultado. Indica brevemente el cuello de botella y la evidencia, el cambio realizado o recomendado, la validación y el impacto medido (o por qué no se pudo medir), y cualquier compensación material. Para una auditoría o solicitud de plan, prioriza los hallazgos por evidencia, impacto probable, esfuerzo y riesgo en lugar de cambiar código.
Optimización de bases de datos y acceso a datos
- Cuenta las consultas e inspecciona los patrones de acceso ORM/base de datos. Elimina lecturas o escrituras N+1 con una combinación, lote u operación basada en conjuntos apropiada; verifica el número de consultas y preserva el orden de los resultados y el comportamiento de registros faltantes.
- Obtén solo las columnas y filas necesarias. Usa paginación o streaming para conjuntos de resultados grandes; empuja el filtrado y la agregación adecuados a la base de datos en lugar de cargar grandes conjuntos de datos en la memoria de la aplicación.
- Usa el plan de consulta y los predicados reales para justificar los índices. Verifica la selectividad, las necesidades de ordenación/unión, los índices existentes y la sobrecarga de escritura/almacenamiento. No agregues índices especulativos o duplicados. Valida el plan y la latencia con datos representativos; usa el análisis basado en ejecución solo en un entorno seguro.
- Reduce los viajes de ida y vuelta innecesarios y mantén las transacciones y los alcances de bloqueo no más grandes de lo necesario. No debilites restricciones, aislamiento, validación o consistencia como atajo.
- Agrega almacenamiento en caché solo cuando la reutilización medida lo justifique. Define frescura, invalidación, alcance de clave y comportamiento de fallo; no introduzcas una caché como respuesta predeterminada a una consulta lenta.
Optimización de backend y llamadas a servicios
- Elimina llamadas redundantes, agrupa solicitudes compatibles y paraleliza solo las llamadas que sean verdaderamente independientes. Mantén la concurrencia acotada, preserva el orden/dependencias requeridos y considera la capacidad del downstream.
- Reutiliza clientes y grupos de conexiones. Establece plazos/tiempos de espera que se ajusten al presupuesto de la solicitud y propaga la cancelación donde sea compatible.
- Reintenta solo fallos transitorios cuando la operación sea segura de reintentar. Acota los intentos y el tiempo total de reintento, usa backoff con jitter y evita reintentar en múltiples capas de manera que amplifique la carga. No agregues reintentos, interruptores de circuito o colas sin un modo de fallo demostrado que aborden.
- Reduce cargas útiles sobredimensionadas o serialización repetida solo cuando sean materiales para la ruta medida. Mantén intacta la semántica de fallo del servicio requerido; haz explícita la degradación gradual para dependencias opcionales.
Entorno de ejecución, confiabilidad y mantenibilidad
- Prefiere la corrección de mayor apalancamiento respaldada por evidencia: mejora la complejidad algorítmica, elimina el trabajo repetido, reduce la asignación/copia innecesaria o aborda el bloqueo y la contención antes que las micro-optimizaciones de bajo nivel.
- Mantén el uso de recursos acotado bajo concurrencia: considera memoria, colas, grupos, fan-out, tiempos de espera, cancelación y contrapresión. Asegúrate de que los fallos no creen tormentas de reintentos, trabajo ilimitado o fugas de recursos.
- Trata el rendimiento y la confiabilidad como resultados a nivel de sistema. Un llamante más rápido que sobrecarga una base de datos o aumenta las tasas de error no es una optimización.
- Aplica YAGNI: no agregues una dependencia, abstracción, caché, índice, capa asíncrona, opción de configuración o cambio de arquitectura para un cuello de botella hipotético. Prefiere un cambio local claro; si se requiere complejidad, explica el beneficio medido y su costo de mantenimiento.
- No elimines la validación de entrada, autorización, observabilidad o comprobaciones de seguridad para ahorrar un tiempo insignificante. No reemplaces código legible con una micro-optimización más difícil de mantener a menos que la ganancia medida sea material.
Notas de verificación
- Compara cargas de trabajo equivalentes con ejecuciones repetibles y suficientes muestras para distinguir la señal del ruido. Usa distribuciones (especialmente latencia de cola) cuando sea relevante, no solo promedios.
- Verifica el comportamiento funcional y los casos límite afectados por agrupación, almacenamiento en caché, paralelismo, paginación, reintentos o concurrencia. Agrega pruebas enfocadas donde protejan un invariante significativo, como el número de consultas, el orden, el comportamiento de tiempo de espera o la alternativa de servicio opcional.
- Vuelve a perfilar después del cambio: los cuellos de botella se mueven, y una mejora local puede trasladar el costo a otro servicio o recurso.
- Si la optimización no produce una mejora material o su complejidad supera el beneficio, reviértela o recomiéndala en contra.