Engenharia de Matchmaking Híbrido: Como a Divisão de Queues do Call of Duty Expõe a Arquitetura Moderna de Game Matchmaking
Em resumo
Analisamos como a divisão de queues de matchmaking em Call of Duty: Black Ops 7 expõe os principais desafios de engenharia de backend, incluindo latência, variação de skill e tempo de fila. O artigo explora a matemática por trás da expansão dinâmica de regras, uma implementação prática em C# para relaxamento de tickets e estratégias para mitigar lock contention em pools distribuídos com Redis. Também apresentamos boas práticas essenciais para desacoplar o provisionamento de dedicated servers e otimizar a infraestrutura para jogos multiplayer escaláveis.
Quando a Activision anunciou que Call of Duty: Black Ops 7 dividiria sua base de jogadores em três queues de matchmaking distintas — Skill-Based Matchmaking (SBMM), Classic Connection-Based e Híbrida —, ela brought um antigo debate de engenharia de backend diretamente para os holofotes. Para títulos competitivos, decidir como parear jogadores não é apenas uma preferência de game design; é um problema complexo de game matchmaking architecture que equilibra restrições de rede sub-milissegundo, variação matemática de skill, fragmentação de pool e custos de cloud compute.
Dividir seu sistema de matchmaking em múltiplas queues distintas parece uma funcionalidade simples de preferência do jogador na superfície. Na realidade, isso dobra ou triplica o overhead de engenharia na sua infraestrutura de backend. Quando você divide uma base de Concurrent Users (CCU) em queues separadas, a densidade de tickets despenca drasticamente, o tempo de fila explode exponencialmente em regiões de baixa densidade e os algoritmos de alocação de servidores enfrentam um churn elevado.
Neste artigo, analisaremos os trade-offs técnicos do SBMM versus matchmaking connection-first, desconstruiremos a matemática por trás da expansão dinâmica de queues híbridas, inspecionaremos código C# real de backend para processamento de tickets e exploraremos como construir pools de matchmaking resilientes que escalam com precisão.
O Trilema Imutável do Matchmaking
Toda arquitetura moderna de game matchmaking precisa resolver um problema de otimização de restrições delimitado por três variáveis concorrentes:
- Latência (RTT): O round-trip time entre o client do jogador e a instância do dedicated server alocada (medido em milissegundos).
- Skill Delta ($\Delta$MMR): A lacuna matemática na representação de habilidade (MMR, Elo ou TrueSkill) entre os jogadores de um determinado lobby.
- Queue Duration ($T_{queue}$): O tempo total que um jogador passa esperando em um estado de atrito de fila ociosa antes que um ticket de match válido se transforme em uma alocação ativa de servidor.
Latência (RTT)
/ \
/ \
/ Match \
/ Ideal \
/ \
Skill Delta (ΔMMR) --------------- Queue Duration (T_queue)
Você pode otimizar facilmente qualquer duas dessas variáveis ao custo absoluto da terceira:
- Baixa Latência + Baixo Skill Delta: Resulta em longos tempos de fila, pois o engine precisa procurar jogadores raros, com habilidades perfeitamente alinhadas e que também estejam geograficamente próximos do mesmo datacenter em nuvem.
- Baixo Tempo de Fila + Baixo Skill Delta: Resulta em alta latência, pois o matchmaker precisa expandir seu raio de busca geográfica mundialmente para encontrar oponentes com a mesma habilidade.
- Baixo Tempo de Fila + Baixa Latência: Resulta em alta variação de skill (a clássica experiência de partidas públicas "connection-first"), pois o matchmaker pega imediatamente os clients disponíveis mais próximos, independentemente das métricas de desempenho.
Quando um título como Black Ops 7 introduz três modos de queue separados, ele força a arquitetura de matchmaking subjacente a manter três loops paralelos de avaliação de regras sobre pools de memória fragmentados.
Se a sua contagem de Concurrent Users (CCU) em uma região específica — por exemplo, América do Sul às 4h da manhã — cair para menos de 2.000 jogadores ativos, dividir esses jogadores em três pools separados reduz a densidade local de cada queue para apenas algumas centenas. Como resultado, as queues connection-first falham em encontrar dedicated servers com ping baixo, e as queues de SBMM entram em um estado de espera indefinido.
Desconstruindo SBMM vs. Ping-First vs. Queues Híbridas
Para construir um backend capaz de processar milhões de tickets de partida, primeiro você precisa entender como cada modelo arquitetural opera nos bastidores.
1. Arquitetura Connection-Based (Ping-First)
Em um engine connection-first, as matrizes de skill são totalmente secundárias ou descartadas. O objetivo principal é minimizar a degradação da rede (jitter, packet loss, RTT alto).
- Client Ping Probing: Ao entrar na queue, o client do jogo envia beacons de ping ICMP ou UDP para uma série de edge gateways regionais (ex.:
us-east-1,eu-central-1,ap-southeast-1). - Geração do Vetor de Ping: O client constrói um vetor de latências:
[ us-east: 24ms, us-west: 78ms, eu-central: 142ms ]e o anexa ao payload de matchmaking. - Indexação Espacial: O matchmaker agrupa os jogadores estritamente em hashes de região com base em limites aceitáveis de latência (ex.: $RTT < 50ms$).
Como a topologia de rede dita a criação das partidas, os buckets de busca de jogadores são previsíveis, permitindo que os tickets sejam resolvidos em tempo $O(1)$ usando queues espaciais simples do tipo FIFO (First-In, First-Out).
2. Arquitetura de Skill-Based Matchmaking (SBMM)
O SBMM prioriza o equilíbrio da partida modelando a capacidade do jogador usando distribuições gaussianas multidimensionais (ex.: TrueSkill 2) ou variantes personalizadas do Elo. As principais entradas incluem taxa de vitória/derrota, taxa de kill/death, dano por minuto e a trajetória de desempenho recente.
- Avaliação de Distância: O matchmaker calcula a distância euclidiana ou de Mahalanobis entre os vetores dos jogadores candidatos em um espaço de skill $N$-dimensional.
- Custo de Ordenação: Os matchmakers não podem confiar em queues FIFO simples. Eles precisam manter sorted sets ou árvores espaciais (como KD-trees) de perfis de tickets para encontrar rapidamente candidatos dentro de uma variação aceitável de skill $\sigma$.
- Contração do Espaço de Busca: À medida que a habilidade aumenta (ex.: o top 0,5% dos jogadores), o pool de candidatos elegíveis encolhe drasticamente. Isso força o sistema a reter os tickets indefinidamente ou a relaxar lentamente seus parâmetros de rigidez de skill.
3. Arquitetura Híbrida Dinâmica
Em vez de forçar os jogadores a escolhas de queue rígidas no código, os backends modernos de produção frequentemente implementam um modelo Híbrido Dinâmico. Nessa configuração, todo ticket de partida começa com regras estritas de SBMM e limites baixos de latência. À medida que $T_{queue}$ aumenta, uma função de decay de regras expande continuamente o delta de skill aceitável ($\Delta MMR$) e o limite de latência ($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)$$
Onde $\alpha$ e $\beta$ são coeficientes de expansão, $\gamma$ controla a inclinação da curva exponencial e $t$ é a duração decorrida na fila em segundos.
Aproveitando a relaxação dinâmica, você evita bloqueios infinitos na queue enquanto mantém uma alta qualidade de partida durante os períodos de pico de CCU.
Implementação de Código: Engine de Expansão Dinâmica de Regras
Abaixo está uma implementação testada em batalha em C# de um engine de expansão dinâmica de regras de matchmaking. Este serviço avalia os tickets recebidos contra pools de queues ativos, calculando matrizes de compatibilidade de ping em tempo real e limites dinâmicos de variação 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;
}
}
}
Principais Aspectos Técnicos Desta Implementação:
- Satisfação Assimétrica de Regras: O método verifica se as restrições em expansão de ambos os jogadores são respeitadas (
pingA <= rulesA.MaxAllowedPingMs && pingB <= rulesB.MaxAllowedPingMs). Um jogador novo na queue há apenas 2 segundos não será puxado para um servidor de 150ms de ping só porque o outro jogador já está esperando há 90 segundos. - Expansão Discreta de Ping: Os limites de latência expandem-se usando intervalos de tempo discretos (
PingStepIntervalSeconds) em vez de curvas contínuas. Isso evita reatribuições desnecessárias nos roteadores de borda a cada loop de tick. - Interseção Espacial: O pareamento baseia-se na interseção de chaves de dicionário entre mapas regionais, selecionando o datacenter com a menor latência cumulativa de round-trip.
Desafios de Infraestrutura de Backend: Concorrência, Lock Contention e Provisionamento de Dedicated Servers
Escrever algoritmos de matchmaking de forma isolada é simples. A verdadeira dificuldade de engenharia surge ao executar este sistema em clusters de nós distribuídos que processam centenas de milhares de tickets simultâneos.
[Player Clients]
│ (WebSockets / Baixa Latência)
▼
[Ingress Load Balancers]
│
▼
[Pool Distribuído de Tickets (ex.: Redis Cluster / Memory Grid)]
│
┌────┴────────────────────────┬────────────────────────┐
▼ ▼ ▼
[Worker Node 1] [Worker Node 2] [Worker Node 3]
│ │ │
└────┬────────────────────────┴────────────────────────┘
│ (Claim Atômico / Lua Mutex Lock)
▼
[Server Orchestration API] ──► Subir instâncias Agones / Fleet
1. Distributed Ticket Lock Contention
Quando múltiplos processos de matchmaking em paralelo escaneiam o mesmo pool central de tickets, condições de corrida (race conditions) são inevitáveis. Duas threads de worker separadas podem avaliar simultaneamente o Ticket #1042 e tentar pareá-lo em dois lobbies completamente diferentes.
Para resolver isso, os desenvolvedores devem executar claims de lock atômico usando primitivas distribuídas (como scripts Lua no Redis ou operações atômicas em memory grid) antes de emitir a atribuição de uma partida. Se um ticket estiver bloqueado por outro worker de matchmaking, a thread libera seu estado imediatamente e faz o backtrack.
2. Comunicação em Tempo Real de Alta Frequência
As atualizações de status de matchmaking não podem depender de polling HTTP padrão sem queimar recursos massivos de compute com handshakes de conexão inúteis. Para manter os clients informados sobre estimativas de duração da fila e buscas dinâmicas de ping, os backends devem manter WebSockets bidirecionais persistentes de canal duplo ou streams gRPC de longa duração.
Se você atualmente depende de loops de polling ineficientes para o estado multiplayer da sua aplicação, confira nosso guia sobre como substituir o HTTP polling por WebSockets na Unreal Engine em backends em tempo real.
3. Handshakes de Alocação de Servidor & Provisionamento de Fleet
Parear jogadores é apenas metade da batalha. Assim que um grupo válido de tickets é formado:
- O matchmaker entra em contato com um orquestrador de dedicated servers (ex.: Agones, controllers customizados de Kubernetes).
- Uma instância limpa de game server precisa ser alocada ou reivindicada no datacenter selecionado dentro de um prazo estrito (tipicamente $< 1500ms$).
- O servidor é inicializado, faz o bind de sua porta UDP de escuta e retorna o payload com seu endereço IP/Porta.
- O matchmaker envia os detalhes de conexão para todos os WebSockets dos clients.
Construir pools distribuídos de tickets, gerenciar lock contention, lidar com clusters de sockets por região e orquestrar o ciclo de vida de dedicated servers manualmente exige meses de trabalho de infraestrutura.
É aqui que o horizOn elimina um enorme overhead de engenharia. Em vez de montar datastores de tickets em Redis, criar wrappers customizados para clusters Agones e gerenciar scripts de auto-scaling para frotas de servidores, o horizOn oferece queues de matchmaking gerenciadas com latência ultra-baixa e orquestração de frotas de servidores pronta para uso. Você define suas regras de matchmaking; o horizOn cuida da distribuição global, do locking atômico de tickets e da alocação automática de servidores de forma fluida.
5 Boas Práticas para Construir uma Arquitetura Moderna de Game Matchmaking
Seja para um shooter competitivo de alto nível ou um título indie casual arcade, siga estes princípios arquiteturais testados na prática:
1. Exija Vetores de Ping do Client Antes do Envio do Ticket
Nunca confie no lookup de Geo-IP do client para determinar a proximidade com seus datacenters. Bases de dados Geo-IP são notoriamente imprecisas para roteamento de borda e ignoram o congestionamento em tempo real dos provedores (ISPs). Sempre force o engine do client a medir a latência direta via testes de ping UDP para todos os endpoints regionais antes de chamar o endpoint de enqueue.
2. Proteja-se Contra a Fragmentação do Pool
Evite criar queues separadas para modos secundários de jogo, a menos que sua base de jogadores ativos justifique isso claramente. Dividir sua base de jogadores entre modos de jogo, preferências de mapa e queues com restrições rígidas de matchmaking acelera a degradação do pool. Se o seu CCU ativo por queue cair abaixo de 1.000 jogadores em uma região, mude automaticamente para modos fallback dinâmicos de queue única.
3. Desacople o Provisionamento de Servidores da Avaliação de Matches
Garantir que as threads do seu matchmaker operem de forma assíncrona em relação à camada de orquestração de frota é essencial. Nunca mantenha o loop de worker do matchmaker bloqueado esperando uma instância de servidor virtual inicializar. Utilize message queues pub/sub não-bloqueantes para solicitar a alocação de servidores e entregar os payloads de conexão quando as instâncias reportarem status saudável.
4. Otimize Custos de Servidores Ociosos com Hibernação Inteligente
O fluxo de matchmaking oscila de forma imprevisível durante horários de pico e despenca bruscamente fora deles. Deixar centenas de instâncias de servidores de jogos ociosas e vazias desperdiça uma receita operacional significativa. Implemente padrões dinâmicos de warm-up de frotas e hibernação de servidores. Para uma análise técnica aprofundada sobre a otimização do uso de servidores ociosos, leia nosso artigo sobre arquitetura de servidores com zero desperdício e propostas de otimização de servidores para Fortnite.
5. Faça Benchmarks de Netcode e Replicação sob Alta Latência
Mesmo a arquitetura de matchmaking mais avançada eventualmente pareará jogadores com lacunas moderadas de latência ($100-120ms$) durante horários de menor movimento. Certifique-se de que o netcode do seu servidor utilize client prediction rígida, compensação de lag (lag compensation) e reconciliação de estado para suportar o atraso de pacotes com elegância. Se a posição dos clients apresentar descontinuidade ou "saltos" durante partidas com ping alto, consulte nosso guia sobre como corrigir o desync de localização do jogador no multiplayer da Unreal Engine.
Resumo & Próximos Passos
A decisão da Activision de oferecer queues explícitas de SBMM, Connection-First e Híbrida em Call of Duty: Black Ops 7 ilustra o quão crítica é a arquitetura de matchmaking para a satisfação do jogador. No entanto, a divisão de queues exige uma densidade massiva de jogadores, networking via sockets de baixa latência, algoritmos dinâmicos de decay de regras e manipulação atômica de tickets.
Se você está construindo seu próximo jogo multiplayer, não perca meses desenvolvendo infraestrutura de backend, servidores de sockets e lógica de alocação de frotas do zero. Saiba como o horizOn capacita desenvolvedores a implantar backends escaláveis para jogos multiplayer, matchmaking automatizado e orquestração de servidores em minutos. Experimente o horizOn gratuitamente hoje mesmo ou explore a documentação do horizOn para acelerar seu fluxo de trabalho no backend.