Saltar al contenido
Volver al blog
Desarrollo 12 min·

WebSockets Serverless: Arquitectura Escala Real con AWS y Node.js

Domina la comunicación en tiempo real sin gestionar servidores. Guía técnica sobre AWS API Gateway, Lambda y DynamoDB para sistemas de alta concurrencia.

Por Equipo Julsmind SAS
Diagrama técnico de arquitectura serverless usando AWS Lambda y API Gateway para WebSockets.

Mantener una conexión TCP abierta durante horas para esperar un simple mensaje de 'notificación' es un desperdicio de recursos que tu presupuesto no debería ignorar. Mientras que en el pasado nos resignábamos a gestionar flotas de servidores EC2 con instancias de Socket.io sudando bajo el peso de 50,000 conexiones concurrentes, la era serverless ha cambiado las reglas del juego. No se trata de si puedes hacerlo, sino de si puedes hacerlo sin que tu infraestructura se convierta en una pesadilla de mantenimiento.

El Problema de los WebSockets en el Mundo Stateless

Por definición, AWS Lambda es stateless. Se enciende, procesa y muere. Los WebSockets, por el contrario, son la definición misma de stateful: requieren una conexión persistente entre el cliente y el servidor. ¿Cómo reconciliar estos dos mundos? La respuesta no está en forzar a Lambda a mantenerse viva, sino en delegar la gestión del estado de la conexión a una capa superior: AWS API Gateway.

La Anatomía de la Conexión

En una arquitectura de WebSockets serverless, el flujo se divide en tres responsabilidades claras:

  • Gestión de Conexión: API Gateway mantiene el socket abierto con el cliente.
  • Persistencia de Estado: DynamoDB almacena el connectionId asociado al usuario.
  • Procesamiento Lógico: Funciones Lambda que se activan solo cuando llega un mensaje o se necesita enviar uno.

Paso 1: Configurando las Rutas en API Gateway

A diferencia de una API REST, los WebSockets en AWS funcionan mediante rutas predefinidas. Necesitas configurar al menos tres:

  1. $connect: Se dispara cuando el cliente inicia el apretón de manos (handshake).
  2. $disconnect: Se dispara cuando el cliente se va o se pierde la conexión.
  3. $default: El cajón de sastre para cualquier mensaje que no coincida con rutas específicas.
"El error más común es no implementar un mecanismo de limpieza para conexiones huérfanas en DynamoDB. Si el cliente desaparece sin un $disconnect limpio, tu tabla se llenará de IDs basura."

Paso 2: Persistencia del ConnectionId en DynamoDB

// Ejemplo de lógica en Lambda $connect
const AWS = require('aws-sdk');
const ddb = new AWS.DynamoDB.DocumentClient();

exports.handler = async (event) => {
  const connectionId = event.requestContext.connectionId;
  const userId = event.queryStringParameters.userId;

  await ddb.put({
    TableName: 'ConnectionsTable',
    Item: { connectionId, userId, ttl: Math.floor(Date.now() / 1000) + 3600 }
  }).promise();

  return { statusCode: 200, body: 'Connected.' };
};

Usar un TTL (Time To Live) en DynamoDB es una práctica recomendada en Medellín y cualquier hub tech para evitar costos innecesarios y mantener la base de datos esbelta. Si el usuario no refresca su sesión, la entrada desaparece automáticamente.

Paso 3: El Reto de la Comunicación Outbound

Enviar un mensaje del servidor al cliente es el punto donde muchos desarrolladores se confunden. Lambda no puede simplemente 'responder' al evento de invocación original para enviar datos asíncronos más tarde. Debes usar el ApiGatewayManagementApi.

El proceso de 'Push'

Cuando ocurre un evento en tu sistema (ej. una nueva venta, un mensaje de chat), tu lógica debe:

  • Consultar en DynamoDB el connectionId del destinatario.
  • Instanciar el SDK de AWS apuntando al callback URL de tu API Gateway.
  • Llamar a postToConnection con el ID y el payload.
const apigw = new AWS.ApiGatewayManagementApi({
  endpoint: event.requestContext.domainName + '/' + event.requestContext.stage
});

try {
  await apigw.postToConnection({
    ConnectionId: targetId,
    Data: JSON.stringify({ message: 'Hola desde el servidor' })
  }).promise();
} catch (e) {
  if (e.statusCode === 410) {
    // La conexión ya no existe, proceder a borrar de la DB
  }
}

Comparativa: Serverless vs. Servidores Tradicionales

CaracterísticaServerless (AWS)Tradicional (EC2/Node.js)
EscalabilidadAutomática (miles de conexiones/seg)Manual / Auto-scaling groups complejos
Costo en Reposo$0 (pago por uso)Costo fijo mensual por instancia
Gestión de EstadoExterna (DynamoDB/Redis)En memoria (volátil)
MantenimientoBajo (Managed Service)Alto (Parches de SO, seguridad)

Consideraciones de Latencia y Límites

No todo es color de rosa. La arquitectura serverless introduce una latencia de 'cold start' si tus funciones Lambda no se invocan con frecuencia. Además, API Gateway tiene un límite predeterminado de 10,000 conexiones concurrentes (aumentable vía soporte). Si estás construyendo el próximo WhatsApp, quizás necesites una capa de optimización adicional con ElastiCache para reducir los tiempos de lectura de los IDs de conexión.

Cómo lo abordamos en Julsmind SAS

En Julsmind SAS, diseñamos sistemas que no solo funcionan, sino que son económicamente viables a largo plazo. Hemos implementado arquitecturas de WebSockets serverless para fintechs en Colombia y startups en EE. UU., logrando reducciones de costos de infraestructura de hasta un 60% al eliminar servidores inactivos. Nuestro enfoque se centra en la robustez: desde el manejo de reconexiones exponenciales en el cliente hasta la orquestación de eventos complejos en el backend utilizando EventBridge.

¿Estás lidiando con problemas de escalabilidad en tus notificaciones en tiempo real o quieres migrar de una arquitectura legacy a una más eficiente? Hablemos sobre cómo optimizar tu stack tecnológico en nuestra página de contacto y llevemos tu producto al siguiente nivel.

¿Tienes un proyecto en mente?

Cotiza gratis con nuestro equipo — sin compromiso.

Cotiza tu proyecto