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

  1. URL Única: No puede probar múltiples URLs en una prueba
  2. Sin JavaScript: No puede ejecutar JavaScript (solo obtiene HTML)
  3. Escenarios simples: Sin flujos de usuario complejos
  4. 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.