Benchmarking de Servidores Web con Apache Bench (ab)
Introducción
Apache Bench (ab) es una herramienta de línea de comandos para hacer benchmarking de servidores web HTTP. A pesar de su nombre, funciona con cualquier servidor web (Apache, Nginx, IIS, etc.) y proporciona métricas de rendimiento esenciales incluyendo solicitudes por segundo, tiempos de respuesta, manejo de conexiones y tasas de fallo. Comprender cómo usar ab correctamente es crucial para pruebas de rendimiento, planificación de capacidad y validación de optimizaciones.
El benchmarking adecuado revela cómo se comporta su servidor web bajo carga, identifica cuellos de botella y valida la efectividad de las optimizaciones. Sin benchmarking preciso, está volando a ciegas: haciendo cambios sin saber si realmente mejoran el rendimiento. Apache Bench proporciona una forma simple, confiable y estandarizada de medir el rendimiento del servidor web.
Esta guía completa cubre los fundamentos de Apache Bench, técnicas de uso, interpretación de resultados, escenarios de prueba avanzados y ejemplos del mundo real. Aprenderá cómo realizar pruebas de rendimiento significativas y tomar decisiones de optimización basadas en datos.
Instalación
# Ubuntu/Debian
apt-get update
apt-get install apache2-utils -y
# CentOS/Rocky Linux
dnf install httpd-tools -y
# macOS (incluido con Apache)
# Ya instalado
# Verificar instalación
ab -V
# Salida: This is ApacheBench, Version 2.3
Uso Básico
Benchmark Simple
# Sintaxis básica
ab [opciones] URL
# Prueba simple: 100 solicitudes, 10 concurrentes
ab -n 100 -c 10 http://localhost/
# Opciones explicadas:
# -n 100: Total de solicitudes a realizar
# -c 10: Número de conexiones concurrentes
Comprendiendo la Salida
ab -n 1000 -c 100 http://localhost/test.html
# Ejemplo de salida:
Server Software: nginx/1.24.0
Server Hostname: localhost
Server Port: 80
Document Path: /test.html
Document Length: 1234 bytes
Concurrency Level: 100
Time taken for tests: 2.456 seconds
Complete requests: 1000
Failed requests: 0
Total transferred: 1456789 bytes
HTML transferred: 1234000 bytes
Requests per second: 407.19 [#/sec] (mean)
Time per request: 245.596 [ms] (mean)
Time per request: 2.456 [ms] (mean, across all concurrent requests)
Transfer rate: 579.42 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 1 0.5 1 3
Processing: 12 235 45.2 228 456
Waiting: 11 234 45.1 227 455
Total: 13 236 45.3 229 457
Percentage of the requests served within a certain time (ms)
50% 229
66% 245
75% 258
80% 267
90% 295
95% 318
98% 354
99% 387
100% 457 (longest request)
Métricas Clave Explicadas
Solicitudes Por Segundo
# Métrica más importante
Requests per second: 407.19 [#/sec] (mean)
# Interpretación:
# - Más alto es mejor
# - Indica capacidad de rendimiento del servidor
# - Línea base para comparación
# Ejemplo de comparación:
# Antes de optimización: 407 req/s
# Después de optimización: 1,842 req/s (mejora de 4.5x)
Tiempo de Respuesta
# Dos métricas importantes:
Time per request: 245.596 [ms] (mean) # Perspectiva del usuario
Time per request: 2.456 [ms] (mean, across all concurrent requests) # Perspectiva del servidor
# Perspectiva del usuario:
# - Tiempo desde inicio de solicitud hasta completación
# - Lo que los usuarios realmente experimentan
# - Incluye espera por solicitudes concurrentes
# Perspectiva del servidor:
# - Tiempo de procesamiento real por solicitud
# - Métrica de eficiencia del servidor
# - Excluye espera de concurrencia
Percentiles
Percentage of the requests served within a certain time (ms)
50% 229 # Mediana - solicitud típica
90% 295 # 90% de usuarios ven esto o mejor
95% 318 # Percentil 95
99% 387 # Percentil 99
100% 457 # Peor caso (máximo)
# Por qué importan los percentiles:
# - El promedio puede ocultar valores atípicos
# - Percentiles 95/99 = experiencia de usuario para usuarios más lentos
# - p50, p95, p99 son métricas estándar de la industria
Solicitudes Fallidas
Complete requests: 1000
Failed requests: 0 # Siempre debería ser 0 en sistema saludable
# Tipos de fallos:
# - Conexión rechazada (servidor sobrecargado)
# - Timeout (solicitud tardó demasiado)
# - Discrepancia de longitud (respuestas inconsistentes)
# - Excepciones (errores del servidor)
# Si > 0: Indica problemas, investigar antes de optimizar
Escenarios de Prueba
Prueba de Rendimiento Base
# Establecer línea base con baja concurrencia
ab -n 1000 -c 10 http://localhost/
# Resultados:
# Solicitudes por segundo: 1,250
# Tiempo de respuesta: 8ms (media)
# Solicitudes fallidas: 0
# Propósito: Medir rendimiento de mejor caso
Prueba de Estrés
# Alta concurrencia para probar límites
ab -n 10000 -c 1000 http://localhost/
# Resultados:
# Solicitudes por segundo: 2,840
# Tiempo de respuesta: 352ms (media)
# Solicitudes fallidas: 0
# Propósito: Encontrar capacidad máxima
Planificación de Capacidad
# Probar a diferentes niveles de concurrencia
for concurrency in 10 50 100 200 500 1000; do
echo "Probando concurrencia: $concurrency"
ab -n 5000 -c $concurrency http://localhost/ 2>&1 | grep "Requests per second"
done
# Los resultados muestran comportamiento de escalado:
# c=10: 1,250 req/s
# c=50: 2,450 req/s
# c=100: 3,180 req/s
# c=200: 3,520 req/s (inicio de meseta)
# c=500: 3,610 req/s (ganancia mínima)
# c=1000: 3,450 req/s (degradación de rendimiento)
# Conclusión: Rendimiento óptimo en c=200-500
Prueba de Carga Sostenida
# Prueba de larga duración para verificar estabilidad
ab -n 100000 -c 100 -t 300 http://localhost/
# -t 300: Ejecutar durante 300 segundos (5 minutos)
# Propósito: Detectar fugas de memoria, agotamiento de conexiones, etc.
# Monitorear durante la prueba:
watch -n 1 'ps aux | grep nginx; free -h; ss -s'
Opciones Avanzadas
Prueba Keep-Alive
# Sin keep-alive (nueva conexión por solicitud)
ab -n 1000 -c 100 http://localhost/
# Con keep-alive (reusar conexiones)
ab -n 1000 -c 100 -k http://localhost/
# Comparación:
# Sin keep-alive: 2,350 req/s, 42ms respuesta
# Con keep-alive: 4,180 req/s, 24ms respuesta (78% más rápido)
# Lección: Keep-alive mejora dramáticamente el rendimiento
Solicitudes POST
# Probar POST con datos
ab -n 1000 -c 100 -p post-data.txt -T "application/json" http://localhost/api/users
# Crear post-data.txt:
cat > post-data.txt << 'EOF'
{"name":"Juan Pérez","email":"[email protected]"}
EOF
# Opciones:
# -p: Archivo de datos POST
# -T: Cabecera Content-Type
Cabeceras Personalizadas
# Agregar cabecera de autenticación
ab -n 1000 -c 100 -H "Authorization: Bearer token123" http://localhost/api/protected
# Agregar múltiples cabeceras
ab -n 1000 -c 100 \
-H "Authorization: Bearer token123" \
-H "Accept: application/json" \
-H "User-Agent: LoadTest/1.0" \
http://localhost/api/
# Cabecera de cookie
ab -n 1000 -c 100 -C "session_id=abc123" http://localhost/
Configuración de Timeout
# Establecer timeout de socket (predeterminado: 30 segundos)
ab -n 1000 -c 100 -s 60 http://localhost/slow-endpoint
# -s 60: Timeout de 60 segundos
# Útil para probar endpoints lentos
Salida a Archivo
# Guardar resultados en archivo
ab -n 1000 -c 100 -g results.tsv http://localhost/
# Formato TSV para análisis:
# starttime seconds ctime dtime ttime wait
# Cada fila = timing de una solicitud
# Generar CSV con métricas clave
ab -n 1000 -c 100 http://localhost/ > results.txt
grep "Requests per second\|Time per request\|Failed requests" results.txt > summary.csv
Ejemplos de Prueba del Mundo Real
Ejemplo 1: Antes/Después de Optimización
#!/bin/bash
# compare-performance.sh
URL="http://localhost/"
REQUESTS=5000
CONCURRENCY=500
echo "=== ANTES de Optimización ==="
ab -n $REQUESTS -c $CONCURRENCY $URL | grep -E "Requests per second|Time per request|Failed requests|Transfer rate"
# Hacer optimizaciones (habilitar caché, compresión, etc.)
echo
echo "Haciendo optimizaciones..."
# ... aplicar optimizaciones ...
sleep 5
echo
echo "=== DESPUÉS de Optimización ==="
ab -n $REQUESTS -c $CONCURRENCY $URL | grep -E "Requests per second|Time per request|Failed requests|Transfer rate"
# Resultados:
# ANTES:
# Solicitudes por segundo: 1,450
# Tiempo por solicitud: 344ms
# Solicitudes fallidas: 67
# DESPUÉS:
# Solicitudes por segundo: 5,280 (mejora del 264%)
# Tiempo por solicitud: 95ms (72% más rápido)
# Solicitudes fallidas: 0 (100% confiable)
Ejemplo 2: Contenido Estático vs Dinámico
# Probar contenido estático (archivo HTML)
echo "Contenido estático:"
ab -n 10000 -c 500 -k http://localhost/static.html | grep "Requests per second"
# Resultado: 8,450 req/s
# Probar contenido dinámico (script PHP)
echo "Contenido dinámico:"
ab -n 10000 -c 500 -k http://localhost/dynamic.php | grep "Requests per second"
# Resultado: 1,240 req/s
# Conclusión: Estático 6.8x más rápido (esperado)
# Acción: Cachear contenido dinámico o usar CDN
Ejemplo 3: Rendimiento de Consultas de Base de Datos
# Probar endpoint con consultas de base de datos
ab -n 1000 -c 100 http://localhost/api/products
# Antes de optimización de base de datos:
# Solicitudes por segundo: 85
# Tiempo por solicitud: 1,176ms
# Percentil 95: 2,340ms
# Después de agregar índices y optimización de consultas:
# Solicitudes por segundo: 620 (mejora del 629%)
# Tiempo por solicitud: 161ms (86% más rápido)
# Percentil 95: 287ms (88% más rápido)
Ejemplo 4: Prueba de Limitación de Tasa de API
# Probar limitación de tasa de API
ab -n 10000 -c 100 http://localhost/api/data
# Verificar respuestas 429 (Demasiadas Solicitudes)
ab -n 10000 -c 100 http://localhost/api/data | grep "Non-2xx responses"
# Respuestas no-2xx: 3,450 (limitación de tasa funcionando)
# Ajustar concurrencia para mantenerse bajo el límite
ab -n 10000 -c 10 http://localhost/api/data | grep "Failed requests"
# Solicitudes fallidas: 0 (dentro del límite de tasa)
Ejemplo 5: Rendimiento CDN vs Origen
# Probar servidor de origen
echo "Servidor de origen:"
ab -n 1000 -c 100 http://origin.ejemplo.com/large-image.jpg
# Solicitudes por segundo: 450
# Tasa de transferencia: 12,560 KB/seg
# Probar CDN
echo "CDN:"
ab -n 1000 -c 100 http://cdn.ejemplo.com/large-image.jpg
# Solicitudes por segundo: 3,280 (mejora del 629%)
# Tasa de transferencia: 91,540 KB/seg (629% más rápido)
# Conclusión: CDN proporciona 7x mejor rendimiento
Mejores Prácticas de Benchmarking
1. Probar desde Ubicación Apropiada
# Malo: Probar desde el mismo servidor (poco realista)
ab -n 1000 -c 100 http://localhost/
# Bueno: Probar desde máquina separada
ab -n 1000 -c 100 http://server-ip/
# Mejor: Probar desde ubicación geográficamente distante
ab -n 1000 -c 100 http://remote-server.com/
# Óptimo: Probar desde múltiples ubicaciones y promediar resultados
2. Calentar Antes de Probar
# Solicitudes de calentamiento (preparar cachés)
ab -n 100 -c 10 http://localhost/
# Luego prueba real
ab -n 10000 -c 500 http://localhost/
# Previene efectos de arranque en frío de sesgar resultados
3. Probar Múltiples Veces
#!/bin/bash
# Ejecutar múltiples pruebas y promediar resultados
RUNS=5
URL="http://localhost/"
echo "Ejecutando $RUNS pruebas de benchmark..."
for i in $(seq 1 $RUNS); do
echo "Prueba $i de $RUNS"
ab -n 1000 -c 100 $URL 2>&1 | grep "Requests per second" >> results.txt
done
# Calcular promedio
awk '{sum += $4; count++} END {print "Promedio:", sum/count, "req/s"}' results.txt
# Salida: Promedio: 4,235 req/s
4. Monitorear Recursos del Sistema
# Monitorear durante la prueba (terminal separada)
watch -n 1 'echo "CPU:"; mpstat 1 1; echo "Memoria:"; free -h; echo "Conexiones:"; ss -s'
# O usar herramienta como htop, glances, o nmon
htop
# Verificar cuellos de botella:
# - CPU al 100%: Necesita más CPU u optimizar código
# - Memoria agotada: Agregar RAM u optimizar uso de memoria
# - Alta espera I/O: Cuello de botella de disco, usar SSD u optimizar consultas
5. Probar Escenarios Realistas
# Malo: Probar solo página principal
ab -n 10000 -c 100 http://localhost/
# Bueno: Probar mezcla de endpoints
ab -n 2000 -c 100 http://localhost/
ab -n 2000 -c 100 http://localhost/products
ab -n 2000 -c 100 http://localhost/about
ab -n 2000 -c 100 http://localhost/contact
# Mejor: Usar herramienta que soporte listas de URLs (ej., wrk, siege)
Interpretando Resultados
Identificando Cuellos de Botella
# Escenario 1: Alta req/s pero alta tasa de fallos
# Solicitudes completas: 8,000
# Solicitudes fallidas: 2,000 (20% de fallo)
# Diagnóstico: Servidor sobrecargado, max_connections o procesos trabajadores muy bajos
# Solución: Aumentar recursos del servidor o límites de conexión
# Escenario 2: Baja req/s, CPU baja
# Solicitudes por segundo: 150
# Uso de CPU: 25%
# Diagnóstico: I/O bloqueante, consultas de base de datos, o llamadas a API externas
# Solución: Agregar caché, optimizar consultas, implementar procesamiento asíncrono
# Escenario 3: Buen rendimiento cae con el tiempo
# Inicio: 3,500 req/s
# Después de 5 min: 1,200 req/s
# Diagnóstico: Fuga de memoria, agotamiento de conexiones, crecimiento de archivo de registro
# Solución: Arreglar fugas de memoria, implementar pooling de conexiones, rotar registros
# Escenario 4: Alta variación en tiempos de respuesta
# Media: 150ms
# Percentil 99: 4,500ms
# Diagnóstico: Valores atípicos, pausas de recolección de basura, o consultas lentas ocasionales
# Solución: Investigar solicitudes lentas, ajustar GC, agregar timeouts de consultas
Objetivos de Rendimiento
# Objetivos típicos por tipo de aplicación:
# Sitio web estático:
# > 5,000 req/s (bueno), > 10,000 req/s (excelente)
# Sitio web dinámico (WordPress, etc.):
# > 100 req/s (bueno), > 500 req/s (excelente)
# Servidor API:
# > 1,000 req/s (bueno), > 5,000 req/s (excelente)
# Aplicación pesada en base de datos:
# > 50 req/s (bueno), > 200 req/s (excelente)
# Aplicación en tiempo real:
# > 10,000 req/s (bueno), > 50,000 req/s (excelente)
# Objetivos de tiempo de respuesta:
# < 100ms: Excelente
# < 300ms: Bueno
# < 1s: Aceptable
# > 1s: Necesita mejora
Limitaciones y Alternativas
Limitaciones de Apache Bench
- URL Única: No puede probar múltiples URLs en una prueba
- Sin JavaScript: No puede ejecutar JavaScript (solo obtiene HTML)
- Escenarios simples: Sin flujos de usuario complejos
- Servidor único: Todas las solicitudes desde una máquina
Herramientas Alternativas
# wrk - Herramienta moderna y scriptable de benchmarking
wrk -t 12 -c 400 -d 30s http://localhost/
# siege - Soporta múltiples URLs
siege -c 100 -r 10 -f urls.txt
# hey - Basada en Go, similar a ab
hey -n 10000 -c 100 http://localhost/
# locust - Basada en Python, escenarios complejos
# Requiere script, buena para comportamiento realista de usuario
# JMeter - Basada en GUI, características empresariales
# Buena para planes de prueba complejos y reportes
Script de Prueba Completo
#!/bin/bash
# comprehensive-benchmark.sh
URL="$1"
OUTPUT_DIR="benchmark-results-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$OUTPUT_DIR"
echo "Iniciando benchmark completo de $URL"
echo "Los resultados se guardarán en $OUTPUT_DIR"
echo
# Prueba 1: Línea base
echo "Prueba 1: Línea base (baja concurrencia)"
ab -n 1000 -c 10 -k "$URL" > "$OUTPUT_DIR/01-baseline.txt"
grep "Requests per second\|Time per request" "$OUTPUT_DIR/01-baseline.txt"
echo
# Prueba 2: Carga moderada
echo "Prueba 2: Carga moderada"
ab -n 5000 -c 100 -k "$URL" > "$OUTPUT_DIR/02-moderate.txt"
grep "Requests per second\|Time per request\|Failed requests" "$OUTPUT_DIR/02-moderate.txt"
echo
# Prueba 3: Carga alta
echo "Prueba 3: Carga alta"
ab -n 10000 -c 500 -k "$URL" > "$OUTPUT_DIR/03-high-load.txt"
grep "Requests per second\|Time per request\|Failed requests" "$OUTPUT_DIR/03-high-load.txt"
echo
# Prueba 4: Prueba de estrés
echo "Prueba 4: Prueba de estrés (encontrar límites)"
ab -n 20000 -c 1000 -k "$URL" > "$OUTPUT_DIR/04-stress.txt"
grep "Requests per second\|Time per request\|Failed requests" "$OUTPUT_DIR/04-stress.txt"
echo
# Prueba 5: Carga sostenida
echo "Prueba 5: Carga sostenida (60 segundos)"
ab -t 60 -c 100 -k "$URL" > "$OUTPUT_DIR/05-sustained.txt"
grep "Requests per second\|Time per request" "$OUTPUT_DIR/05-sustained.txt"
echo
# Generar resumen
echo "=== Resumen del Benchmark ===" > "$OUTPUT_DIR/summary.txt"
echo >> "$OUTPUT_DIR/summary.txt"
for file in "$OUTPUT_DIR"/*.txt; do
echo "$(basename $file):" >> "$OUTPUT_DIR/summary.txt"
grep "Requests per second" "$file" >> "$OUTPUT_DIR/summary.txt"
echo >> "$OUTPUT_DIR/summary.txt"
done
echo "Benchmarking completo. Resultados en $OUTPUT_DIR/"
cat "$OUTPUT_DIR/summary.txt"
# Uso
chmod +x comprehensive-benchmark.sh
./comprehensive-benchmark.sh http://localhost/
Conclusión
Apache Bench es una herramienta esencial para pruebas de rendimiento de servidores web. Puntos clave:
Cuándo Usar ab:
- Pruebas rápidas de rendimiento
- Comparación antes/después de optimización
- Planificación de capacidad
- Pruebas de regresión
- Validación de rendimiento en CI/CD
Métricas Clave:
- Solicitudes por segundo (rendimiento)
- Tiempo de respuesta (latencia)
- Percentiles (95, 99)
- Solicitudes fallidas (confiabilidad)
Mejores Prácticas:
- Probar desde máquina separada
- Calentar antes de probar
- Ejecutar múltiples pruebas y promediar
- Monitorear recursos del sistema
- Probar escenarios realistas
- Usar keep-alive (flag -k)
Objetivos de Rendimiento:
- Contenido estático: > 5,000 req/s
- Contenido dinámico: > 100-500 req/s
- APIs: > 1,000 req/s
- Tiempo de respuesta: < 300ms (bueno), < 100ms (excelente)
Apache Bench proporciona métricas de rendimiento rápidas y confiables que permiten decisiones de optimización basadas en datos. Úselo regularmente para validar cambios y asegurar que su servidor web funcione óptimamente bajo carga.


