Acabas de lanzar tu API en producción. Todo funciona… hasta que llegan los primeros 100 usuarios y la respuesta pasa de 200ms a 5 segundos. El pánico se apodera de ti.
No, no necesitas más servidores. Necesitas entender dónde se está perdiendo el tiempo. En este post vamos a ponernos el sombrero de detective y rastrear cada milisegundo de tu API: desde que llega la petición hasta que sale la respuesta.
Te voy a enseñar las herramientas y técnicas que usan los equipos de performance para encontrar y eliminar cuellos de botella en cualquier stack: Node.js, Python, PHP o Go. Porque una API rápida no es un lujo, es una necesidad.
¿QUÉ ABORDAR EN EL POST?
Diagnóstico inicial: Medir antes de tocar nada
Contexto técnico:
Antes de optimizar, necesitas saber dónde estás parado. No se puede mejorar lo que no se mide.
- Herramientas básicas:
curl -wpara medir tiempos desde el cliente.- Postman / Insomnia con métricas de respuesta.
- Chrome DevTools (Network tab) para ver el timeline completo.
- Métricas clave:
- TTFB (Time To First Byte): Tiempo hasta que el servidor empieza a enviar datos.
- Latencia de red: Tiempo de viaje de la petición.
- Tiempo de procesamiento: Lo que tarda tu código en ejecutarse.
Ejemplo práctico:
bash
# Medir TTFB y tiempo total de una API
curl -w "TTFB: %{time_starttransfer}s\nTiempo total: %{time_total}s\n" \
-o /dev/null -s https://tuapi.com/endpoint
Pregunta clave:
¿Tu API es lenta por la red, por el servidor o por tu código?
El servidor: ¿Está al límite?
Contexto técnico:
Muchas veces el problema no es tu código, sino que el servidor está ahogado.
- Recursos a monitorizar:
- CPU: Si está al 100%, tu código es pesado o tienes un bucle infinito.
- RAM: Si se está usando swap, el rendimiento cae en picado.
- Disco (I/O): Lecturas/escrituras lentas pueden ralentizar todo.
- Red: Ancho de banda saturado o conexiones abiertas.
- Herramientas:
htop,glancespara CPU/RAM.iotop,iostatpara disco.nethogs,iftoppara red.netstat,sspara conexiones activas.
Ejemplo práctico:
bash
# Ver uso de CPU y RAM en tiempo real htop # Ver estadísticas de disco iostat -x 2 # Ver conexiones activas al puerto 80/443 ss -tulpn | grep :80
Pregunta clave:
¿Tu servidor tiene recursos disponibles o ya está saturado?
El gestor de procesos y servidor web: Configuración y tuning
Contexto técnico:
Tu servidor web (Nginx/Apache) y el gestor de procesos (PM2, Gunicorn, PHP-FPM) tienen configuraciones que afectan directamente el rendimiento.
- Nginx:
- Número de workers (
worker_processes). - Límite de conexiones por worker (
worker_connections). - Buffers y timeouts.
- Número de workers (
- PM2 (Node.js):
- Modo cluster para usar todos los núcleos de la CPU.
- Límite de memoria y reinicio automático.
- Gunicorn (Python):
- Número de workers (
workers = (2 x núcleos) + 1). - Threads y timeouts.
- Número de workers (
- PHP-FPM:
- Número de procesos hijos (
pm.max_children). - Tiempo de ejecución máximo (
request_terminate_timeout).
- Número de procesos hijos (
Ejemplo práctico (Nginx):
nginx
worker_processes auto; # Un worker por núcleo de CPU worker_connections 1024; # Buffers para peticiones grandes client_body_buffer_size 10K; client_header_buffer_size 1k;
Ejemplo práctico (PM2 en modo cluster):
bash
# Iniciar app usando todos los núcleos de la CPU pm2 start app.js -i max --name "mi-api"
Pregunta clave:
¿Tu servidor web está configurado para exprimir al máximo el hardware disponible?
La base de datos: El cuello de botella número 1
Contexto técnico:
Si tu API consulta una BBDD, estadísticamente, ese es el punto más lento.
- Problemas comunes:
- Falta de índices: Consultas que escanean toda la tabla.
- N+1 queries: Hacer 100 consultas en lugar de 1 (típico en ORMs).
- Consultas pesadas: Joins mal optimizados o falta de filtros.
- Sin caché: La misma consulta se ejecuta una y otra vez.
- Herramientas de diagnóstico:
- PostgreSQL:
pg_stat_statements,EXPLAIN ANALYZE. - MySQL:
slow_query_log,EXPLAIN. - MongoDB:
explain()y perfil de consultas lentas.
- PostgreSQL:
Ejemplo práctico (PostgreSQL):
sql
-- Activar logs de consultas lentas ALTER SYSTEM SET log_min_duration_statement = 1000; -- 1 segundo -- Analizar plan de ejecución EXPLAIN ANALYZE SELECT * FROM usuarios WHERE email = 'test@mail.com';
Ejemplo práctico (MySQL):
sql
-- Activar slow query log SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 1; -- Ver índices recomendados EXPLAIN SELECT * FROM pedidos WHERE fecha > '2024-01-01';
Pregunta clave:
¿Tu BBDD está devolviendo solo lo necesario o estás sobrecargándola?
El código: Optimizaciones directas
Contexto técnico:
Aquí es donde tu habilidad como programador marca la diferencia.
- Errores típicos:
- Bucles innecesarios anidados (O(n²)).
- Procesamiento de datos que podría hacerse en la BBDD.
- Conversiones de formato redundantes.
- Uso de librerías pesadas para tareas triviales.
- Técnicas de optimización:
- Asincronía: Usar async/await o promesas para no bloquear el event loop.
- Streaming: Procesar datos en trozos en lugar de cargar todo en memoria.
- Lazy loading: Cargar solo lo que se necesita en ese momento.
- Pool de conexiones: Reutilizar conexiones a BBDD o servicios externos.
Ejemplo práctico (Node.js):
javascript
// MAL: Bloquea el event loop
const data = fs.readFileSync('/archivo-gigante.json');
const result = data.map(item => transformar(item));
// BIEN: Streaming y asincronía
const stream = fs.createReadStream('/archivo-gigante.json', { highWaterMark: 64 * 1024 });
stream.on('data', (chunk) => {
procesarChunk(chunk);
});
Ejemplo práctico (Python):
python
# MAL: N+1 queries con ORM
for user in usuarios:
pedidos = Pedidos.objects.filter(usuario_id=user.id) # Una consulta por usuario
# BIEN: Una sola consulta con join
pedidos = Pedidos.objects.select_related('usuario').all()
Pregunta clave:
¿Estás usando las estructuras de datos y algoritmos adecuados para tu caso de uso?
La caché: El turbo de tu API
Contexto técnico:
La caché es la forma más rápida de mejorar el rendimiento sin tocar ni una línea de código (siempre que se use bien).
- Tipos de caché:
- Caché en memoria: Redis, Memcached para respuestas frecuentes.
- Caché de HTTP: Cabeceras
Cache-Control,ETagen el navegador. - Caché de consultas: Guardar resultados de BBDD que apenas cambian.
- CDN: Servir contenido estático desde servidores cercanos al usuario.
- Estrategias:
- Cache-Aside: La app consulta caché primero, y si no hay, va a BBDD y guarda.
- Write-Through: Escribir en BBDD y caché al mismo tiempo.
- TTL (Time To Live): Definir cuánto tiempo vive cada dato en caché.
Ejemplo práctico (Redis + Node.js):
javascript
const redis = require('redis');
const client = redis.createClient();
async function getUsuario(id) {
// 1. Intentar obtener de caché
const cached = await client.get(`usuario:${id}`);
if (cached) return JSON.parse(cached);
// 2. Si no está en caché, consultar BBDD
const usuario = await db.query('SELECT * FROM usuarios WHERE id = ?', [id]);
// 3. Guardar en caché con expiración (TTL de 1 hora)
await client.setex(`usuario:${id}`, 3600, JSON.stringify(usuario));
return usuario;
}
Ejemplo práctico (Cabeceras HTTP):
nginx
# En Nginx, caché de archivos estáticos por 1 año
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
Pregunta clave:
¿Qué datos se repiten constantemente y podrían servirse desde caché?
Monitorización: El ojo que todo lo ve
Contexto técnico:
No sirve de nada optimizar si no tienes visibilidad de lo que pasa en producción.
- Herramientas recomendadas:
- APM (Application Performance Monitoring):
- New Relic, Datadog, Dynatrace (pagos).
- Elastic APM, Prometheus + Grafana (open source).
- Logs estructurados:
- Usar JSON en logs para poder filtrar y buscar fácilmente.
- Centralizar logs con ELK (Elasticsearch, Logstash, Kibana) o Loki.
- Alertas:
- Configurar alertas cuando el tiempo de respuesta supere un umbral.
- Monitorear errores 500 y 504.
- APM (Application Performance Monitoring):
Ejemplo práctico (Prometheus + Grafana):
yaml
# Configurar métricas personalizadas en Node.js
const client = require('prom-client');
const httpRequestDuration = new client.Histogram({
name: 'http_request_duration_seconds',
help: 'Duración de peticiones HTTP',
labelNames: ['method', 'route', 'status']
});
app.use((req, res, next) => {
const end = httpRequestDuration.startTimer();
res.on('finish', () => {
end({ method: req.method, route: req.route?.path, status: res.statusCode });
});
next();
});
Pregunta clave:
¿Tienes visibilidad real de lo que pasa en tu API o trabajas a ciegas?
Matar cuellos de botella no es magia ni intuición. Es un proceso metódico de medición, análisis y acción.
Empieza siempre midiendo. Luego ataca donde más duele: primero el servidor, luego la configuración, después la BBDD, y finalmente el código. La caché es tu aliada, y la monitorización, tu mejor inversión.
Tu API puede ser rápida. Solo necesitas saber dónde mirar.
