Contexto: por qué la deriva de estado es un fallo “silencioso”
En un sportsbook en tiempo real, el estado operativo se materializa en varias capas: ingesta (feeds), pricing/trading, riesgo, y finalmente liquidación/contabilidad. La plataforma asume implícitamente que todas “ven” el mismo mundo con el mismo orden causal.
La deriva de estado aparece cuando dos o más capas mantienen versiones distintas del mismo hecho (marcador, reloj, suspensiones, límites, exposición, elegibilidad) y esas diferencias no disparan alarmas. El sistema sigue aceptando apuestas y cotizando, pero pierde:
- Determinismo (misma entrada ≠ misma decisión).
- Reproducibilidad (no se puede reconstruir por qué se aceptó/rechazó algo).
- Auditabilidad (complicación regulatoria y disputas).
Esto se conecta directamente con el marco de determinismo en sistemas regulados (ver /es/insights/ingenieria/el-determinismo-como-ventaja-competitiva-en-trading-regulado) y con patrones que “parecen” escalar pero degradan coherencia en producción (ver /es/insights/ingenieria/la-ilusion-de-escalabilidad-en-arquitecturas-de-sportsbooks).
Anatomía del estado en un sportsbook (infraestructura-first)
Capas típicas y sus fuentes de verdad de facto
- Ingesta
- Fuentes: proveedor A/B, scraping, oficial, trading desks.
- Estado: eventos, clock, scores, flags (suspensión, VAR, lesión), calidad de feed.
- Trading / Pricing
- Estado: parámetros del modelo, snapshot del evento, offsets, latencia percibida, guardrails.
- Riesgo
- Estado: exposición por mercado/selección/cliente, límites, reglas AML, KYC, correlaciones, “cooldowns”.
- Execution / Bet placement
- Estado: reglas de aceptación, idempotencia, locking de precio, expiraciones.
- Ledger
- Estado: balances, reservas, settlement, reconciliación.
En la práctica, cada capa termina manteniendo caches, proyecciones y materializaciones. El fallo no es tener copias; el fallo es no tener un protocolo explícito de convergencia.
“Estado” no es solo datos: es orden y tiempo
Para trading en vivo, dos propiedades son críticas:
- Orden de eventos: qué update ocurrió antes (y bajo qué reloj).
- Validez temporal: hasta cuándo un precio o un estado era válido.
Si cada servicio reinterpreta orden/tiempo (por latencia, reintentos, clocks desalineados), la deriva es inevitable.
Modos de deriva de estado (los que no suelen alertar)
1) Divergencia por orden (out-of-order) y replays parciales
Síntoma: ingesta corrige un score/clock; trading ya repreció; riesgo aún calcula exposición con el estado anterior.
Causas infra:
- Kafka/streaming sin claves de orden por entidad (eventId/marketId).
- Retries que reinsertan mensajes antiguos con timestamps “nuevos”.
- Consumers con commits adelantados o lag variable por partición.
Impacto:
- Precios ofertados basados en un estado que riesgo considera inválido.
- Aceptación de apuestas en ventanas que deberían estar suspendidas.
2) Divergencia por “caches inteligentes” (TTL) y fallbacks
Síntoma: trading usa cache local para resiliencia; ingesta cambia estado; riesgo consulta otro store con TTL distinto.
Causas infra:
- TTL heterogéneos por servicio.
- Circuit breakers que activan un fallback a un snapshot antiguo.
- “Stale-while-revalidate” sin límites de staleness por mercado.
Impacto:
- El sistema se mantiene “verde” (latencia baja), pero actúa sobre datos obsoletos.
3) Divergencia por semántica distinta del mismo flag
Síntoma: “suspendido” significa cosas distintas (por ejemplo, suspendido para aceptar vs suspendido para cotizar).
Causas:
- Contratos API débiles (booleanos ambiguos).
- Evolución de schema sin versionado semántico.
- Normalización incompleta entre proveedores.
Impacto:
- Aceptación de apuestas con pricing “congelado”.
- Rechazos intermitentes difíciles de explicar al cliente.
4) Divergencia por identidad (IDs) y mapeos inconsistentes
Síntoma: el mismo partido tiene distintos eventIds según feed; trading y riesgo agregan exposición en entidades diferentes.
Causas:
- Servicio de entity resolution no determinista.
- Mapeos eventual-consistent sin “hard locks” para eventos live.
- Merge/split de entidades durante el evento.
Impacto:
- Subestimación de exposición real.
- Límites que no se aplican donde deberían.
5) Divergencia por particiones de red: “split-brain” funcional
Síntoma: dos regiones/az activas aceptan apuestas con estados distintos; reconcilian después.
Causas infra:
- Multi-region activo/activo sin consenso estricto para el plano de control.
- Stores con consistencia eventual en rutas críticas (p.ej., límites y suspensiones).
- Falta de fencing tokens en writers.
Impacto:
- Pérdida de atomicidad entre aceptación y reserva de riesgo.
- Incidentes de “void” y disputas de settlement.
Señales operativas: cómo se ve en métricas (cuando nadie mira lo correcto)
Métricas que suelen estar “bien” aunque haya deriva
- CPU/memoria normales.
- Lag medio aceptable.
- Error rate HTTP bajo.
- P99 de colocación bajo por uso de caches.
Métricas que sí delatan deriva
- Discrepancias de versión por entidad (eventVersion/marketVersion) entre servicios.
- Tasa de decisiones no reproducibles: misma requestId → distinta decisión al reejecutar con el mismo snapshot.
- Drift budget consumido: porcentaje de operaciones ejecutadas con staleness > X ms.
- Mismatch de suspensiones: apuestas aceptadas durante ventanas suspendidas según ingesta.
- Reconciliación de exposición: delta entre exposición online vs ledger/settlement.
Contención: limitar el blast radius sin “apagar el mundo”
Guardrails deterministas en el plano de ejecución
- Price locking con versión: un bet accept debe incluir (eventId, marketId, version) y fallar si el writer ve una versión distinta.
- Fencing para writers de estado crítico (suspensiones, límites): tokens monotónicos para evitar split-brain.
- Idempotencia end-to-end: requestId + invariantes (stake, selection, price, version).
“Fail closed” selectivo, no global
- Cerrar solo mercados/eventos con drift > umbral.
- Degradar a “view-only” cuando la ingesta es incierta.
- Aplicar circuit breakers por entidad, no por servicio.
Presupuestos de staleness por mercado
No todos los mercados toleran lo mismo:
- Moneyline live: staleness permitido muy bajo.
- Props de baja frecuencia: tolerancia mayor.
Hacerlo explícito evita que caches “optimicen” donde no deben.
Prevención estructural: arquitectura para convergencia, no solo para throughput
Estado como stream con versiones monotónicas
- Versionado por entidad (event/market) con monotonicidad garantizada.
- Consumers deben rechazar updates con version <= lastSeen.
- Correcciones (amendments) como eventos explícitos, no overwrites silenciosos.
Un plano de control único para suspensiones y elegibilidad
Separar:
- Data plane: precios, snapshots, cotización.
- Control plane: suspender/activar, límites, reglas de aceptación.
El control plane requiere consistencia más fuerte que el data plane.
Reconciliación continua (online) con invariantes
Ejemplos de invariantes:
- No aceptar si marketState != OPEN.
- Exposición por marketId debe ser >= suma de reservas de bets aceptadas.
- Settlement solo si eventState terminal y versión final confirmada.
La reconciliación debe correr como servicio de producción, no como batch post-mortem.
Observabilidad orientada a estado (no a servicios)
- Traces con state vector (versions, timestamps, source).
- Logs estructurados con causalidad: parent updateId → decisionId.
- Dashboards por entidad (top eventos por drift), no por microservicio.
Para una taxonomía amplia de ingeniería/operación, ver /es/insights.
Caso típico de incidente (patrón)
Cadena de eventos
- Feed A envía gol con timestamp T; feed B lo envía 2s después.
- Ingesta publica update v105 (gol), luego recibe corrección y publica v106 (anulado).
- Trading consume v105, reprice; su cache TTL mantiene pricing 3s.
- Riesgo consume v106 a tiempo y marca mercado suspendido.
- Execution acepta apuestas porque valida contra cache de trading (OPEN) y no contra control plane (SUSPENDED).
Por qué “no se ve”
- No hay errores: todo responde.
- Latencia es baja: caches funcionan.
- El drift ocurre en una ventana pequeña, pero suficiente para pérdidas.
Key takeaways
- La deriva de estado no es “inconsistencia eventual”; es pérdida de determinismo en decisiones de aceptación/pricing/riesgo.
- Medir salud por servicios oculta el problema; medir versiones y staleness por entidad lo revela.
- La contención efectiva combina price locking con versión, fencing y fail-closed selectivo.
- La prevención exige un control plane consistente y reconciliación continua con invariantes.