Caso de Estudio: $240K/Año Ahorrados en AWS para un SaaS de Salud

Isometric flat illustration of a declining AWS cost bar chart from red to green with a stethoscope-cloud icon and DEVOPSARG laptop sticker

Un SaaS de salud llegó a nosotros con un problema simple: su factura AWS había crecido de $12K/mes a $38K/mes en 18 meses, pero su base de usuarios solo se había duplicado. Algo estaba escalando de forma lineal cuando tendría que ser sublineal.

Su VP de Ingeniería fue directo: "Tenemos 50.000 usuarios y gastamos $38K/mes. Nuestro competidor tiene 200.000 usuarios y gasta menos que nosotros. ¿Qué estamos haciendo mal?"

Después de 2 semanas de auditoría y 6 semanas de implementación, bajamos su factura a $18.200/mes — una reducción del 52%, ahorrando $237.600/año. Acá está cada cosa que cambiamos.

La Auditoría

Arrancamos categorizando el gasto por servicio AWS:

Servicio Costo Mensual % del Total
EC2 (nodos EKS) $16.400 43%
RDS (PostgreSQL) $7.200 19%
ElastiCache (Redis) $3.100 8%
S3 + CloudFront $2.800 7%
NAT Gateway $2.600 7%
Data Transfer $2.400 6%
Volúmenes EBS $1.800 5%
Otros $1.700 5%
Total $38.000 100%

Cada línea tenía potencial de optimización. Vamos una por una.

1. EC2/EKS: Right-Size + Spot + Karpenter ($16.400 → $6.800)

Este fue el mayor win. El cluster EKS corría en instancias m5.2xlarge On-Demand porque "eso es lo que sugería la guía Quick Start de AWS."

Cambios realizados:

  • Reemplazamos Cluster Autoscaler con Karpenter
  • Agregamos 15 tipos de instancia a la lista permitida
  • Movimos el 75% de los workloads a instancias Spot
  • Right-sizing de todos los pods en base a 2 semanas de datos de VPA
  • Agregamos HPA a todos los servicios stateless

Escribimos sobre el setup de Karpenter en detalle en nuestro post de Karpenter + Spot + Scale-to-Zero.

Ahorro: $9.600/mes (58%)

2. RDS: Reserved Instances + Read Replicas ($7.200 → $3.600)

Corrían una instancia PostgreSQL RDS db.r6g.2xlarge — On-Demand, Multi-AZ. La base de datos estaba al 15% de utilización de CPU en promedio.

Cambios realizados:

  • Bajamos a db.r6g.xlarge (la CPU solo llegaba al 40% durante el pico con la instancia más chica)
  • Compramos una Reserved Instance 1 año All Upfront (42% de descuento)
  • Agregamos una read replica para las queries de analytics que martillaban el primario
  • Movimos los batch jobs nocturnos para que golpearan la replica en vez del primario
-- Antes: queries de analytics en el primario
SELECT date_trunc('day', created_at), count(*)
FROM patient_records 
WHERE created_at > now() - interval '90 days'
GROUP BY 1;

-- Después: misma query ruteada a la read replica via connection string
-- analytics_db_url = postgres://replica-endpoint:5432/healthdb

Ahorro: $3.600/mes (50%)

3. ElastiCache: Right-Size + Reserved ($3.100 → $1.400)

Corrían cache.r6g.xlarge con 3% de utilización de memoria. Cacheaban session data para 50K usuarios — eso cabe en un cache.r6g.large con margen de sobra.

Cambios realizados:

  • Bajamos a cache.r6g.large
  • Compramos Reserved Instance por 1 año
  • Implementamos TTL en todas las cache keys (tenían 2M de keys sin expiración)

Ahorro: $1.700/mes (55%)

4. NAT Gateway: El Asesino Silencioso del Presupuesto ($2.600 → $800)

Este sorprendió a todos. NAT Gateway cobra $0.045/GB por data processing — y los pods estaban pulleando imágenes Docker a través de NAT en cada deploy.

Cambios realizados:

  • Configuramos VPC endpoints para ECR (se acabó el NAT para los image pulls)
  • Agregamos S3 VPC endpoint (los logs y backups pasaban por NAT)
  • Configuramos VPC endpoints para STS y CloudWatch
  • Movimos el tráfico no esencial a instancias con IPs públicas
# VPC Endpoints que agregamos
aws ec2 create-vpc-endpoint \
  --vpc-id vpc-xxx \
  --service-name com.amazonaws.us-east-1.ecr.api \
  --vpc-endpoint-type Interface

aws ec2 create-vpc-endpoint \
  --vpc-id vpc-xxx \
  --service-name com.amazonaws.us-east-1.s3 \
  --vpc-endpoint-type Gateway

