Una API que funciona sin problemas en staging puede desmoronarse en el momento en que llega el tráfico real. El fallo rara vez es dramático. Es una muerte por mil cortes. Un hilo bloqueado aquí. Una consulta redundante allá. Bajo una carga ligera, estas pequeñas ineficiencias pasan desapercibidas. Bajo la presión de producción, se acumulan hasta provocar el agotamiento del pool de hilos, la asfixia de la base de datos y tiempos de respuesta que destruyen la confianza del usuario. La optimización del rendimiento no consiste en encontrar una solución mágica. Consiste en construir un sistema donde cada capa respete la escasez de hilos, memoria y conexiones a la base de datos. Cuando tratas esos recursos como finitos, dejas de reaccionar a las caídas del sistema y empiezas a prevenirlas.

Elimina el Sync-over-Async antes de que te mate

El patrón más destructivo en aplicaciones ASP.NET Core de alto tráfico es el sync-over-async. Se ve cuando alguien llama a .Result o .Wait() en un método asíncrono porque necesita el valor de inmediato y no quiere refactorizar la pila de llamadas. Esa decisión bloquea el hilo que realiza la llamada. El hilo se queda inactivo, esperando un trabajo que ya se está ejecutando en otro lugar, pero el runtime no puede reutilizarlo para otra solicitud.

Cuando suficientes solicitudes hacen esto, el pool de hilos se agota. Tu gráfico de CPU parece saludable porque los procesadores no están ocupados, pero tu latencia se dispara. Las solicitudes se encolan, esperando hilos que nunca se liberarán. La solución es mecánica pero requiere disciplina: usa await en toda la pila de llamadas. Si un método llama a una API asíncrona, debe ser asíncrono él mismo. Aquí no hay atajos. Cada bloqueo síncrono que elimines te devuelve margen de maniobra para el tráfico real.

Deja de desperdiciar trabajo en clientes desconectados

Los clientes se desconectan. Los navegadores cierran pestañas. Las aplicaciones móviles pierden la señal. Si tu servidor no sabe que el cliente se ha ido, seguirá ejecutando consultas a la base de datos, analizando JSON y consumiendo hilos en una respuesta que nadie recibirá. Pasa un CancellationToken a cada operación asíncrona que lo admita. Esto incluye consultas de Entity Framework, llamadas HTTP con HttpClient y cualquier trabajo de fondo de larga duración.

Cuando la conexión se pierde, el token activa la cancelación y el trabajo se detiene inmediatamente. Esto preserva los ciclos de CPU de la base de datos y devuelve los hilos al pool más rápidamente. Es un pequeño cambio en las firmas de los métodos que rinde grandes frutos bajo carga.

Cachea lo que no cambia

Es probable que tu catálogo de productos no cambie entre cada solicitud. Tus indicadores de configuración, ciertamente tampoco. Sin embargo, muchas APIs consultan la base de datos repetidamente para obtener los mismos datos estáticos. El output caching en ASP.NET Core te permite almacenar respuestas renderizadas y servirlas directamente desde la memoria sin volver a tocar tus controladores o la base de datos.

Usa la invalidación basada en etiquetas con cuidado. Cuando actualices un producto, invalida solo la etiqueta asociada a esa categoría o artículo. No necesitas vaciar toda la caché. Esto mantiene alto tu ratio de aciertos de caché (cache hit ratio) y bajo el recuento de consultas a la base de datos.

Corrige primero tu acceso a datos

Las llamadas a la base de datos consumen la mayor parte del tiempo de solicitud en la mayoría de las APIs. Antes de optimizar cualquier otra cosa, mira aquí.

Si estás consultando datos solo para mostrarlos y nunca planeas actualizarlos, añade AsNoTracking() a tus consultas de Entity Framework. EF Core omitirá el seguimiento de cambios y la creación de instantáneas (snapshots).