Ingénierie du matchmaking hybride : ce que la division des queues de Call of Duty révèle sur l'architecture moderne de matchmaking
En bref
La division des queues de matchmaking dans Call of Duty: Black Ops 7 met en lumière les contraintes complexes de l'ingénierie backend multijoueur, entre latence réseau, écart de niveau (MMR) et coûts de serveurs. Pour éviter la fragmentation des pools de joueurs et l'explosion des temps d'attente, les architectures modernes adoptent des moteurs d'expansion dynamique de règles combinant verrous atomiques et assouplissement progressif des critères. Cet article détaille le trilemme du matchmaking, propose une implémentation C# d'un moteur de relaxation de règles et présente les meilleures pratiques pour orchestrer efficacement des serveurs dédiés à grande échelle.
Lorsque Activision a annoncé que Call of Duty: Black Ops 7 diviserait sa base de joueurs en trois queues de matchmaking distinctes — le Skill-Based Matchmaking (SBMM), le Connection-Based classique et une approche Hybride —, cela a remis au premier plan un débat d'ingénierie backend de longue date. Pour les titres compétitifs, décider de la manière d'associer les joueurs n'est pas seulement une préférence de game design ; il s'agit d'un problème complexe d'architecture de matchmaking de jeu (game matchmaking architecture) qui doit équilibrer des contraintes réseau sous la milliseconde, la variance mathématique du niveau de jeu, la fragmentation du pool de joueurs et les coûts de compute dans le cloud.
Diviser votre système de matchmaking en plusieurs queues distinctes semble être une simple fonctionnalité de confort pour les joueurs en surface. En réalité, cela double ou triple la charge d'ingénierie sur votre infrastructure backend. Lorsque vous séparez une base de joueurs simultanés (CCU) en différentes queues, la densité de tickets chute drastiquement, les temps d'attente explosent de manière exponentielle dans les régions à faible densité, et les algorithmes d'allocation de serveurs subissent un churn accru.
Dans cet article, nous analyserons les compromis techniques entre le SBMM et le matchmaking orienté connexion (ping-first), disséquerons les mathématiques derrière l'expansion dynamique des queues hybrides, examinerons du code C# backend réel pour le traitement des tickets, et explorerons la création de pools de matchmaking résilients capables de passer à l'échelle proprement.
Le trilemme immuable du matchmaking
Toute architecture moderne de matchmaking de jeu doit résoudre un problème d'optimisation sous contraintes délimité par trois variables concurrentes :
- Latence (RTT) : Le round-trip time entre le client du joueur et l'instance de dedicated server allouée (mesuré en millisecondes).
- Skill Delta ($\Delta$MMR) : L'écart mathématique de niveau (représenté par MMR, Elo ou TrueSkill) entre les joueurs au sein d'un même lobby.
- Durée dans la queue ($T_{queue}$) : Le temps total passé par un joueur à attendre dans la queue avant qu'un ticket valide ne se transforme en une allocation de serveur active.
Latency (RTT)
/ \
/ \
/ Match \
/ idéal \
/ \
Skill Delta (ΔMMR) --------------- Queue Duration (T_queue)
Vous pouvez facilement optimiser deux de ces variables au détriment direct de la troisième :
- Faible latence + Faible Skill Delta : Entraîne des temps d'attente élevés dans la queue, car le moteur doit rechercher de rares joueurs ayant exactement le même niveau tout en étant proches du même datacenter cloud.
- Faible durée de queue + Faible Skill Delta : Entraîne une latence élevée, car le matchmaker doit élargir son rayon de recherche géographique à l'échelle mondiale pour trouver des adversaires de niveau équivalent.
- Faible durée de queue + Faible latence : Entraîne une forte variance de niveau (l'expérience classique des parties publiques "ping-first"), car le matchmaker regroupe immédiatement les clients les plus proches sans tenir compte de leurs métriques de performance.
Lorsqu'un titre comme Black Ops 7 introduit trois modes de queue distincts, cela force l'architecture de matchmaking sous-jacente à maintenir trois boucles d'évaluation de règles en parallèle sur des pools de mémoire fragmentés.
Si votre nombre d'utilisateurs simultanés (CCU) dans une région spécifique — par exemple l'Amérique du Sud à 4 heures du matin — chute en dessous de 2 000 joueurs actifs, diviser ces joueurs en trois pools distincts réduit la densité locale pour chaque queue à quelques centaines de personnes seulement. Résultat : les queues orientées connexion ne parviennent plus à trouver de dedicated servers avec un ping faible, et les queues SBMM restent bloquées indéfiniment.
Déconstruction : SBMM vs Ping-First vs Queues Hybrides
Pour concevoir un backend capable de gérer des millions de tickets de matchmaking, vous devez d'abord comprendre le fonctionnement interne de chaque modèle architectural.
1. Architecture basée sur la connexion (Ping-First)
Dans un moteur ping-first, les matrices de niveau sont secondaires ou ignorées. L'objectif principal est de minimiser la dégradation du réseau (jitter, perte de paquets, RTT élevé).
- Test de ping client (Ping Probing) : Dès l'entrée dans la queue, le client de jeu émet des balises (beacons) ICMP ou UDP vers une série de gateways régionales (ex.
us-east-1,eu-central-1,ap-southeast-1). - Génération du vecteur de ping : Le client construit un vecteur de latences :
[ us-east: 24ms, us-west: 78ms, eu-central: 142ms ]et l'attache au payload du matchmaking. - Indexation spatiale : Le matchmaker regroupe strictement les joueurs dans des hashes régionaux en fonction de seuils de latence acceptables (ex. $RTT < 50ms$).
Puisque la topologie réseau dicte la création de la partie, les buckets de recherche de joueurs sont prévisibles, ce qui permet de résoudre les tickets en un temps $O(1)$ à l'aide de simples queues spatiales FIFO (First-In, First-Out).
2. Architecture Skill-Based Matchmaking (SBMM)
Le SBMM donne la priorité à l'équité des parties en modélisant les capacités des joueurs via des distributions gaussiennes multidimensionnelles (par exemple TrueSkill 2) ou des variantes d'Elo personnalisées. Les données clés incluent le ratio victoires/défaites, le ratio éliminations/morts, les dégâts par minute et la trajectoire de performance récente.
- Évaluation de la distance : Le matchmaker calcule la distance euclidienne ou de Mahalanobis entre les vecteurs des joueurs candidats dans un espace de compétences à $N$ dimensions.
- Coût de tri : Les matchmakers ne peuvent pas s'appuyer sur de simples queues FIFO. Ils doivent maintenir des ensembles triés (sorted sets) ou des arbres spatiaux (comme les KD-trees) de profils de tickets pour trouver rapidement des candidats dans une variance de niveau $\sigma$ acceptable.
- Contraction de l'espace de recherche : À mesure que le niveau augmente (ex. le top 0,5 % des joueurs), le pool de candidats éligibles se réduit drastiquement. Cela force le système soit à conserver les tickets indéfiniment, soit à assouplir progressivement ses paramètres de rigueur de niveau.
3. Architecture Hybride Dynamique
Plutôt que d'imposer aux joueurs des choix de queues rigides en dur, les backends de production modernes implémentent fréquemment un modèle Hybride Dynamique. Dans cette configuration, chaque ticket de matchmaking débute avec des règles SBMM strictes et des limites de latence basses. Au fur et à mesure que $T_{queue}$ augmente, une fonction de dégradation des règles élargit continuellement l'écart de niveau acceptable ($\Delta MMR$) et la limite de latence ($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)$$
Où $\alpha$ et $\beta$ sont des coefficients d'expansion, $\gamma$ contrôle la sévérité de la courbe exponentielle, et $t$ est le temps écoulé dans la queue en secondes.
En exploitant la relaxation dynamique, vous évitez les blocages infinis dans la queue tout en maintenant une excellente qualité de matchmaking pendant les pics de CCU.
Implémentation de code : Moteur d'expansion dynamique des règles
Voici une implémentation C# éprouvée d'un moteur d'expansion dynamique des règles de matchmaking. Ce service évalue les tickets entrants par rapport aux pools de queues actives, en calculant en temps réel les matrices de compatibilité de ping et les plafonds de variance MMR dynamiques.
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; } // Représentation Elo/MMR
public Dictionary<string, int> RegionalPingMap { get; set; } = new(); // ex. "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>
/// Calcule les critères élargis pour un ticket en fonction du temps passé dans la queue.
/// </summary>
public DynamicMatchRules GetRelaxedRules(MatchmakingTicket ticket, DateTime currentUtc)
{
double elapsedTime = (currentUtc - ticket.EnqueuedAtUtc).TotalSeconds;
// Expansion exponentielle pour la tolérance de niveau afin de garantir que les joueurs de haut niveau finissent par trouver une partie
double mmrExpansion = Math.Pow(elapsedTime, 1.35) * 4.5;
double calculatedMmrDelta = Math.Min(MaxMmrCap, BaseMmrDelta + mmrExpansion);
// Expansion discrète par étapes pour la tolérance de latence (évite le basculement permanent entre serveurs)
int pingSteps = (int)Math.Floor(elapsedTime / PingStepIntervalSeconds);
int calculatedPingLimit = Math.Min(AbsoluteMaxPingMs, BasePingMs + (pingSteps * 15));
return new DynamicMatchRules
{
MaxAllowedMmrDelta = calculatedMmrDelta,
MaxAllowedPingMs = calculatedPingLimit
};
}
/// <summary>
/// Évalue si deux tickets peuvent être associés dans une même session de jeu.
/// </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. Évaluer le Skill Delta
double actualMmrDelta = Math.Abs(ticketA.SkillRating - ticketB.SkillRating);
if (actualMmrDelta > rulesA.MaxAllowedMmrDelta || actualMmrDelta > rulesB.MaxAllowedMmrDelta)
{
return false; // Écart de niveau trop important pour la durée actuelle dans la queue
}
// 2. Évaluer la compatibilité de ping sur un Datacenter commun
int lowestCombinedPing = int.MaxValue;
foreach (var (region, pingA) in ticketA.RegionalPingMap)
{
if (ticketB.RegionalPingMap.TryGetValue(region, out int pingB))
{
// Le match doit satisfaire les limites de ping dynamiques des DEUX joueurs
if (pingA <= rulesA.MaxAllowedPingMs && pingB <= rulesB.MaxAllowedPingMs)
{
int combinedPing = pingA + pingB;
if (combinedPing < lowestCombinedPing)
{
lowestCombinedPing = combinedPing;
selectedRegion = region;
}
}
}
}
return selectedRegion != null;
}
}
}
Aspects techniques clés de cette implémentation :
- Satisfaction asymétrique des règles : La méthode vérifie que les contraintes dynamiques des deux joueurs sont respectées (
pingA <= rulesA.MaxAllowedPingMs && pingB <= rulesB.MaxAllowedPingMs). Un nouveau joueur entré dans la queue depuis 2 secondes ne sera pas entraîné sur un serveur à 150 ms de ping simplement parce que l'autre joueur attend depuis 90 secondes. - Paliers de ping discrets : Les limites de latence s'élargissent par étapes temporelles discrètes (
PingStepIntervalSeconds) plutôt que sur une courbe continue. Cela évite les réaffectations inutiles au niveau des routeurs edge à chaque boucle d'évaluation. - Intersection spatiale : L'association repose sur l'intersection des clés de dictionnaire entre les cartes régionales, en sélectionnant le datacenter présentant la latence cumulative RTT la plus faible.
Défis d'infrastructure backend : Concurrence, Lock Contention et Provisioning de Dedicated Servers
Rédiger des algorithmes de matchmaking de manière isolée est relativement simple. La véritable difficulté d'ingénierie survient lorsque vous exécutez ce système sur des clusters de nœuds distribués gérant des centaines de milliers de tickets simultanés.
[Clients Joueurs]
│ (WebSockets / Faible Latence)
▼
[Load Balancers Ingress]
│
▼
[Pool de Tickets Distribué (ex. Cluster Redis / Memory Grid)]
│
┌────┴────────────────────────┬────────────────────────┐
▼ ▼ ▼
[Worker Node 1] [Worker Node 2] [Worker Node 3]
│ │ │
└────┬────────────────────────┴────────────────────────┘
│ (Atomic Claim / Mutex Lock Lua)
▼
[API d'Orchestration Serveur] ──► Démarrage d'instances Agones / Fleets
1. Lock Contention sur les tickets distribués
Lorsque plusieurs processus de matchmaking parallèles scrutent le même pool de tickets centralisé, des race conditions surviennent inévitablement. Deux threads workers distincts pourraient évaluer simultanément le Ticket #1042 et tenter de l'associer à deux lobbies complètement différents.
Pour résoudre ce problème, les développeurs doivent exécuter des verrous atomiques (atomic lock claims) à l'aide de primitives distribuées (telles que des scripts Lua Redis ou des opérations atomiques en grille mémoire) avant d'émettre une assignation de match. Si un ticket est verrouillé par un autre worker, le thread libère immédiatement son état et effectue un rollback.
2. Communication temps réel haute fréquence
Les mises à jour du statut de matchmaking ne peuvent pas reposer sur du polling HTTP standard sans consommer des ressources système considérables en handshakes réseau inutiles. Pour informer les clients des estimations de temps d'attente dans la queue et des recherches de ping dynamique, les backends doivent maintenir des connexions persistantes via des WebSockets bicanaux ou des flux gRPC longue durée.
Si vous vous appuyez actuellement sur du polling inefficace pour gérer l'état de votre jeu multijoueur, découvrez notre guide pour abandonner le polling HTTP au profit des WebSockets Unreal Engine dans les backends temps réel.
3. Handshakes d'allocation de serveur & Provisioning de Fleet
Associer des joueurs ne représente que la moitié du travail. Une fois un groupe de tickets valide formé :
- Le matchmaker contacte un orchestrateur de dedicated servers (ex. Agones, contrôleurs Kubernetes sur mesure).
- Une instance de serveur de jeu propre doit être réservée ou allouée dans le datacenter sélectionné avec un délai très strict (généralement $< 1500ms$).
- Le serveur démarre, lie son port d'écoute UDP et renvoie le payload contenant son adresse IP et son port.
- Le matchmaker transmet les détails de connexion à tous les WebSockets des clients concernés.
Concevoir des pools de tickets distribués, gérer la lock contention, administrer des clusters de sockets régionaux et orchestrer le cycle de vie des dedicated servers nécessite des mois de développement d'infrastructure.
C'est là que horizOn élimine cette lourde charge d'ingénierie. Au lieu d'assembler des bases Redis pour vos tickets, d'écrire des wrappers pour Agones et de concevoir des scripts de scaling pour vos fleets, horizOn fournit des queues de matchmaking ultra-rapides et entièrement gérées, ainsi qu'une orchestration de serveurs clé en main. Vous définissez vos règles de matchmaking ; horizOn gère la distribution globale, le verrouillage atomique des tickets et l'allocation automatique des serveurs de manière transparente.
5 bonnes pratiques pour concevoir une architecture de matchmaking moderne
Que vous développiez un jeu de tir compétitif à fort enjeu ou un titre arcade indépendant, appliquez ces principes d'architecture éprouvés :
1. Exigez les vecteurs de ping client avant la soumission du ticket
Ne vous fiez jamais à la géolocalisation IP des clients pour déterminer leur proximité avec vos datacenters. Les bases de données Geo-IP sont notoirement imprécises pour le routage edge et ne prennent pas en compte la congestion temps réel des FAI. Forcez toujours le client de jeu à mesurer la latence directe via des requêtes UDP vers l'ensemble des endpoints régionaux avant d'appeler l'endpoint d'entrée en queue.
2. Protégez-vous contre la fragmentation des pools
Évitez de créer des queues séparées pour des modes de jeu secondaires, à moins que votre base de joueurs actifs ne le justifie clairement. Séparer vos joueurs selon les modes de jeu, les préférences de cartes et la sévérité des règles de matchmaking accélère la dégradation du pool. Si le nombre de CCU actifs par queue tombe en dessous de 1 000 joueurs dans une région, basculez automatiquement vers un mode de fallback dynamique à queue unique.
3. Découplez le provisioning de serveurs de l'évaluation du matchmaking
Assurez-vous que les threads de votre matchmaker fonctionnent de manière asynchrone par rapport à votre couche d'orchestration de serveurs. Ne bloquez jamais une boucle de worker du matchmaker en attendant le démarrage d'une instance virtuelle. Utilisez des files d'attente de messages pub/sub non bloquantes pour demander les allocations de serveurs et livrer les paramètres de connexion une fois que les instances confirment leur état opérationnel (healthy).
4. Optimisez les coûts des serveurs inactifs grâce à l'hibernation intelligente
L'affluence dans le matchmaking varies de façon imprévisible, avec des pics aux heures de pointe et des chutes brutales en heures creuses. Laisser des centaines d'instances de serveurs tourner à vide représente un gaspillage financier considérable. Mettez en place des stratégies dynamiques de préchauffage (warm-up) et d'hibernation pour vos fleets. Pour une analyse technique approfondie sur l'optimisation des serveurs inactifs, lisez notre article sur l'architecture de serveurs sans gaspillage et les propositions d'optimisation basées sur Fortnite.
5. Testez votre netcode et la réplication en cas de forte latence
Même l'architecture de matchmaking la plus avancée sera parfois amenée à associer des joueurs avec une latence modérée ($100-120ms$) pendant les heures creuses. Veillez à ce que le netcode de votre serveur s'appuie sur une prédiction client rigoureuse, de la compensation de lag et de la réconciliation d'état pour absorber sereinement les retards de paquets. Si la position des joueurs saccade ou téléporte lors de parties à ping élevé, consultez notre guide sur la correction de la désynchronisation de position des joueurs dans le multijoueur Unreal Engine.
Résumé et prochaines étapes
La décision d'Activision de proposer des queues SBMM, Connection-First et Hybrides dans Call of Duty: Black Ops 7 illustre à quel point l'architecture de matchmaking est cruciale pour la satisfaction des joueurs. Cependant, la division des queues exige une très forte densité de joueurs, un réseau de sockets à faible latence, des algorithmes de dégradation dynamique des règles et une gestion atomique des tickets.
Si vous développez votre prochain jeu multijoueur, ne perdez pas des mois à construire votre infrastructure backend, vos serveurs de sockets et votre logique d'allocation de serveurs à partir de zéro. Découvrez comment horizOn permet aux développeurs de déployer des backends multijoueurs scalables, un matchmaking automatisé et une orchestration de serveurs en quelques minutes. Essayez horizOn gratuitement dès aujourd'hui ou explorez la documentation horizOn pour accélérer le développement de votre backend.