Ingeniería de Matchmaking Híbrido: Cómo la división de colas de Call of Duty expone la arquitectura moderna de Game Matchmaking
En resumen
Este artículo técnico analiza los desafíos de arquitectura de game matchmaking derivados de dividir las colas de jugadores en modos SBMM, basados en conexión e híbridos. Explora los equilibrios fundamentales entre latencia, varianza de habilidad (MMR) y tiempos de espera, acompañados de un motor dinámico de degradación de reglas en C#. Asimismo, examina soluciones backend esenciales como el bloqueo atómico de tickets, comunicación WebSocket en tiempo real y aprovisionamiento automático de dedicated servers para prevenir la fragmentación de pools de jugadores.
Cuando Activision anunció que Call of Duty: Black Ops 7 dividiría a su base de jugadores en tres colas de matchmaking distintas —Skill-Based Matchmaking (SBMM), la clásica basada en conexión e híbrida—, puso en el centro de atención un debate de ingeniería de backend con mucha historia. Para los títulos competitivos, decidir cómo emparejar a los jugadores no es solo una preferencia de diseño de juego; es un problema complejo de arquitectura de game matchmaking que equilibra restricciones de red de submilisegundos, varianza matemática de habilidad, fragmentación de pools y costes de cloud compute.
Dividir tu sistema de matchmaking en múltiples colas independientes parece, en la superficie, una simple función de preferencia del jugador. En realidad, duplica o triplica el overhead de ingeniería en tu infraestructura de backend. Cuando divides una base de jugadores concurrentes en colas separadas, la densidad de tickets cae drásticamente, los tiempos de espera en cola explotan exponencialmente en regiones de baja densidad y los algoritmos de asignación de servidores sufren un mayor churn.
En este artículo, analizaremos los trade-offs técnicos de SBMM frente al matchmaking centrado en la conexión (connection-first), diseccionaremos las matemáticas detrás de la expansión dinámica de colas híbridas, inspeccionaremos código backend C# real para el procesamiento de tickets y exploraremos cómo construir pools de matchmaking resilientes que escalen limpiamente.
El trilema inmutable del Matchmaking
Toda arquitectura moderna de game matchmaking debe resolver un problema de optimización con restricciones delimitado por tres variables contrapuestas:
- Latencia (RTT): El tiempo de ida y vuelta (round-trip time) entre el cliente del jugador y la instancia de Dedicated Server asignada (medido en milisegundos).
- Skill Delta ($\Delta$MMR): La brecha matemática en la representación de habilidad (MMR, Elo o TrueSkill) entre los jugadores de un lobby determinado.
- Duración en cola ($T_{queue}$): El tiempo total que un jugador pasa esperando en cola antes de que un ticket de partida válido pase a una asignación activa de servidor.
Latency (RTT)
/ \
/ \
/ Ideal \
/ Match \
/ \
Skill Delta (ΔMMR) --------------- Queue Duration (T_queue)
Puedes optimizar fácilmente para cualquiera de dos de estas variables a expensas absolutas de la tercera:
- Baja latencia + Bajo Skill Delta: Da como resultado largos tiempos de cola porque el motor debe buscar a jugadores raros y perfectamente capacitados que también vivan cerca del mismo datacenter cloud.
- Bajo tiempo de cola + Bajo Skill Delta: Da como resultado una alta latencia porque el matchmaker debe expandir su radio de búsqueda geográfica a nivel mundial para encontrar oponentes de igual nivel.
- Bajo tiempo de cola + Baja latencia: Da como resultado una alta varianza de habilidad (la clásica experiencia de partida pública "connection-first") porque el matchmaker toma inmediatamente a los clientes más cercanos disponibles sin importar las métricas de rendimiento.
Cuando un título como Black Ops 7 introduce tres modos de cola independientes, obliga a la arquitectura de matchmaking subyacente a mantener tres bucles paralelos de evaluación de reglas sobre pools de memoria fragmentados.
Si tu recuento de usuarios concurrentes (CCU) en una región específica —por ejemplo, Sudamérica a las 4 AM— cae por debajo de los 2.000 jugadores activos, dividir a esos jugadores en tres pools independientes reduce la densidad local de cada cola a solo unas pocas cientos de personas. Como resultado, las colas connection-first no logran encontrar dedicated servers de bajo ping y las colas SBMM se estancan indefinidamente.
Desconstruyendo SBMM vs. Ping-First vs. Colas Híbridas
Para construir un backend capaz de gestionar millones de tickets de partida, primero debes comprender cómo funciona cada modelo arquitectónico bajo el capó.
1. Arquitectura basada en conexión (Ping-First)
En un motor ping-first, las matrices de habilidad son totalmente secundarias o directamente se descartan. El objetivo principal es minimizar la degradación de la red (jitter, packet loss, RTT elevado).
- Sondeo de ping del cliente: Al entrar en la cola, el cliente del juego emite beacons de ping ICMP o UDP a una serie de edge gateways regionales (por ejemplo,
us-east-1,eu-central-1,ap-southeast-1). - Generación del vector de ping: El cliente construye un vector de latencias:
[ us-east: 24ms, us-west: 78ms, eu-central: 142ms ]y lo adjunta al payload de matchmaking. - Indexación espacial: El matchmaker clasifica a los jugadores estrictamente en hashes de región según umbrales de latencia aceptables (por ejemplo, $RTT < 50ms$).
Dado que la topología de la red dicta la creación de partidas, los buckets de búsqueda de jugadores son predecibles, lo que permite resolver tickets en tiempo $O(1)$ utilizando colas espaciales simples FIFO (First-In, First-Out).
2. Arquitectura de Skill-Based Matchmaking (SBMM)
SBMM prioriza la equidad de la partida modelando la capacidad del jugador mediante distribuciones gaussianas multidimensionales (por ejemplo, TrueSkill 2) o variantes personalizadas de Elo. Los datos de entrada clave incluyen la relación victorias/derrotas, la proporción bajas/muertes, el daño por minuto y la trayectoria de rendimiento reciente.
- Evaluación de distancia: El matchmaker calcula la distancia euclidiana o de Mahalanobis entre los vectores de los jugadores candidatos en un espacio de habilidad de $N$ dimensiones.
- Coste de ordenación: Los matchmakers no pueden confiar en colas FIFO simples. Deben mantener sets ordenados o árboles espaciales (como KD-trees) de perfiles de tickets para encontrar rápidamente candidatos dentro de una varianza de habilidad aceptable $\sigma$.
- Contracción del espacio de búsqueda: A medida que aumenta la habilidad (por ejemplo, el top 0.5% de los jugadores), el pool de candidatos elegibles se reduce drásticamente. Esto obliga al sistema a mantener los tickets indefinidamente o a flexibilizar gradualmente sus parámetros de rigurosidad de habilidad.
3. Arquitectura Híbrida Dinámica
En lugar de forzar a los jugadores a elegir entre colas rígidas, los backends de producción modernos suelen implementar un modelo Híbrido Dinámico. En esta configuración, cada ticket de partida comienza con un SBMM estricto y límites de latencia reducidos. A medida que $T_{queue}$ aumenta, una función de degradación de reglas expande continuamente el skill delta aceptable ($\Delta MMR$) y el límite de latencia ($RTT_{max}$)...
$$\Delta MMR_{allowed}(t) = \Delta MMR_{base} + \alpha \cdot t^{\gamma}$$
$$RTT_{allowed}(t) = \min\left(RTT_{max_cap}, RTT_{base} + \beta \cdot \lfloor t / \Delta t_{step} \rfloor\right)$$
Donde $\alpha$ y $\beta$ son coeficientes de expansión, $\gamma$ controla la inclinación de la curva exponencial y $t$ es la duración transcurrida en cola en segundos.
Al aprovechar la relajación dinámica, evitas el bloqueo indefinido en cola mientras mantienes una alta calidad de partida durante los períodos de mayor CCU.
Implementación de código: Motor de expansión dinámica de reglas
A continuación se muestra una implementación probada en combate en C# de un motor de expansión dinámica de reglas de matchmaking. Este servicio evalúa los tickets entrantes frente a pools de colas activas, calculando matrices de compatibilidad de ping en tiempo real y límites dinámicos de varianza de MMR.
using System;
using System.Collections.Generic;
using System.Linq;
namespace Horizon.Matchmaking.Engine
{
public class MatchmakingTicket
{
public string TicketId { get; set; } = Guid.NewGuid().ToString();
public string PlayerId { get; set; }
public double SkillRating { get; set; } // Elo/MMR representation
public Dictionary<string, int> RegionalPingMap { get; set; } = new(); // e.g., "us-east": 28
public DateTime EnqueuedAtUtc { get; set; }
}
public class DynamicMatchRules
{
public double MaxAllowedMmrDelta { get; set; }
public int MaxAllowedPingMs { get; set; }
}
public class MatchmakingEvaluator
{
private const double BaseMmrDelta = 50.0;
private const double MaxMmrCap = 600.0;
private const int BasePingMs = 35;
private const int AbsoluteMaxPingMs = 180;
private const double PingStepIntervalSeconds = 4.0;
/// <summary>
/// Calculates the expanded criteria for a ticket based on elapsed queue time.
/// </summary>
public DynamicMatchRules GetRelaxedRules(MatchmakingTicket ticket, DateTime currentUtc)
{
double elapsedTime = (currentUtc - ticket.EnqueuedAtUtc).TotalSeconds;
// Exponential expansion for skill tolerance to ensure high-skill players eventually match
double mmrExpansion = Math.Pow(elapsedTime, 1.35) * 4.5;
double calculatedMmrDelta = Math.Min(MaxMmrCap, BaseMmrDelta + mmrExpansion);
// Step-wise discrete expansion for latency tolerance (prevents constant server hopping)
int pingSteps = (int)Math.Floor(elapsedTime / PingStepIntervalSeconds);
int calculatedPingLimit = Math.Min(AbsoluteMaxPingMs, BasePingMs + (pingSteps * 15));
return new DynamicMatchRules
{
MaxAllowedMmrDelta = calculatedMmrDelta,
MaxAllowedPingMs = calculatedPingLimit
};
}
/// <summary>
/// Evaluates whether two tickets can be paired into a match session.
/// </summary>
public bool CanMatchTickets(MatchmakingTicket ticketA, MatchmakingTicket ticketB, DateTime currentUtc, out string selectedRegion)
{
selectedRegion = null;
DynamicMatchRules rulesA = GetRelaxedRules(ticketA, currentUtc);
DynamicMatchRules rulesB = GetRelaxedRules(ticketB, currentUtc);
// 1. Evaluate Skill Delta
double actualMmrDelta = Math.Abs(ticketA.SkillRating - ticketB.SkillRating);
if (actualMmrDelta > rulesA.MaxAllowedMmrDelta || actualMmrDelta > rulesB.MaxAllowedMmrDelta)
{
return false; // Skill variance too wide for current queue age
}
// 2. Evaluate Common Datacenter Ping Compatibility
int lowestCombinedPing = int.MaxValue;
foreach (var (region, pingA) in ticketA.RegionalPingMap)
{
if (ticketB.RegionalPingMap.TryGetValue(region, out int pingB))
{
// Match must satisfy BOTH players' dynamic ping limits
if (pingA <= rulesA.MaxAllowedPingMs && pingB <= rulesB.MaxAllowedPingMs)
{
int combinedPing = pingA + pingB;
if (combinedPing < lowestCombinedPing)
{
lowestCombinedPing = combinedPing;
selectedRegion = region;
}
}
}
}
return selectedRegion != null;
}
}
}
Aspectos técnicos clave de esta implementación:
- Satisfacción asimétrica de reglas: El método verifica que se respeten las restricciones en expansión de ambos jugadores (
pingA <= rulesA.MaxAllowedPingMs && pingB <= rulesB.MaxAllowedPingMs). Un jugador nuevo que lleva 2 segundos en cola no se verá arrastrado a un servidor con 150 ms de ping solo porque el otro jugador lleva esperando 90 segundos. - Escalonamiento discreto de ping: Los límites de latencia se expanden utilizando intervalos de tiempo discretos (
PingStepIntervalSeconds) en lugar de curvas continuas. Esto evita reasignaciones innecesarias de edge routers en cada bucle de tick. - Intersección espacial: El emparejamiento se basa en intersecciones de claves de diccionario a través de mapas regionales, seleccionando el datacenter con la latencia acumulada más baja.
Desafíos de infraestructura backend: Concurrencia, Lock Contention y aprovisionamiento de Dedicated Servers
Escribir algoritmos de matchmaking de forma aislada es sencillo. La verdadera dificultad de ingeniería surge cuando ejecutas este sistema en clusters de nodos distribuidos que manejan cientos de miles de tickets concurrentes.
[Player Clients]
│ (WebSockets / Low Latency)
▼
[Ingress Load Balancers]
│
▼
[Distributed Ticket Pool (e.g., Redis Cluster / Memory Grid)]
│
┌────┴────────────────────────┬────────────────────────┐
▼ ▼ ▼
[Worker Node 1] [Worker Node 2] [Worker Node 3]
│ │ │
└────┬────────────────────────┴────────────────────────┘
│ (Atomic Claim / Lua Mutex Lock)
▼
[Server Orchestration API] ──► Spin up Agones / Fleet Instances
1. Lock Contention de tickets distribuidos
Cuando múltiples procesos de matchmaker paralelos escanean el mismo pool central de tickets, ocurren condiciones de carrera inevitablemente. Dos hilos de worker independientes podrían evaluar simultáneamente el Ticket #1042 e intentar emparejarlo en dos lobbies completamente diferentes.
Para resolver esto, los desarrolladores deben ejecutar reclamos de bloqueos atómicos (atomic lock claims) mediante primitivas distribuidas (como scripts de Lua en Redis u operaciones atómicas en memory grid) antes de emitir una asignación de partida. Si un ticket está bloqueado por otro worker, el hilo libera inmediatamente su estado y retrocede.
2. Comunicación en tiempo real de alta frecuencia
Las actualizaciones de estado del matchmaking no pueden depender del HTTP polling estándar sin quemar recursos de cómputo masivos en handshakes de conexión inútiles. Para mantener a los clientes informados sobre estimaciones de duración en cola y búsquedas dinámicas de ping, los backends deben mantener WebSockets persistentes de doble canal o streams gRPC de larga duración.
Si actualmente estás dependiendo de bucles de polling ineficientes para el estado multiplayer de tu juego, consulta nuestra guía sobre cómo abandonar el HTTP polling en favor de WebSockets de Unreal Engine en backends en tiempo real.
3. Handshakes de asignación de servidor y aprovisionamiento de fleets
Emparejar jugadores es solo la mitad de la batalla. Una vez que se forma un grupo de tickets válido:
- El matchmaker se conecta con un orquestador de dedicated servers (por ejemplo, Agones o controladores personalizados de Kubernetes).
- Se debe reclamar o asignar una instancia limpia de servidor de juego en el datacenter seleccionado dentro de un plazo estricto (normalmente $< 1500ms$).
- El servidor se inicia, vincula su puerto UDP de escucha y devuelve su payload con la dirección IP/Puerto.
- El matchmaker envía los detalles de conexión a todos los WebSockets de los clientes.
Construir pools de tickets distribuidos, gestionar el lock contention, administrar clusters de sockets basados en regiones y orquestar manualmente el ciclo de vida de dedicated servers requiere meses de trabajo de infraestructura.
Aquí es donde horizOn elimina un importante overhead de ingeniería. En lugar de ensamblar almacenes de tickets en Redis, escribir wrappers personalizados para clusters de Agones y gestionar scripts de escalado de fleets de servidores, horizOn ofrece colas de matchmaking gestionadas de ultra baja latencia y orquestación de fleets de servidores desde el primer momento. Tú escribes tus conjuntos de reglas de matchmaking; horizOn se encarga de la distribución global, el bloqueo atómico de tickets y la asignación automática de servidores de manera fluida.
5 buenas prácticas para construir una arquitectura moderna de Game Matchmaking
Ya sea que estés construyendo un shooter competitivo de alto nivel o un título indie arcade casual, sigue estos principios arquitectónicos probados en producción:
1. Exige vectores de ping del cliente antes del envío del ticket
Nunca confíes en las búsquedas Geo-IP de la dirección del cliente para determinar la proximidad a tus datacenters. Las bases de datos Geo-IP son notoriamente imprecisas para el enrutamiento edge e ignoran la congestión del ISP en tiempo real. Obliga siempre al motor del cliente a medir la latencia directa mediante sondas de ping UDP a todos los endpoints regionales antes de llamar al endpoint de encolamiento.
2. Protégete contra la fragmentación del pool
Evita crear colas separadas para modos de juego secundarios a menos que tu base de jugadores activos lo justifique con claridad. Dividir a tus jugadores entre modos de juego, preferencias de mapa y colas de rigor de matchmaking intensifica la degradación del pool. Si tu CCU activo por cola cae por debajo de 1.000 jugadores en una región, cambia automáticamente a modos fallback dinámicos de cola única.
3. Desacopla el aprovisionamiento de servidores de la evaluación de partidas
Asegúrate de que los hilos de tu matchmaker operen de forma asíncrona respecto a tu capa de orquestación de fleets. Nunca mantengas abierto un bucle worker de matchmaking activo mientras esperas a que se inicie una instancia de servidor virtual. Utiliza colas de mensajes pub/sub no bloqueantes para solicitar asignaciones de servidores y entregar payloads de conexión cuando las instancias reporten un estado saludable.
4. Optimiza los costes de servidores inactivos con hibernación inteligente
El matchmaking experimenta picos impredecibles durante las horas punta y cae drásticamente fuera de ellas. Dejar cientos de instancias de servidores de juego inactivas y vacías malgasta ingresos operativos significativos. Implementa patrones dinámicos de warm-up de fleets e hibernación de servidores. Para un análisis técnico profundo sobre cómo optimizar el uso de servidores inactivos, lee nuestra publicación sobre arquitectura de servidores sin desperdicio y propuestas de optimización de servidores en Fortnite.
5. Realiza benchmarks de Netcode y Replicación bajo alta latencia
Incluso la arquitectura de matchmaking más avanzada emparejará ocasionalmente a jugadores a través de brechas de latencia moderadas ($100-120ms$) durante las horas de menor actividad. Asegúrate de que el netcode de tu servidor utilice predicción del cliente, compensación de lag y reconciliación de estado rigurosas para tolerar el retraso de paquetes con fluidez. Si las posiciones del cliente presentan tirones o saltos durante partidas con ping alto, consulta nuestra guía sobre cómo solucionar la desincronización de ubicación del jugador en el multijugador de Unreal Engine.
Resumen y próximos pasos
La decisión de Activision de ofrecer colas explícitas de SBMM, Connection-First e Híbridas en Call of Duty: Black Ops 7 ilustra lo crítica que es la arquitectura de matchmaking para la satisfacción del jugador. Sin embargo, dividir las colas requiere una inmensa densidad de jugadores, redes de sockets de baja latencia, algoritmos de degradación dinámica de reglas y un manejo atómico de tickets.
Si estás construyendo tu próximo juego multijugador, no malgastes meses escribiendo infraestructura de backend, servidores de sockets y lógica de asignación de fleets desde cero. Descubre cómo horizOn permite a los desarrolladores desplegar backends multijugador escalables, matchmaking automatizado y orquestación de servidores en cuestión de minutos. Prueba horizOn gratis hoy mismo o explora la documentación de horizOn para acelerar tu flujo de trabajo de backend.