Ahorro: $1.800/mes (69%)

Los costos de NAT Gateway son uno de los ítems más ignorados en las facturas AWS. Cada empresa que auditamos está pagando de más por NAT.

5. S3 + CloudFront: Lifecycle Policies + Compresión ($2.800 → $1.600)

Estaban guardando cada versión de cada archivo para siempre. Los uploads de documentos médicos de hace 3 años seguían en S3 Standard.

Cambios realizados:

  • S3 Intelligent-Tiering para todos los buckets (mueve los datos fríos automáticamente a tiers más baratos)
  • Lifecycle policy: pasar a Glacier después de 1 año para archivos de compliance
  • Habilitamos compresión en CloudFront (Brotli) — redujo el bandwidth un 40%
  • Configuramos cache headers correctos — el hit ratio del CDN pasó de 60% a 94%
{
  "Rules": [
    {
      "ID": "ArchiveOldDocuments",
      "Status": "Enabled",
      "Transitions": [
        {
          "Days": 90,
          "StorageClass": "STANDARD_IA"
        },
        {
          "Days": 365,
          "StorageClass": "GLACIER"
        }
      ]
    }
  ]
}

Ahorro: $1.200/mes (43%)

6. Data Transfer: Mantener el Tráfico Dentro del VPC ($2.400 → $1.200)

Los cargos de cross-AZ data transfer los estaban comiendo vivos. Servicios en us-east-1a hablaban con servicios en us-east-1c, pagando $0.01/GB en cada dirección.

Cambios realizados:

  • Configuramos topology-aware routing en Kubernetes (preferir misma AZ)
  • Movimos los servicios que se hablan mucho a la misma AZ
  • Comprimimos los payloads inter-servicios (gRPC con protobuf en vez de JSON)
# Topology-aware routing
apiVersion: v1
kind: Service
metadata:
  name: user-service
  annotations:
    service.kubernetes.io/topology-mode: Auto

Ahorro: $1.200/mes (50%)

7. Volúmenes EBS: Eliminar Huérfanos + Cambiar Tipos ($1.800 → $800)

22 volúmenes EBS sin adjuntar, haciendo nada. PersistentVolumes de pods eliminados que nadie limpió. Clásico.

Cambios realizados:

  • Eliminamos 22 volúmenes EBS huérfanos (ahorro inmediato de $400/mes)
  • Cambiamos volúmenes GP2 a GP3 (20% más barato, mejor performance)
  • Redujimos la frecuencia de snapshots de horaria a diaria para los volúmenes no críticos
# Encontrar volúmenes huérfanos
aws ec2 describe-volumes \
  --filters Name=status,Values=available \
  --query 'Volumes[*].{ID:VolumeId,Size:Size,Created:CreateTime}' \
  --output table

Ahorro: $1.000/mes (56%)

El Resultado Final

Servicio Antes Después Ahorro %
EC2/EKS $16.400 $6.800 $9.600 58%
RDS $7.200 $3.600 $3.600 50%
ElastiCache $3.100 $1.400 $1.700 55%
NAT Gateway $2.600 $800 $1.800 69%
S3/CloudFront $2.800 $1.600 $1.200 43%
Data Transfer $2.400 $1.200 $1.200 50%
EBS $1.800 $800 $1.000 56%
Otros $1.700 $2.000 -$300 -18%
Total $38.000 $18.200 $19.800 52%

"Otros" subió levemente porque agregamos herramientas de monitoring (Kubecost, exporters custom) que tienen un pequeño costo de compute. Vale cada centavo.

Ahorro anual: $237.600

El proyecto completo — auditoría, implementación, testing, documentación — tomó 8 semanas y les costó una fracción del ahorro de un solo mes.

El Cambio Más Importante

Las optimizaciones técnicas fueron importantes, pero el cambio cultural importó más. Instalamos nuestro dashboard de FinOps el primer día del proyecto, para que el equipo pudiera ver los costos en tiempo real desde el arranque.

Para la semana 3, los ingenieros ya venían a nosotros con ideas de optimización que nosotros no habíamos visto. Un developer notó que su servicio hacía 10 veces más llamadas a la API de S3 de las necesarias por falta de una capa de cache. Otro encontró un cron job que levantaba una instancia grande 2 minutos cada hora.

Cuando hacés los costos visibles, los ingenieros optimizan naturalmente. Solo necesitan los datos.

Qué Haríamos Diferente

Arrancar con right-sizing antes de Spot. Hicimos Karpenter primero, pero si hubiéramos right-siziado los pods antes, Karpenter hubiera sido aún más eficiente desde el día uno. Los nodos se empaquetan mejor cuando los pods piden lo que realmente necesitan.

