AWS 28 may 2026 · 10 min de lectura

Serverless sin dolor: Amplify, Lambda y DynamoDB para apps reales

Yohangel Ramos

Yohangel Ramos

Tech Lead · Senior Fullstack Developer

Cuando construimos el panel de administración de Toyota Colombia en Destiny, la pregunta no era "¿serverless o no?", sino "¿cómo montamos un backend que nadie tenga que cuidar de madrugada?". El panel alimentaba una PWA offline-first hecha en Next.js: catálogos, contenido, datos que los concesionarios consultaban desde el terreno. El tráfico era irregular, el equipo de operaciones no existía como tal, y pagar por servidores encendidos las 24 horas para picos ocasionales no tenía sentido. Serverless encajó por eso: por la forma del problema, no por la moda.

Por qué serverless tenía sentido aquí

No uso serverless por defecto. Lo uso cuando el patrón de carga es irregular y el equipo es pequeño. Este proyecto cumplía las dos cosas.

  • Tráfico a ráfagas. El panel se usaba en ventanas concretas: cargas de contenido, actualizaciones de catálogo, consultas puntuales. Entre medias, casi nada. Pagar EC2 encendido para eso es quemar dinero.
  • Cero equipo de plataforma. No había SREs. Con Lambda y DynamoDB no parcheo sistemas operativos, no gestiono escalado, no me despierto porque un disco se llenó.
  • Aislamiento por evento. Cada request es una invocación. Un pico no tumba el servicio entero; escala hacia arriba y luego a cero.

El precio de esto son otras cosas: modelar bien DynamoDB desde el día uno, vigilar los cold starts y aceptar que estás casado con AWS. Vale la pena si eres honesto con esos costos.

Modelar DynamoDB: single-table y patrones de acceso

El error más común con DynamoDB es tratarlo como Postgres sin joins. No lo es. En DynamoDB modelas los patrones de acceso primero, y la tabla después.

Para el panel usé un diseño single-table: una sola tabla, con PK y SK genéricos, y varios tipos de entidad conviviendo. Los patrones que necesitábamos eran claros: traer un vehículo por id, listar vehículos por categoría, traer el contenido asociado a un modelo.

{
  "PK": "VEHICLE#corolla-2023",
  "SK": "METADATA",
  "type": "vehicle",
  "name": "Corolla",
  "year": 2023,
  "category": "sedan",
  "GSI1PK": "CATEGORY#sedan",
  "GSI1SK": "VEHICLE#corolla-2023"
}

La clave: el acceso por id va contra PK/SK, y el listado por categoría va contra un índice secundario global (GSI1PK/GSI1SK). Nada de escaneos completos de tabla. Cada consulta es un Query sobre una clave de partición conocida, que es lo único que DynamoDB hace barato y predecible.

Reglas que me ahorraron dolor:

  1. Escribe la lista de patrones de acceso antes de tocar la tabla. Si aparece uno nuevo después, casi siempre es un GSI más, no un rediseño.
  2. Prefijos en las claves (VEHICLE#, CATEGORY#) para que entidades distintas convivan sin chocar.
  3. Nunca un Scan en el camino caliente. Si necesitas filtrar por algo, ese algo va en una clave.

Cold starts: reales, pero domesticables

Los cold starts existen y son molestos, pero en 2026 el drama está muy exagerado. En este panel, el backend eran funciones Node en Lambda detrás de API Gateway, y la latencia extra en el primer arranque nunca fue un problema de negocio: era una herramienta interna, no un checkout con SLA de milisegundos.

Aun así, lo que sí hago:

  • Bundle pequeño. Menos dependencias que cargar, arranque más rápido. Empaquetar con esbuild y sacar lo que no uso importa más que cualquier truco.
  • Cliente de DynamoDB fuera del handler, para reutilizar la conexión entre invocaciones calientes.
  • Memoria como palanca de CPU. En Lambda la CPU escala con la memoria asignada; subir de 128 a 512 MB muchas veces sale más barato y más rápido, porque la función termina antes.
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, QueryCommand } from "@aws-sdk/lib-dynamodb";

// Fuera del handler: se reutiliza entre invocaciones calientes.
const ddb = DynamoDBDocumentClient.from(new DynamoDBClient({}));

export const handler = async (event: { category: string }) => {
  const res = await ddb.send(
    new QueryCommand({
      TableName: process.env.TABLE_NAME,
      IndexName: "GSI1",
      KeyConditionExpression: "GSI1PK = :pk",
      ExpressionAttributeValues: { ":pk": `CATEGORY#${event.category}` },
    }),
  );
  return { items: res.Items ?? [] };
};

💡 Con serverless no pagas por servidores; pagas por diseñar bien tus patrones de acceso desde el primer día. Esa deuda de diseño no se refactoriza barato.

Amplify para el frontend y auth

El panel en sí lo entregamos con Amplify: hosting del frontend, auth con Cognito por debajo, y el pegamento entre el cliente y las Lambda. Para un panel interno con login, Amplify te quita semanas de plomería. No tienes que montar tú el flujo de sesiones ni el hosting con CI.

Es cómodo, con un precio: Amplify es opinado. Cuando quieres salirte de su camino feliz, la abstracción empieza a pesar y depurar lo que genera por debajo cuesta. Para el alcance de este proyecto, el intercambio fue bueno.

Infra como código y los trade-offs de operación

Todo esto vive en código. Nada de crear recursos a mano en la consola: tablas, funciones, roles IAM, todo declarado. Hoy, con la experiencia de gestionar la infra de JXBS en OpenTofu (ECS Fargate, colas, Secrets Manager, CI/CD), tengo aún más claro que el peor error es tener recursos que nadie sabe de dónde salieron. Si no está en código, no existe.

Lo bueno de este modelo, sin adornos:

  • Sin servidores que parchear ni mantener.
  • Escala a cero: sin tráfico, sin factura de cómputo.
  • Menos superficie de operación para un equipo pequeño.

Lo incómodo, con la misma honestidad:

  • Lock-in real. DynamoDB y Lambda no se migran a otro cloud sin reescribir.
  • Depurar es distinto. No hay un servidor donde entrar; vives en logs y trazas.
  • El coste puede sorprender si haces Scan o modelas mal: DynamoDB es baratísimo bien usado y carísimo mal usado.

Qué haría distinto hoy (2026)

Volvería a elegir serverless para este caso; la forma del problema no cambió. Pero cambiaría cosas:

  • Menos Amplify, más control explícito. Hoy montaría el hosting y el auth de forma más directa y dejaría Amplify para prototipos, no para lo que va a vivir años.
  • Empezaría en OpenTofu, no en la herramienta propietaria. Es lo que uso ahora en producción y no volvería atrás.
  • Invertiría más temprano en observabilidad. Trazas distribuidas desde el día uno; en serverless, no verlas es volar a ciegas.

Serverless sin dolor no significa serverless sin decisiones. Significa tomar las decisiones difíciles al principio —modelado de datos, IaC, observabilidad— para no pagarlas de madrugada después.

Yohangel Ramos

Escrito por Yohangel Ramos

Senior Fullstack Developer y Tech Lead. Construyo con React, Next.js, Nest.js y AWS — y escribo sobre lo que aprendo en el camino.

Hablemos →

Sigue leyendo

IA

Embeddings más baratos: recortar dimensiones y cuantizar sin perder recall

IA

Qué va a pasar con el desarrollo de software: predicciones con fechas (julio 2026)