WebSocket streaming y eventos
TL; DR
Flujo de trabajo = canal confiable (WSS) + offsets resumibles + eventos idempotentes + límites estrictos y retroceso. Haga: JWT autenticación, autorización de topics, heartbeats, seq/offset + resume-token, at-least-once + dedoup. Para la escala - charding por usuario/tenant, sticky-routing, y la cola (Kafka/NATS/Redis Streams) como fuente de la verdad.
1) Casos de negocio de iGaming (que es realmente streamim)
Balance/límites: cambios instantáneos de balance, límites de RG, bloqueos.
Apuestas/rondas/resultados: confirmación, estado, cálculo de ganancias.
Torneos/liderazgos: posiciones, temporizadores, eventos de premios.
Pagos: estado de payout/refund, banderas KYC/AML - como notificaciones (y las críticas permanecen en NAT + webhooks).
Eventos de servicio: mensajes de chat, banners de inserción, estados de sesión, mantenimiento.
2) Protocolo y conexión
Sólo WSS (TLS 1. 2+/1. 3). Máximo de 1 conexión activa por dispositivo/sesión predeterminada.
Ping/Pong: el cliente es un 'ping' cada 20-30 s, el tiempo de respuesta es de 10 s. El servidor restablece la conexión a 3 tiempos seguidos.
Compresión: 'permessage-deflate', límite de tamaño de fotograma (por ejemplo, ≤ 64 KB).
Formato de carga útil: JSON para exteriores, Protobuf/MsgPack para interiores/móviles.
3) Autenticación y autorización
Handshake con JWT en query/header ('Sec-WebSocket-Protocol '/' Authorization'), token TTL corto (≤ 15 min), refresh por out-of-band (NAT).
Tenant-scoped claims: `sub`, `tenant`, `scopes`, `risk_flags`.
ACL en topics/canales: suscripción sólo a los 'topic' permitidos (por ejemplo: 'user: {id}', 'tournament: {id}', 'game: {table}').
Intersección de la conexión cuando el token caduca: «ventana suave» 60 s.
4) Modelo de suscripción
El cliente, después de conectar, envía comandos:json
{ "op":"subscribe", "topics":["user:123", "tournament:456"], "resume_from":"1748852201:987654" }
{ "op":"unsubscribe", "topics":["tournament:456"] }
'resume _ from' - offset (ver § 5) si el cliente restaura la conexión.
El servidor responde ack/nack, ACLs que no han salido - en 'nack' con 'reason'.
5) Garantías de envío y resumen
Objetivo: at-least-once por canal + idempotencia en el cliente.
Cada evento tiene un 'seq' monótono dentro de un «lote» (normalmente user/room) y un 'event _ id' global para el dedoop.
En re-connect, el cliente pasa 'resume _ from' = el último 'seq' (o 'offset' del broker) confirmado. El servidor carga los eventos perdidos desde el «origen de la verdad» (Kafka/NATS/Redis Streams).
Si el lag supera la retention (por ejemplo, 24 h): el servidor envía un 'snapshot' de estado y un nuevo 'seq'.
- Almacenar 'last _ seq '/' event _ id' en almacenamiento durable (IndexedDB/Keychain).
- Dedoup por 'event _ id', omitir eventos con 'seq ≤ last_seq', detectar agujeros (gap) → consultas de snapshot auto-' resync'.
6) Esquema de mensaje (envelope)
json
{
"ts": "2025-11-03T12:34:56. 789Z",
"topic": "user:123",
"seq": "1748852201:987654", // partition:offset
"event_id": "01HF..", // UUID/KSUID
"type": "balance. updated",
"data": { "currency":"EUR", "delta"--5. 00, "balance":125. 37 },
"trace_id": "4e3f.., "//for correlation
"signature": "base64 (hmac (...)) "//optional for partners
}
'tipo' es una taxonomía de dominio (ver diccionario de eventos).
PII/PCI - Eliminar/enmascarar a nivel de puerta de enlace.
7) Backpressure, cuotas y protección contra clientes «caros»
Server → Client: per-connection nat-queue con «ventana deslizante». Desbordado: restablece las suscripciones a topics o disconnect «ruidosos» con el código '1013 '/' policy _ violation'.
Client → Server: límites en 'subscribe/unsubscribe' (por ejemplo, ≤ 10/sec), límite de lista de topics (≤ 50), intervalo mínimo de repetición de suscripción.
Rate limits por IP/tenant/clave. Anomalías → bloqueo temporal.
Prioridad: eventos vitales (balance, límites RG) - cola prioritaria.
8) Protección y seguridad
Perfil WAF/bot en endpoint handshake, lista de orígenes permitidos.
mTLS entre la puerta de enlace edge y los nodos de streaming.
Protección DoS: cookies SYN en L4, límites en el número de WS abiertos/intervalo keep-alive.
Anti-replay: 'timestamp' en la firma de carga útil opcional (para socios) con una ventana válida de 5 min.
Aislamiento de inquilinos: charding físico/lógico, llaves/tokens per-tenant.
9) Arquitectura de transporte
Puerta de enlace (edge): terminate TLS, authN/Z, cuotas, enrutamiento por lote.
Nodos Stream: stateless workers con routing sticky por 'hash (user_id)% N'.
Corredor de eventos: Kafka/NATS/Redis Streams es la fuente de la verdad y el buffer de réplica.
State-service: almacena snapshots (balance, posiciones en el torneo).
Multirregión: activo-activo; GSLB para la región más cercana; home-region se fija en el inicio de sesión; con feilover es un resumen «frío» de otra región.
10) Orden, coherencia, idempotencia
El ordenamiento está garantizado dentro del lote (user/room), no globalmente.
Consistencia: un evento puede llegar antes de una respuesta NAT; UX debe ser capaz de vivir con un estado intermedio (optimistic UI + reconciliation).
Idempotencia: volver a procesar 'event _ id' no cambia el estado del cliente.
11) Errores, reconnect y «tormenta»
Códigos de cierre: '1000' (normal), '1008' (policy), '1011' (internal), '1013' (server overload).
Cliente exponencial backoff + jitter: 1s, 2s, 4s... max 30s.
Durante reconnectaciones masivas ("thundering herd'): el servidor da respuestas 'retry _ after' y 'grises' con una pista para usar SSE fallback para read-only.
12) Caché y snapshots
Cada suscripción puede comenzar con un snapshot de estado actual, luego con un flujo de eventos diff.
Versionar el esquema 'data _ version' y la compatibilidad (la extensión de campo no rompe clientes).
13) Observabilidad y SLO
Métricas:- Conexiones: activas, instaladas/segundos, distribuidas por arrendatarios/regiones.
- Entrega: p50/p95 retraso del corredor al cliente, drop-rate, resend-rate.
- Fiabilidad: proporción de currículos exitosos sin snapshot, detector de gap.
- Errores: 4xx/5xx en el handshake, códigos de cierre, hits de límite.
- Carga: comandos RPS 'subscribe', tamaño de cola, CPU/NET.
- Establecimiento de WS p95 ≤ 500 ms (dentro de la región).
- End-to-end latency eventos p95 ≤ 300 ms (user-partition).
- Resume success ≥ 99%, message loss = 0 (по at-least-once).
- Uptime stream-endpoint ≥ 99. 95%.
14) Administración de esquemas y versiones
Diccionario de eventos con propietarios, ejemplos y semántica.
Evolución «suave»: sólo la adición de campos opcionales; eliminación - después de '@ deprecated' período.
Pruebas contractuales contra clientes SDK, linternas en JSON Schema/Protobuf.
15) Incidencias de Playbucks (incrustar en tu playbook compartido)
Latency Growth: cambiar lotes a nodos redundantes, aumentar el tamaño del batch del bróker, habilitar la priorización de eventos vitales.
Tormenta de reconnectaciones: activar 'retry _ after', elevar temporalmente los límites de handshake, habilitar el follback SSE.
Fuga de tokens: rotación de JWKS, revocación de tokens afectados, reconnect forzado con re-auth.
Pérdida de lote de broker: puesta en modo snapshot, replay después de la recuperación.
16) Mini especificación API (simplificado)
Handshake (HTTP GET → WS):
GET /ws? tenant=acme&client=web
Headers:
Authorization: Bearer <JWT>
X-Trace-Id: <uuid>
Comandos de cliente:
json
{ "op":"subscribe", "topics":["user:123"], "resume_from":"1748852201:42" }
{ "op":"unsubscribe", "topics":["user:123"] }
{ "op":"ping", "ts":"2025-11-03T12:34:56Z" }
Respuestas del servidor:
json
{ "op":"ack", "id":"subscribe:user:123" }
{ "op":"event", "topic":"user:123", "seq":"1748852201:43", "type":"balance. updated", "data":{...} }
{ "op":"snapshot", "topic":"user:123", "seq":"1748852201:42", "state":{...} }
{ "op":"error", "code":"acl_denied", "reason":"no access to topic tournament:456" }
{ "op":"pong", "ts":"..." }
17) Lista de verificación de la UAT
- Resumen de offset después de 1/10/60 minutos de downtime del cliente.
- Dedoop: volver a entregar el mismo 'event _ id' no cambia el estado.
- El detector gap → un 'snapshot' automático y una alineación.
- Cuotas y retroceso: el cliente cargado recibe policy-disconnect.
- Multirregión: failover de la región con preservación offset.
- Seguridad: token rockero caducado por JWT, intento de suscripción fuera de la ACL.
- El RG/balance del evento viene antes/después de NAT - IU correctamente «cosido».
18) Errores frecuentes
No hay 'seq/offset' y reanudación - perder eventos y confianza.
Mezclar comandos de pago críticos en mutaciones WS: use NAT.
Ausencia de backpressure/cuotas - Conexiones «suspendidas» y avalancha de memoria.
El ordenamiento mundial es caro y no es necesario; suficiente orden en el partido.
La lógica de PII en los eventos es la violación de la privacidad y PCI/GDPR.
Falta de diccionario de eventos y versionamiento: los clientes se rompen.
Resumen
Los streams WebSocket proporcionan señales reactivas UX y en línea si se construyen como un canal resumible, seguro y limitado: WSS + mTLS/JWT, ACL en topics, seq/offset + resume, at-least-once con dedupom, backpressure/cupos, broker como fuente de verdad, observabilidad y SLO. Así que los streams siguen siendo rápidos para el usuario y manejables para la plataforma, sin compromisos de seguridad y dinero.