49  N5. Serverless y edge: tu código sin tu servidor

Lambda, Functions, Workers y cuándo la nube corre por ti

NotaEn una frase

Serverless es la cimentación alternativa del Cap. 1 llevada al extremo: no hay proceso escuchando — tu función despierta con cada request y muere al responder. Pagas por ejecución, no por servidor encendido. Cuándo conviene y cuándo te sale carísimo es la lección del capítulo.

49.1 El problema

Tu API de tareas tiene tráfico irregular: 200 requests el lunes, 3 el martes, 5.000 cuando un cliente la descubre. En el modelo de N1–N2 pagas un servidor encendido 24/7 que duerme la mayor parte del día — como dejar un taxi corriendo en la puerta por si algún día necesitas ir al aeropuerto. La pregunta de este capítulo: ¿y si el taxi solo existiera mientras viajas?

49.2 Cómo lo resuelve un equipo

El modelo FaaS (Function as a Service): subes una función — no una app, no un servidor — y el proveedor la ejecuta cuando algo la invoca (un HTTP request, un archivo subido, un cron). Cobran por milisegundo de ejecución. Sin requests, costo ≈ $0.

El caso que enamora: webhooks, eventos, trabajos ocasionales. El caso que arruina: una API con tráfico constante y alto, donde el precio por ejecución supera al servidor fijo — y donde los cold starts (la función “fría” tarda en despertar) pegan en cada usuario nuevo.

49.3 Conceptos nuevos

  • Ops FaaS: Lambda, Azure Functions, Cloud Functions — función por evento
  • Ops Edge: Cloudflare Workers, Vercel Edge, Deno Deploy — tu código corre en el datacenter más cercano al usuario
  • U Cold start: la función dormida tarda ~100ms–2s en despertar — imperceptible o inaceptable según tu caso
  • U Sin estado: cada ejecución nace nueva — la memoria y el disco NO sobreviven entre requests
  • Ops Vendor lock-in: la función usa el runtime/eventos del proveedor — moverla es un proyecto

49.4 La explicación visual

flowchart LR
  R["Request"] -->|"despierta"| F["tu función<br/><i>solo existe ejecutándose</i>"] --> Res["Response"]
  F -.->|"se duerme"| Z["$0 mientras duerme"]

Comparado con lo que ya conoces:

Proceso (N1/N2) Serverless
Qué existe Un proceso escuchando 24/7 Una función que despierta por evento
Pagas por Servidor encendido (aunque duerma) Milisegundos de ejecución
Estado Memoria/disco del proceso NADA persiste entre requests
Escala Tú la configuras Automática: 1 o 10.000 ejecuciones
Latencia Siempre caliente Cold starts ocasionales

49.5 Implementación

El mismo endpoint — GET /tasks — como función serverless. Fíjate qué desaparece: el listen(), el puerto, el servidor. La función recibe un

Un puerto es una puerta numerada dentro de una computadora: la IP llega al edificio, el puerto al departamento. :3000 = “la app que escucha en el departamento 3000”. Postgres suele usar 5432, HTTP el 80, HTTPS el 443.

evento (el request envuelto por el proveedor) y devuelve un objeto:

// handler.ts — AWS Lambda + API Gateway
export const handler = async (event: any) => {
  const tasks = await listTasks();   // DB via DATABASE_URL from env
  return {
    statusCode: 200,
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(tasks),     // Lambda returns an OBJECT, not res.json()
  };
};
// No app.listen() — the platform invokes handler() per request
# handler.py — AWS Lambda
def handler(event, context):
    tasks = list_tasks()              # DB via DATABASE_URL from env
    return {
        "statusCode": 200,
        "headers": {"Content-Type": "application/json"},
        "body": json.dumps(tasks),
    }
# No uvicorn, no port — AWS calls handler(event, context)
// main.go — AWS Lambda
func handler(ctx context.Context, req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
    tasks, err := listTasks(ctx)     // DB via DATABASE_URL from env
    if err != nil {
        return events.APIGatewayProxyResponse{StatusCode: 500}, nil
    }
    body, _ := json.Marshal(tasks)
    return events.APIGatewayProxyResponse{StatusCode: 200, Body: string(body)}, nil
}

func main() { lambda.Start(handler) } // the runtime calls handler() per request

¿Y si ya tienes una app completa y no quieres reescribirla? Los adaptadores envuelven tu app existente en un handler: aws-serverless-express (Express), mangum (FastAPI), aws-lambda-go-api-proxy (Go). Tu código de los caps. 2–7 viaja casi intacto — la ruta sigue siendo la ruta, pero ahora la invoca la plataforma en vez de un socket.

