Cache des transactions et des résultats de jeu : approches et risques
1) Pourquoi cacheter et où il est vraiment nécessaire
Cache est un outil pour réduire la latence et la charge sur le noyau. Dans iGaming, c'est critique pour :- Lire les bilans et les états de transaction (requêtes GET fréquentes) ;
- Historique des jeux/spins et des agrégats (haut de gamme, derniers N résultats) ;
- Métadonnées des jeux/fournisseurs, limites de mise, annuaires statiques ;
- Fid de coefficients et de références « rapides » pour UX (bannières, statuts promotionnels).
Mais le cache n'est jamais une source de vérité pour l'argent et les exodes. La vérité est le ledger/portefeuille et les résultats confirmés du fournisseur.
2) Ligne rouge : ce qui ne peut pas être caché
Enregistrement de l'argent : débiter/créditer le solde (opérations d'enregistrement) - uniquement par l'intermédiaire de la base de données/ledger avec les transactions et l'idempotence.
Solutions de pari/gain avant confirmation du fournisseur.
KYC/AML et drapeaux de conformité affectant les paiements.
Secrets/tokens (cache dans la mémoire du processus est acceptable, mais pas cache partagé).
3) Modèles de base de cache
Cache-aside (lazy) : l'application recherche d'abord dans le kesh, si elle échoue - lit de la base de données et met dans le kesh ('get → miss → load → set'). Polyvalent et sûr à lire.
Write-through : l'enregistrement dans l'OBD passe par le cache ; assure la pertinence de la clé, mais augmente la latence de l'enregistrement.
Write-behind (write-back) : écriture d'abord dans le cache, puis asynchrone dans la base de données. Interdit pour l'argent/les résultats - risque de perte en cas de chute.
Read-through : kesh lui-même sait comment sortir de la base de données (proxy kesh, par exemple, Redis avec modules/sidecar). Bon pour les métadonnées.
Recommandation : cache-aside pour les lectures, write-through seulement là où c'est sûr, write-behind - jamais pour les vérités argent/jeu.
4) Consistance et idempotence
Source de vérité : ledger (append-only), opérations avec 'opération _ id' et traitement idempotent.
Équilibre : nous lisons à partir du cache, mais nous confirmons toute divergence à partir de la base avant les actions critiques (dépôt/retrait/gros pari).
Invalidité : lorsque les clés de solde/statut correspondantes sont enregistrées avec succès dans l'OBD → del/expire.
Déduplication : outbox/inbox + clés d'identité pour les webhooks/paiements ; kesh ne participe pas au dedup, il ne fait qu'accélérer la lecture.
5) TTL, handicap et « droit à l'obsolescence »
Short-TTL pour l'équilibre : 1-5 secondes (ou soft-TTL avec refresh arrière-plan).
Statuts des transactions : court TTL (5-30 s) avec un handicap actif par événement (« deposit _ completed », « settled »).
Historique des jeux : TTL 1-10 minutes, handicapé par l'événement « nouveau _ round ».
Métadonnées/guides : TTL 10-60 minutes, warm-up à la dérive.
Handicap d'événement : le bus d'événement (Kafka/PubSub) publie « wallet _ updated », « bet _ settled », « bonus _ changed » → les abonnés suppriment/mettent à jour les clés.
6) Modèles anti-schtorm (tempête de défauts et dogon)
Request coalescing : un flux « conduit » la requête à la base, les autres attendent (mutex per key).
Stale-while-revalidate : Nous donnons « légèrement obsolète », en parallèle, nous mettons à jour dans le fond.
Jitter pour TTL : randomiser la TTL (± 20 %) afin que les clés n'expirent pas en même temps.
Back-off sur les échecs : en cas de bogues/erreurs persistantes, un non-cache temporaire (voir ci-dessous).
7) Non-caching et le cardinal gris des erreurs
Pour « introuvable » (par exemple, pas encore de statut de transaction) - un court TTL negative 1-3 s.
Ne faites pas d'erreurs OBD/fournisseur plus de quelques secondes - sinon fixez l'accident.
Entrez les clés de canary pour l'observabilité : l'augmentation de la proportion de negative-hits est une raison d'alerte.
8) Structure des clés et segmentation
Именование: `wallet:{userId}`, `txn:{txnId}:status`, `game:{provider}:{tableId}:last_results`, `leaderboard:{tournamentId}:top100`.
Segments/neimspace par bou/region/brand : 'prod : eu : wallet : {userId}' - exclure les intersections et les débris croisés.
Limitez la cardinalité - surtout pour les leaders et l'histoire.
9) Cache sur edge, en cluster et en mémoire
Edge-cache (CDN/WAF) : uniquement pour les données non personnelles (métadonnées de jeu, leaders publics, médias). Les paramètres de requête sont whitelist ; protection contre le cache-busting.
Redis/Memcached (cluster) : base pour les lectures personnelles ; inclure les snapshots AOF/RDB, les répliques et les quotas.
Cache en process : accès microseconde pour les guides chauds ; des mécanismes d'invalidité (broadcast, version clé) sont nécessaires.
10) Cash Case : accélérations sûres
Équilibre du joueur
Lecture : cache-aside avec TTL 1-5 s.
Enregistrement : transaction dans la base de données → del cache du bilan ; en action critique (retrait/gros taux) - « recheck from DB ».
Anti-course : version de verrouillage optimiste de l'équilibre.
Statut du paiement
Scénario : l'utilisateur clique sur « mettre à jour le statut ».
Solution : cache-aside + TTL negative sur « pending « / » unknown »2-5 s ; La rénovation selon вебхуку PSP → инвалидация.
Bonus/Wager
Agrégats (progrès en %) : nous cachons 10-30 s ; invalidité par événement 'bet _ placed/settled'.
11) Mallettes de jeu : front rapide sans déformation de la vérité
Historique des spins/paris
Derniers N événements : liste de cache avec restriction (par exemple 100), TTL 1-10 min, remplissage par événement « round _ finished ».
Vous ne pouvez pas afficher de « gain » tant qu'il n'y a pas de confirmation du fournisseur → le statut intermédiaire « pending ».
Jeux Live (WebSocket)
Cache à court terme des derniers messages/état de la table à 1-3 s pour les clients connectés rapidement.
Segmentez les clés de state par 'tableId/market'.
Liderboards
Precompute + cache de 10 à 60 s ; pour les updates de masse - les rafraîchissements de batch et l'invalidité partielle des « fenêtres ».
12) Risques et comment les fermer
Double dépréciation/gains fantômes : lecture uniquement à partir de la cache ; tous les prélèvements/encours - par l'intermédiaire de l'OBD et de l'idempotence.
Les anciennes données → un litige avec le joueur : TTL courts, « réalité stricte » avant paiement, statuts transparents (« en attente de confirmation »).
Split-brein cluster kesh : quorum/sentinelle, timeouts, refus de write-behind.
Cache stampede sur clés chaudes : coalescing, jitter, stale-while-revalidate.
Cash-injection/poisoning : clés strictes, signatures/signature pour les réponses API cachées, vérifications canaries.
Vie privée/PII : cryptage de canal (mTLS), interdiction de la cache sur le bord pour les données personnelles, TTL courts, nettoyage dans le logout.
13) L'observation de la cache
Métriques pour chaque couche :- Hit/Miss ratio par catégorie de clés ; redis_ops/sec, latency p95/p99, evictions, memory_usage.
- Clés Canaries : 'cache _ health : {segment}' - Vérifie la proportion de cache negative et l'heure de mise à jour.
- Logs : défauts de « paquets », fréquents « del » un segment à la fois = signe d'un service « bruyant ».
- Traces : spans « cache get/set/del » avec clés en main (sans PII).
14) Mini-architecture (référence)
1. Application (API/WS) → cluster Redis (TLS, auth).
2. Source de vérité : Wallet DB (ledger), Game results store.
3. Bus d'événements : 'wallet _ updated', 'bet _ settled', 'promo _ changed'.
4. Invalide : abonné aux événements → 'del '/' set' des clés chaudes.
5. Edge-cache : ressources publiques/leaders uniquement.
6. Observabilité : Dashboards de Cash, alertes par stampede, succès négatifs.
15) Politiques de TTL (matrice approximative)
16) Exemple de pseudo-code (lecture sûre du bilan)
python def get_balance(user_id):
key = f"wallet:{user_id}"
bal = cache. get(key)
if bal is not None:
bal de retour : nous prenons de la BD et mettons avec un court TTL + jitter bal = db. get_wallet_balance(user_id)
cache. set(key, bal, ttl=randint(1,5))
return bal
def apply_transaction(op_id, user_id, delta):
enregistrement atomique en OBD avec idempotence if db. exists_op(op_id):
return db. get_result(op_id)
res = db. apply_ledger (op_id, user_id, delta) # transaction cache. delete (f « wallet : {user _ id} ») # invalidation return res17) Chèque-liste de production-prêt
- Distinction claire : la vérité est dans la base de données, le cache est seulement pour les lectures.
- Patterns : cache-aside pour les lectures ; write-behind est interdit.
- Handicap d'événement : 'wallet _ updated', 'bet _ settled', 'promo _ changed'.
- Court TTL + jitter ; negative-cache ≤ 3 с.
- Antistorme : coalescing, stale-while-revalidate.
- Segmentation des clés par bou/région/marque ; limite de cardinalité.
- Observabilité : hit/miss, evictions, p95, alertes sur stampede/negative-spikes.
- Edge-cache uniquement pour les données publiques ; personnel - uniquement dans Redis/TLS.
- Runbook : Que faire en cas de dissynchrone (refresh forcé, désactivation temporaire de la cache du segment).
- Tests réguliers : charge sur les clés chaudes, exercices de stampede.
Résumé
Kesh dans iGaming est un accélérateur de lecture, pas une « deuxième base de données pour l'argent ». Gardez la vérité dans le ledger, assurez l'idempotence et l'invalidité d'événement, gardez les TTL courts et la mécanique antistorming, séparez les données edge-cache et personnelles, surveillez les métriques de cache. Vous obtiendrez ainsi un UX rapide sans « illusions de gain », doubles débits et problèmes réglementaires.
