Contrôler ce qui entre.
Une file déplace le travail dans le temps, mais ne crée pas de capacité de traitement. Si les entrées dépassent durablement les sorties, elle finit par accumuler de la latence. J’ai donc placé un contrôle d’admission avant d’y ajouter une demande.
Mon ordonnanceur vérifie d’abord le plafond global : 3 000 demandes, en additionnant la file et les traitements actifs. Il contrôle ensuite la limite du joueur, fixée à 10 demandes en cours ou en attente. Le budget de débit est vérifié après ces deux limites. Une demande refusée pour saturation ne consomme donc pas de jeton à cet endroit.
Limite du joueur. Le contrôle s’arrête au premier motif de refus ; ces demandes ne consomment pas de jeton. 10 demandes de ce joueur sont désormais en attente ou en cours.
Autoriser une rafale sans ouvrir les vannes.
J’utilise un seau de jetons par joueur : une réserve de 18 unités qui se recharge à 9 unités par seconde, jusqu’à son plafond. Une opération ordinaire consomme une unité. La consommation et la récupération de munitions ont un coût de 0,25.
Je calcule la recharge au moment d’une demande, à partir du temps écoulé depuis la dernière mise à jour. Le joueur peut absorber une courte rafale s’il dispose de crédits, puis son débit reste soumis à la recharge. La limite de 10 demandes non terminées continue de s’appliquer en parallèle.
Lorsqu’une demande est refusée, mon traitement renvoie un motif et peut envoyer un snapshot de l’inventaire pour recaler l’interface. Le client dispose ainsi d’une réponse explicite, plutôt que d’attendre indéfiniment une opération qui ne partira pas.
PSEUDO-CODE EXPLICATIFjetons = min(18, jetons + secondesÉcoulées × 9)
coût = opération de munitions ? 0,25 : 1
si jetons < coût : refuser
sinon : consommer puis mettre en fileRéduire le travail avant de l’ordonnancer.
Pour la consommation de munitions, j’ai prévu un regroupement des demandes compatibles. Si une demande correspondante attend encore dans la file, je peux lui ajouter les tirs entrants, dans la limite de 250 tirs cumulés. Je ne modifie pas de cette façon une opération déjà sortie de la file.
Cette fusion se produit avant la soumission d’une nouvelle entrée à l’ordonnanceur. Les nouvelles demandes passent d’abord leurs validations, puis leur effet peut rejoindre le travail déjà en attente. Le gain recherché porte sur le nombre de traitements distincts à effectuer.
La base de données a aussi son rythme.
J’ai séparé la mutation en mémoire de sa persistance différée. Un inventaire modifié devient éligible à une sauvegarde après un intervalle configuré de 5 secondes ; un verrou occupé ou une erreur peut repousser ce moment. La boucle de sauvegarde planifie un flush à la fois.
Je regroupe les objets modifiés par lots de 50 pour les insertions ou mises à jour, et les suppressions par lots de 200. Certaines erreurs de transaction déclenchent jusqu’à trois tentatives. Si le flush échoue, je conserve le travail et espace les tentatives suivantes avec un délai exponentiel plafonné à 30 secondes.
Cette écriture différée implique un intervalle où le cache contient des changements qui ne sont pas encore durables. Je garde cette distinction dans mon architecture : une action acceptée en mémoire et une sauvegarde SQL terminée sont deux événements différents.
Le principePoursuivre l’explorationJe borne le débit, l’attente et l’exécution séparément. Une demande refusée reçoit une réponse ; elle ne s’accumule pas dans une file sans limite.