El edge es serverless + geografía: la función no corre en us-east-1 sino en el punto de presencia más cercano al usuario (Cloudflare Workers corre en 300+ ciudades). Ideal para: auth checks, redirects, personalización ligera — código que debe responder en <50ms desde cualquier país.

La autenticación responde “¿quién eres?” — login, contraseña, token. Se confunde con autorización (“¿qué puedes hacer?”), que es la pregunta siguiente. Primero te identificas, luego te dejan o no pasar.

Convierte mi endpoint GET /tasks de [Express/FastAPI/Go] a una función
serverless para [AWS Lambda / Cloudflare Workers / Vercel]. Requisitos:
usa el adaptador apropiado para no reescribir toda la app (o el handler
nativo si prefieres), la conexión a la base de datos debe funcionar con
el modelo sin estado (conexión por ejecución o pooler — explícame cuál
y por qué), y dame el archivo de configuración de despliegue. Al final
explícame qué partes de mi código asumen un proceso siempre vivo y
dejarían de funcionar.

49.6 ¿Por qué no es siempre la respuesta?

Lo único que necesitas llevarte: serverless gana cuando el tráfico es irregular (pagas solo lo que usas y escala solo) y pierde cuando el tráfico es constante y alto (una función cobra más por millón de requests que un servidor fijo) o cuando el trabajo es largo (límites de ~15 min) o con estado (websockets, sesiones en memoria). El cálculo es honesto, no ideológico.

La memoria RAM es el escritorio de trabajo del programa: rápida, pero se borra al apagar. Los datos que deben sobrevivir van a disco o a la base de datos. Por eso let tasks = [] pierde todo al reiniciar.

Una sesión es la conversación continuada entre tú y el servidor: HTTP no recuerda nada entre requests, así que la sesión (vía cookie o token) es el “pulso de mano” que te identifica en cada llamada.

Alternativa Modelo Qué te cuesta Cuándo elegirla
VPS (N1) Proceso siempre vivo Toda la ops Aprendizaje, control total, precio fijo
PaaS (N2) Subes código Precio/unidad mayor El default sano para la mayoría de APIs
Contenedores (cap. 30+) Subes imagen Sabes Docker + orquestación Portabilidad entre proveedores
FaaS Subes función Cold starts, límites, lock-in, sin estado Eventos, webhooks, tráfico irregular
Edge Función en 300 ciudades Runtime limitado (no todo Node corre) Latencia global, middleware ligero

El espectro completo del libro en una tabla: misma app, cinco cimentaciones. La elección correcta depende del tráfico, el equipo y el tiempo de vida del proceso — nunca del hype.

49.7 Errores comunes

Error Por qué pasa Fix
“Serverless = gratis” El free tier engaña; a 10M requests/mes Lambda puede costar más que un VPS Haz la cuenta a tu volumen real
Estado en memoria entre requests La función nueva no recuerda nada Estado externo: DB, Redis, KV — como debe ser siempre
El monolito en UNA lambda Una función gigante hace todo → cold start enorme, despliegue lento Funciones por dominio, o app completa via adaptador sabiendo el costo
Conexión DB por request Postgres no tolera 5.000 funciones abriendo conexiones Pooler (RDS Proxy, PgBouncer, Neon serverless)
Ignorar cold starts en auth El primer request del día tarda 2s Keep-warm, provisioned concurrency, o acepta el trade

49.8 Buenas prácticas

  • Diseña sin estado de todas formas — es buena práctica para todo backend (el proceso siempre vivo también puede morir), y te deja la puerta serverless abierta.

Un proceso es un programa en ejecución: el sistema operativo le da memoria propia y un lugar en la fila del CPU. Tu API es un proceso; Postgres es otro; el navegador es varios. Que “se caiga el proceso” = el programa murió.

  • Serverless para eventos primero: webhooks, procesamiento de archivos, crons — ahí el modelo es perfecto antes de mover tu API entera.
  • Mide el cold start real de tu función con sus dependencias — el de los benchmarks (una función vacía) no es el tuyo.
  • Vigila la factura por volumen: configura alertas; el modelo escala solo… incluido el cobro.

49.9 Ejercicio

  1. Clasifica cada caso como “serverless conviene” o “proceso fijo conviene”: (a) webhook de pagos que llega 50 veces/día, (b) API con 2M requests/día estables, (c) resize de imágenes al subirse, (d) app de chat con websockets.
  2. ¿Por qué tu función Lambda no puede guardar la sesión del usuario en una variable global?
  3. ¿Qué dos diferencias hay entre res.json(tasks) (Express) y el return {statusCode, body} de Lambda?

Una variable es una caja con nombre donde guardas un valor: let total = 42 guarda el 42 bajo el nombre total. Puedes leerla y cambiarla después (total = 50). const = caja que no se puede reemplazar.

