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.

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
connectionIdasociado 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:
$connect: Se dispara cuando el cliente inicia el apretón de manos (handshake).$disconnect: Se dispara cuando el cliente se va o se pierde la conexión.$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
connectionIddel destinatario. - Instanciar el SDK de AWS apuntando al callback URL de tu API Gateway.
- Llamar a
postToConnectioncon 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ística | Serverless (AWS) | Tradicional (EC2/Node.js) |
|---|---|---|
| Escalabilidad | Automá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 Estado | Externa (DynamoDB/Redis) | En memoria (volátil) |
| Mantenimiento | Bajo (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.