"¿Lambda o Fargate?" es una de esas preguntas que la gente hace esperando un ganador, y no lo hay. Las dos ejecutan código sin que gestiones servidores, pero resuelven problemas con forma distinta. Después de montar bastantes servicios sobre ECS Fargate y bastantes funciones en Lambda, dejé de elegir por moda y empecé a mirar tres cosas de la carga: cómo llega el tráfico, cuánto dura cada unidad de trabajo y cuánto estado necesita mantener el proceso. Casi siempre la decisión se cae sola por su propio peso.
No es serverless contra contenedores
El primer malentendido es tratarlo como serverless contra contenedores. Fargate también es serverless: defines una task, AWS la ejecuta y no tocas una sola instancia EC2. La diferencia real está en el modelo de ejecución. Lambda es efímero e invocado por evento: llega una petición, se levanta un entorno, corre tu handler, responde y se apaga. Fargate es un proceso de larga vida: tu contenedor arranca una vez, se queda escuchando y atiende peticiones hasta que lo escalas o lo matas.
Esa diferencia lo tiñe todo. En Lambda no controlas el ciclo de vida: no hay un "arranque" tuyo donde abrir un pool de conexiones y reutilizarlo tranquilo, porque el entorno puede reciclarse en cualquier momento. En Fargate sí tienes un proceso estable con su arranque, su memoria caliente y su pool de conexiones vivo entre peticiones. Elegir es, en el fondo, elegir cuánto control quieres sobre ese ciclo de vida.
La forma del tráfico manda
Lo primero que miro es cómo llega el trabajo. Si el tráfico es intermitente, a ráfagas o impredecible —un webhook que salta a veces, un cron que procesa cuando toca, un endpoint con picos raros— Lambda encaja de forma natural. Escala de cero a muchas invocaciones en paralelo sin que configures nada, y cuando no hay tráfico no pagas nada. Ese "de cero a mil sin avisar" es exactamente donde Fargate sufre: mantener capacidad para el pico significa pagar contenedores encendidos esperando, y escalar reactivo tarda más que un arranque de Lambda.
Si el tráfico es sostenido y constante, el cálculo se invierte. Un servicio con carga estable durante todo el día, en Lambda, es un goteo continuo de invocaciones que se factura por cada milisegundo y termina saliendo caro frente a un puñado de contenedores Fargate a pleno rendimiento. Ahí un proceso de larga vida aprovecha mejor cada CPU: no repite el coste de arrancar en cada petición y mantiene calientes recursos que Lambda tendría que rehacer una y otra vez.
💡 Lambda cobra por invocación y duración: brilla cuando a veces el tráfico es cero. Fargate cobra por contenedor encendido: brilla cuando el tráfico casi nunca es cero. Antes de decidir, dibuja la curva de tráfico de un día.
Duración y límites: el reloj de los 15 minutos
El segundo eje es cuánto dura cada unidad de trabajo. Lambda tiene un tope duro de quince minutos por invocación. Para una API o un procesamiento de eventos sobra, pero cualquier trabajo que pueda pasarse —una migración larga, un procesado de vídeo, un job que recorre un dataset grande— no cabe, y trocearlo para que quepa es complejidad que no siempre compensa. Fargate no tiene ese reloj: una task puede correr minutos u horas, lo que la hace el sitio natural para trabajo por lotes largo o procesos que simplemente tienen que estar vivos.
También está el arranque en frío. Cuando Lambda levanta un entorno nuevo, la primera invocación paga el coste de inicializar el runtime. Para cargas tolerantes a latencia da igual; para un endpoint sensible a la cola de latencias, ese frío ocasional se nota. Se mitiga —concurrencia aprovisionada, runtimes ligeros— pero es un factor real. Fargate no tiene arranque en frío por petición: el arranque lo pagas una vez cuando escalas una task nueva, no en cada invocación.
Estado, conexiones y dependencias
El tercer eje es cuánto estado y qué dependencias necesita el proceso. El caso clásico es la base de datos. Un pool de conexiones vive de reutilizar conexiones entre peticiones, y eso encaja con un proceso de larga vida como Fargate. En Lambda, cada entorno concurrente abre las suyas, y bajo un pico puedes agotar las conexiones de PostgreSQL sin darte cuenta; la solución pasa por un proxy de conexiones intermedio, otra pieza más en el diagrama. No es imposible hacer Lambda contra una base relacional, pero es fricción que Fargate no tiene.
// En Fargate esto se inicializa UNA vez al arrancar el
// contenedor y el pool se reutiliza en cada petición.
import { Pool } from 'pg';
const pool = new Pool({ max: 10 }); // vive entre peticiones
export async function handler(req: Request) {
const client = await pool.connect(); // reutiliza, no reabre
try {
return await consulta(client, req);
} finally {
client.release();
}
}
Ese mismo patrón en Lambda es traicionero: el pool sobrevive mientras el entorno se reutiliza, pero se multiplica por cada entorno concurrente, y ahí es donde revienta la base bajo carga.
Mi regla, y por qué mezclo los dos
Al final mi criterio es simple. Empiezo en Lambda cuando el trabajo es corto, disparado por eventos, con tráfico irregular y poco estado: webhooks, tareas asíncronas, glue entre servicios de AWS. Me muevo a Fargate cuando el trabajo es sostenido, de larga duración, o necesita conexiones vivas y control del ciclo de vida: APIs con carga constante, workers de cola que procesan sin parar, batch largo.
Y no elijo uno para todo el sistema. En el mismo producto convive un servicio HTTP en Fargate con conexión estable a Postgres y, al lado, funciones Lambda para los webhooks entrantes y los jobs asíncronos. Elegí mezclar en vez de forzar un único modelo porque cada carga tiene su forma, y pelearse con la herramienta para que haga algo que no es lo suyo se paga siempre: en Lambda con proxies y troceo, en Fargate con capacidad ociosa esperando un pico que no llega. Como casi todo en AWS, no hay una mejor herramienta, hay la mejor herramienta para la forma de tu problema.