return hace dos cosas a la vez: devuelve el resultado Y termina la función — lo que esté debajo nunca corre. return task = “aquí está el plato, salgo de la cocina”.

  1. Conviene: (a) eventos irregulares — pagas ~nada entre webhooks;
    1. procesamiento por evento — el caso clásico. Proceso fijo: (b) tráfico alto y estable — el costo por ejecución supera al servidor;
    2. websockets necesitan conexiones persistentes — las funciones mueren al responder.
  2. Porque cada ejecución puede correr en una instancia distinta (o en la misma reciclada después) — la variable global no es confiable: existe solo mientras esa ejecución viva y no la ven las demás. La sesión va en un store externo (Redis, DB, o JWT en el token).
    1. Lambda devuelve un objeto que la plataforma traduce a HTTP — no escribes en un socket; el body debe ser string serializado tú mismo. (b) No hay listen() ni puerto: la plataforma decide cuándo invocarte — tu código es un handler, no un servidor.

49.10 Mini reto

Un equipo migra su API completa a Lambda “para ahorrar”. Su tráfico: 3M requests/día constantes, cada request toca Postgres. Dos semanas después la factura subió y los usuarios se quejan de lentitud intermitente. ¿Qué salió mal? (Pista: tres cosas.)

  1. El cálculo era al revés: a 90M requests/mes constantes, el costo por ejecución + API Gateway supera a un contenedor/VM fija — serverless ahorra con tráfico irregular, no con carga sostenida.
  2. Cold starts: los usuarios sentían los despertares — funciones con muchas dependencias tardan en arrancar; con tráfico constante igual aparecen cuando AWS recicla instancias o escala.
  3. Conexiones a Postgres: miles de funciones abriendo conexiones nuevas agotaron la base (Postgres tolera ~cientos, no miles) — necesitaban un pooler (RDS Proxy) que olvidaron o cuya latencia extra empeoró todo.

Moraleja: serverless es una herramienta con un nicho, no un upgrade.

49.11 Vocabulario técnico del capítulo

Una función es una receta reutilizable: recibe ingredientes (parámetros), hace pasos y devuelve un plato (return). La escribes una vez y la llamas mil veces: add(2, 3) → 5.

Un objeto es una colección de datos con nombre: {name: "Ana", age: 30} — cada dato es una propiedad (clave → valor). Python los llama dict, Go los arma con struct, TS con objetos/interface.

Una clase es el molde de un objeto: define qué datos y qué métodos tiene. class Task es el molde; new Task() es una instancia concreta. Go no tiene clases — usa struct + métodos sueltos.

Un string es texto entre comillas: "hola". El nombre viene de “cadena de caracteres” — una secuencia de letras. Todo lo que llega de un formulario o una URL llega como string, aunque parezca número.

null (TS), None (Py), nil (Go) = “aquí no hay valor”. Es la respuesta a “¿qué devuelvo cuando no hay nada?” — y la fuente del bug más famoso de la historia (su inventor lo llamó “el error del billón de dólares”). Por eso el código revisa if x is not None.

Asíncrono = empezar algo sin esperar sentado a que termine: pides la pizza (async) y sigues trabajando; cuando llega, te avisan. Lo opuesto a síncrono (esperar parado). Vital cuando la espera es larga: red, disco, bases de datos.

Un middleware es un filtro en la cadena del request: pasa por él antes de llegar a tu handler. Auth es middleware — “verifica el token” vive una vez y protege todas las rutas, no se copia en cada una.

Un deploy es poner tu código a correr en el servidor: copiar la imagen, iniciar el proceso, verificar que responde. El objetivo es que sea aburrido — automático, repetible, con rollback.

El runtime es el motor que ejecuta tu código: Node.js es el runtime de JavaScript fuera del navegador; CPython es el de Python; Go compila a binario y el runtime va empaquetado dentro. Es “quien corre” lo que escribiste.

Una dependencia es código de terceros que tu proyecto usa: npm install, pip install, go get las traen. Cada una es deuda — ahora funciona, pero hay que mantenerla, actualizarla y confiar en ella.

Una base de datos es el programa que guarda datos de forma permanente y los responde rápido. Relacional (Postgres): tablas con relaciones. No relacional: documentos, clave-valor, grafos — cada una para una forma de dato distinta.

Un token es una credencial portable: una cadena que dice quién eres y hasta cuándo. El servidor la emite tras el login; el cliente la presenta en cada request en vez de la contraseña.

Un JWT (JSON Web Token) es un token firmado: el servidor lo genera con su secreto y puede verificarlo sin consultar la BD. Ojo: está firmado, no cifrado — cualquiera puede leer su contenido, pero no modificarlo.

49.12 Lo que deberías saber hacer ahora