Problema
Los pipelines de ingestión de logs son el cuello de botella de muchas arquitecturas de observabilidad. En entornos basados en AWS, la tendencia es lanzar instancias Graviton (ARM) para reducir la factura, pero la mayoría de los agentes tradicionales, como Logstash, siguen ejecutándose sobre la JVM. La combinación de alto consumo de RAM, tiempos de arranque lentos y licencias de software puede hacer que el costo total supere el beneficio esperado del hardware más barato.
El desafío recurrente es decidir si una solución nativa (por ejemplo, un binario estático escrito en Rust) realmente entrega más eventos por segundo y menor gasto operativo, y cómo validar esa hipótesis sin invertir semanas de pruebas manuales.
Causa
-
Sobrecarga de la JVM
La JVM necesita un heap mínimo que, en instancias con 1 GB o menos, provoca swapping o throttling de créditos de CPU en la familia t4g. El proceso también arranca con varios megabytes de código y dependencias, lo que influye en el RSS y en la latencia de arranque. -
Plugins y dependencias no esenciales
Logstash carga todos los plugins disponibles por defecto, aunque la pipeline solo use unos pocos. Cada plugin añade clases, JARs y configuraciones que aumentan el consumo de memoria y el tiempo de compilación de la pipeline. -
Modelo de precios basado en instancia
La facturación de EC2 se basa en la clase de instancia, no en la carga real. Si la pipeline no utiliza la CPU disponible, se paga por recursos ociosos. En Graviton, la relación precio‑rendimiento es mejor, pero sigue siendo lineal con la familia elegida. -
Cuellos de botella externos
Cuando la salida es un clúster OpenSearch, el rendimiento del pipeline se ve limitado por la capacidad de ingestión del clúster, no por el agente de logs. Esto enmascara mejoras internas si no se mide adecuadamente.
Solución
Una metodología reproducible para comparar cualquier agente de ingestión (JVM o binario estático) en AWS Graviton:
1. Definir la carga de trabajo representativa
- Seleccionar un formato de log real (por ejemplo, Apache combined).
- Crear un archivo de 500 k líneas que incluya los patrones que la pipeline debe parsear.
- Mantener la misma configuración de filtros (grok, date, mutate) y destinos (file, OpenSearch).
2. Normalizar el entorno de ejecución
- Usar la misma AMI base (Amazon Linux 2023) y la misma versión del kernel.
- Desactivar servicios no esenciales (firewalld, auditd) para evitar ruido en CPU/RSS.
- Aplicar SELinux en modo enforcing y fail2ban para seguridad, pero sin afectar el benchmark.
3. Seleccionar instancias Graviton comparables
- c7g.large (2 vCPU, 4 GiB) como referencia de “baseline”.
- c7g.medium (1 vCPU, 2 GiB) para validar la hipótesis de “más trabajo con menos dinero”.
- t4g.small (2 vCPU burstable, 2 GiB) para observar el comportamiento bajo throttling de créditos.
4. Automatizar la ejecución y recolección de métricas
- Utilizar
stress-ngpara generar carga constante y evitar fluctuaciones de CPU. - Capturar
top -b -n 1yps -o pid,rss,cmdal final de cada iteración. - Exportar métricas a CloudWatch mediante
aws cloudwatch put-metric-datapara análisis posterior.
5. Calcular coste por evento
- Obtener el precio on‑demand de cada tipo de instancia (
aws pricing get-products). - Incluir cargos de Marketplace si corresponde (ej. $0.03 /h).
- Dividir el coste total de la ejecución (tiempo en segundos × precio por hora) entre el número de eventos procesados.
6. Repetir con al menos tres iteraciones y tomar la mediana
- La mediana elimina outliers provocados por GC pauses o picos de red.
7. Analizar el punto de saturación del sink
- Si el destino es OpenSearch, medir el throughput del clúster con
_cat/indices?vy comparar con la tasa de envío del agente. - Ajustar el número de shards y el tamaño del bulk para equilibrar la carga.
Con este proceso, cualquier agente que entregue un binario estático (Rust, Go, C++) puede ser puesto a prueba contra Logstash sin depender de datos específicos del proyecto original.
Cuándo aplicar esta solución
- Síntomas de alto gasto: factura de EC2 que supera el presupuesto aunque la CPU esté bajo el 30 %.
- Problemas de memoria: OOM killer en instancias pequeñas o throttling de créditos en t4g.
- Necesidad de reducir latencia de arranque: pipelines que deben escalar rápidamente bajo demanda (por ejemplo, en eventos de seguridad).
- Entornos con OpenSearch o Elasticsearch: cuando el agente de ingestión es el único componente que se puede optimizar sin tocar el clúster de búsqueda.
No es adecuada si la pipeline depende de plugins exclusivos de Logstash que no están implementados en la alternativa (ej. connectors JMS, Azure Event Hubs). En esos casos, la sustitución completa no es viable y se debe evaluar un enfoque híbrido.
Código
#!/usr/bin/env bash
set -euo pipefail
# Variables
LOGFILE="apache_combined.log"
PIPELINE_CONF="pipeline.conf"
ITER=3
INSTANCE_TYPE="c7g.medium"
REGION="us-east-1"
# 1. Lanzar instancia temporal
INSTANCE_ID=$(aws ec2 run-instances \
--image-id ami-0abcdef1234567890 \
--instance-type "$INSTANCE_TYPE" \
--region "$REGION" \
--tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=log-bench}]"
--query "Instances[0].InstanceId" --output text)
# 2. Esperar a que esté disponible
aws ec2 wait instance-status-ok --instance-ids "$INSTANCE_ID" --region "$REGION"
# 3. Copiar artefactos
PUBLIC_IP=$(aws ec2 describe-instances --instance-ids "$INSTANCE_ID" \
--query "Reservations[0].Instances[0].PublicIpAddress" --output text)
scp -i ~/.ssh/mykey.pem "$LOGFILE" "$PIPELINE_CONF" ec2-user@"$PUBLIC_IP":~/bench/
# 4. Ejecutar benchmark
ssh -i ~/.ssh/mykey.pem ec2-user@"$PUBLIC_IP" bash -s <<'EOS'
cd ~/bench
for i in $(seq 1 $ITER); do
/usr/local/bin/ferrostash -f "$PIPELINE_CONF" -i "$LOGFILE" > out_$i.log 2>&1 &
PID=$!
sleep 60 # tiempo suficiente para procesar todo el archivo
kill $PID
ps -p $PID -o rss= >> rss_$i.txt
done
EOS
# 5. Recopilar resultados
scp -i ~/.ssh/mykey.pem ec2-user@"$PUBLIC_IP":~/bench/rss_*.txt ./results/
# 6. Terminar instancia
aws ec2 terminate-instances --instance-ids "$INSTANCE_ID" --region "$REGION"
Verificación
-
Consistencia de salida
- Comparar
diff -q out_1.log out_2.logpara asegurar que todas las iteraciones generan el mismo número de líneas que el archivo de entrada.
- Comparar
-
Rendimiento
- Calcular eventos por segundo:
events=$(wc -l < "$LOGFILE");throughput=$(echo "$events / 60" | bc)(asumiendo 60 s de ejecución).
- Calcular eventos por segundo:
-
Uso de memoria
- Revisar los valores de RSS en
rss_*.txt. Un binario estático debería registrar menos de 100 MiB en una instancia c7g.medium.
- Revisar los valores de RSS en
-
Coste por evento
- Obtener precio por hora (
aws pricing get-products ... --service-code AmazonEC2 --filters Type=TERM_MATCH,Field=instanceType,Value=c7g.medium). - Multiplicar por el tiempo de ejecución (en horas) y dividir entre
events.
- Obtener precio por hora (
-
Cuello de botella del sink
- Ejecutar
curl -s http://opensearch-node:9200/_cat/indices?vantes y después del benchmark; la diferencia endocs.countdebe coincidir conevents.
- Ejecutar
Si los valores de throughput y coste por evento son superiores a los obtenidos con Logstash bajo la misma configuración, la alternativa es viable.
Notas adicionales
- Creditos de CPU en burstable: en t4g, monitorea
CPUCreditBalancecon CloudWatch. Un balance en cero indica que la instancia está limitada y los resultados no son comparables. - Tamaño mínimo recomendado: los binarios estáticos en Rust pueden arrancar con 512 MiB, pero para evitar swapping bajo carga sostenida, 2 GiB es una práctica segura.
- Cobertura de plugins: antes de migrar, verifica que los plugins críticos estén implementados. En el caso de FerroStash, la cobertura es del 88 % de los plugins oficiales de Logstash 9.x.
- Imagen Docker: si prefieres contenedores, la diferencia de tamaño de imagen (142 MiB vs 899 MiB) reduce el tiempo de pull y el espacio en disco de los nodos de orquestación.
- Persistencia de configuraciones: almacena
pipeline.confen S3 y usaaws s3 cpal iniciar la instancia para mantener la consistencia entre ejecuciones.
Con esta guía, cualquier equipo de DevOps puede validar de forma objetiva si una solución sin JVM aporta mejoras reales de rendimiento y coste en sus pipelines de logs, sin depender de un caso aislado.