Instalar el dashboard de costos antes, no después. Armamos el dashboard de FinOps al comienzo del proyecto en este caso, pero en proyectos anteriores lo dejamos para el final. Es un error: sin visibilidad de costos en tiempo real, los ingenieros no tienen el feedback loop para optimizar durante la implementación. El dashboard debería ser lo primero que se instala, no lo último.

Auditar los snapshots de RDS desde el día uno. Nos tomó hasta la semana 4 notar que tenían snapshots manuales de RDS acumulándose desde 2022 — nadie los borraba nunca. No era una línea enorme, pero era dinero tirado. Los snapshots automatizados tienen políticas de retención; los manuales no expiran solos. Ahora lo chequeamos en la primera semana de toda auditoría.


¿Tu factura AWS crece más rápido que tu base de usuarios? Es normal — y tiene solución. Pedí una evaluación gratuita de infraestructura y te mostramos exactamente dónde está el desperdicio.

Preguntas frecuentes

¿Cuánto tiempo lleva típicamente un proyecto de optimización de costos AWS de este tamaño?

Para un cluster/factura de este tamaño (~$38K/mes, región única, ~40 microservicios), contá con 2 semanas de auditoría seguidas de 4-8 semanas de implementación. La mayoría de los wins son seguros para shipear en las primeras 2-3 semanas: right-sizing, Reserved Instances, VPC endpoints, lifecycle policies. La migración a Karpenter y la adopción de Spot toman otras 2-4 semanas porque necesitan testing adecuado y rollout gradual.

¿Qué es lo primero que mirás cuando la factura AWS pega un salto?

Tres sospechosos en orden: (1) EC2/EKS compute (usualmente 40-60% de la factura — fijate en instancias oversized, HPAs faltantes, sin Spot), (2) Data transfer y NAT Gateway (frecuentemente 10-20% de la factura, casi siempre arreglable con VPC endpoints y topology-aware routing), (3) RDS o ElastiCache con Multi-AZ On-Demand (solo Reserved Instances puede bajar esto 40-50%). Usá AWS Cost Explorer agrupado por servicio para triangular rápido.

¿De verdad se pueden bajar los costos de EKS 50-70% sin romper producción?

Sí, de forma rutinaria. La receta estándar: (a) migrá de Cluster Autoscaler a Karpenter para mejor bin packing, (b) diversificá a 10+ familias de instancias para que la capacidad Spot siempre esté disponible, (c) clasificá los workloads en tiers Spot-ready / Spot-fallback / On-Demand-only, (d) right-size los pods en base a 2 semanas de datos de recomendaciones VPA, (e) hacé scale to zero en dev/staging fuera del horario laboral con KEDA cron triggers. Hecho con cuidado, cero incidentes en producción — lo shipiamos en clusters con 200+ microservicios.

¿Por qué es tan caro NAT Gateway, y los VPC endpoints son realmente la solución?

NAT Gateway cobra $0.045/GB por data processing además de las tarifas por hora. Si tus pods pulean imágenes Docker, escriben logs a S3, llaman a STS, o llegan a CloudWatch APIs por NAT, estás pagando el fee de procesamiento por cada byte. ECR VPC endpoint, S3 gateway endpoint, y STS y CloudWatch interface endpoints solucionan esto — el tráfico se queda dentro del VPC y bypasea NAT por completo. En este caso, esto solo ahorró $1.800/mes (69% de reducción en costos de data transfer).

Reserved Instances o Savings Plans — ¿cuál usar?

Compute Savings Plans para tu baseline On-Demand (el más flexible — aplica a EC2, Fargate y Lambda). Reserved Instances para RDS, ElastiCache y Redshift donde los Savings Plans no aplican. Arrancá con 1 año No Upfront para el baseline predecible — eso es 30-40% de descuento con casi ningún riesgo de lock-in. Guardá el 3 años All Upfront para workloads absolutamente estables (primarios de base de datos, control plane).

¿Cómo manejan las interrupciones de Spot en producción sin impacto al usuario?

Cuatro reglas: (1) diversificá los tipos de instancia (mínimo 10+ familias) para que AWS siempre tenga capacidad Spot para darte, (2) distribuí en 3 AZs para que un evento de interrupción en una zona no mate todo, (3) configurá terminationGracePeriodSeconds: 120 en todos los workloads Spot para que drenen limpio al recibir el aviso de 2 minutos, (4) mantené los servicios stateful (brokers Kafka, primarios Redis) en On-Demand únicamente. Con este setup, las tasas de interrupción reales están bajo el 5% y los incidentes visibles para el usuario son cero.