I bot sono sopravvissuti all'outage di Yandex — anche la tua architettura backend di gioco dovrebbe sopravvivere
In breve
Scopri come rilevare la migrazione dei bot durante gli outage dei provider e proteggere l'architettura backend del tuo gioco con il failover.
La tua dashboard di monitoraggio mostra un calo del traffico del 40% da un giorno all'altro. Tiri un sospiro di sollievo — il DDoS deve essere finito. Ma l'analisi di Cloudflare sull'outage Yandex dell'ottobre 2023 ha dimostrato che questa ipotesi era catastroficamente sbagliata: i bot malintenzionati non sono morti quando i data center di Yandex sono andati in tilt. Sono migrati verso OVH, Hetzner e DigitalOcean in pochi minuti. Le tue metriche aggregate ti hanno mentito mentre gli attaccanti continuavano a operare.
Se il tuo backend di gioco gira su un unico cloud provider con bot detection basata su IP, sei a un solo evento infrastrutturale dallo stesso punto cieco. Questo runbook analizza cosa rivela l'outage di Yandex sul comportamento dei bot durante i guasti infrastrutturali e come progettare backend di gioco in grado di sopravvivere.
Cosa si rompe: quando il tuo provider di infrastruttura va in tilt
Quando un importante provider di infrastruttura subisce un outage, quattro sistemi critici falliscono contemporaneamente — e falliscono in modi che sembrano miglioramenti a un monitoraggio ingenuo.
Il tuo layer di autenticazione va offline. Se l'autenticazione dei tuoi giocatori dipende dal servizio di identità di un unico provider, i giocatori non possono accedere. Durante l'outage di Yandex, i servizi strettamente accoppiati all'infrastruttura Yandex hanno subito un'interruzione completa del servizio. I giocatori vedevano schermate di errore; i bot che non si autenticano hanno continuato a colpire endpoint altrove.
La tua bot detection perde il suo frame di riferimento. La maggior parte dei backend di gioco traccia gli IP malintenzionati per ASN o hosting provider. Quando quegli IP vanno offline, i tuoi sistemi di detection li marcano come "risolti" e smettono di controllarli. Nel frattempo, la stessa flotta di bot riappare su nuovi IP presso un altro provider.
Il tuo rate limiting diventa inutile. I rate limit basati su IP sono legati a specifici range di indirizzi. Quando i bot migrano, ottengono nuovi IP con reputazione pulita. Il tuo rate limiter li tratta come nuovo traffico legittimo.
I tuoi aggregati di traffico diventano disinformazione. L'analisi di Cloudflare su Yandex ha mostrato che il traffico AS13238 (Yandex Search) era composto quasi interamente da crawler legittimi — il calo lì era reale. Ma AS200350 (Yandex.Cloud) ha visto diminuire lo scanning e il probing di vulnerabilità malevoli, mentre l'automazione ad alto volume si è semplicemente trasferita. I grafici di traffico aggregato che mostravano un calo del 99% ti raccontavano la storia sbagliata.
La modalità di guasto principale: il tuo monitoraggio pensa che le cose siano migliorate quando la minaccia si è solo spostata.
Come rilevarlo: individuare la migrazione dei bot in tempo reale
La detection richiede un monitoraggio su più livelli, non solo il volume di traffico aggregato. Ogni livello cattura un aspetto diverso del pattern di migrazione.
Livello 1: Correlazione delle sorgenti di traffico
Invece di tracciare il totale delle richieste al secondo, monitora la distribuzione delle sorgenti di traffico per autonomous system number (ASN). Quando il traffico di un provider cala, verifica se il traffico di altri provider aumenta proporzionalmente. Un pattern correlato di calo e aumento tra ASN è la firma della migrazione dei bot.
Ecco un detector Python che identifica questo pattern:
import time
from collections import defaultdict
from dataclasses import dataclass
from typing import Dict, List, Optional
@dataclass
class TrafficSnapshot:
timestamp: float
source_asn: str
request_count: int
unique_ips: int
error_rate: float
class BotMigrationDetector:
"""Detects correlated traffic drops and rises across ASNs
to identify bot farm migration during infrastructure failures."""
def __init__(self, migration_threshold: float = 0.3):
self.history: Dict[str, List[TrafficSnapshot]] = defaultdict(list)
self.migration_threshold = migration_threshold
self.window_seconds = 300 # 5-minute rolling window
def record_snapshot(self, snapshot: TrafficSnapshot):
self.history[snapshot.source_asn].append(snapshot)
self._prune_old_data()
def _prune_old_data(self):
cutoff = time.time() - self.window_seconds
for asn in self.history:
self.history[asn] = [
s for s in self.history[asn]
if s.timestamp > cutoff
]
def detect_migration(self) -> Optional[Dict]:
"""Returns migration report if suspicious pattern detected."""
declining_asns = []
rising_asns = []
for asn, snapshots in self.history.items():
if len(snapshots) < 2:
continue
mid = len(snapshots) // 2
first_half = snapshots[:mid]
second_half = snapshots[mid:]
avg_first = sum(s.request_count for s in first_half) / len(first_half)
avg_second = sum(s.request_count for s in second_half) / len(second_half)
if avg_first > 0:
change_ratio = (avg_second - avg_first) / avg_first
if change_ratio < -self.migration_threshold:
declining_asns.append({
'asn': asn,
'drop_pct': abs(change_ratio) * 100,
'baseline': avg_first
})
elif change_ratio > self.migration_threshold:
rising_asns.append({
'asn': asn,
'rise_pct': change_ratio * 100,
'current': avg_second
})
if declining_asns and rising_asns:
return {
'type': 'bot_migration',
'declining': declining_asns,
'rising': rising_asns,
'confidence': self._calculate_confidence(declining_asns, rising_asns)
}
return None
def _calculate_confidence(self, declining, rising) -> float:
"""Higher confidence when drop and rise magnitudes match."""
total_drop = sum(d['drop_pct'] for d in declining)
total_rise = sum(r['rise_pct'] for r in rising)
if total_drop == 0:
return 0.0
ratio = min(total_rise, total_drop) / max(total_rise, total_drop)
return round(ratio * 100, 2)
# Usage: detect Yandex-style migration
detector = BotMigrationDetector(migration_threshold=0.25)
# Yandex traffic collapsing
detector.record_snapshot(TrafficSnapshot(
timestamp=time.time() - 240,
source_asn="AS13238", request_count=15000,
unique_ips=8500, error_rate=0.02
))
detector.record_snapshot(TrafficSnapshot(
timestamp=time.time() - 120,
source_asn="AS13238", request_count=1200, # 92% drop
unique_ips=900, error_rate=0.45
))
# OVH traffic surging (bots migrated here)
detector.record_snapshot(TrafficSnapshot(
timestamp=time.time() - 240,
source_asn="AS16276", request_count=5000,
unique_ips=3200, error_rate=0.01
))
detector.record_snapshot(TrafficSnapshot(
timestamp=time.time() - 120,
source_asn="AS16276", request_count=18500, # 270% increase
unique_ips=2800, error_rate=0.08
))
migration = detector.detect_migration()
if migration:
print(f"Bot migration detected: {migration['confidence']}% confidence")
for d in migration['declining']:
print(f" {d['asn']}: -{d['drop_pct']:.0f}%")
for r in migration['rising']:
print(f" {r['asn']}: +{r['rise_pct']:.0f}%")
Questo detector osserva i cali e gli aumenti di traffico correlati tra più ASN. Quando AS13238 cala del 92% e AS16276 aumenta contemporaneamente del 270%, segnala una probabile migrazione di bot invece di un legittimo cambiamento del traffico. Il punteggio di confidenza aumenta quando le magnitudini del calo e dell'aumento coincidono — un segno distintivo della migrazione coordinata.
Livello 2: Behavioral Fingerprinting
Non limitarti a tracciare da dove arriva il traffico — traccia come si comporta. I bot che migrano tra provider mantengono firme comportamentali identiche: gli stessi intervalli di richiesta, pattern di header, durate di sessione e comportamento di retry sugli errori. La reputazione IP si azzera quando si spostano; il comportamento no.
Costruisci un fingerprint comportamentale per ogni sessione e applica un hash:
import hashlib
import statistics
def fingerprint_session(requests: list[dict]) -> str:
"""Generate a behavioral fingerprint from a session's request pattern."""
intervals = []
for i in range(1, len(requests)):
delta = requests[i]['timestamp'] - requests[i-1]['timestamp']
intervals.append(round(delta, 3))
if len(intervals) < 2:
return "insufficient_data"
# Key behavioral features
interval_mean = round(statistics.mean(intervals), 3)
interval_stdev = round(statistics.stdev(intervals), 3)
total_duration = round(requests[-1]['timestamp'] - requests[0]['timestamp'], 3)
error_count = sum(1 for r in requests if r.get('status_code', 200) >= 400)
retry_count = sum(1 for r in requests if r.get('is_retry', False))
unique_paths = len(set(r['path'] for r in requests))
user_agent_count = len(set(r.get('user_agent', '') for r in requests))
fingerprint_data = (
f"{interval_mean}|{interval_stdev}|{total_duration}|"
f"{error_count}|{retry_count}|{unique_paths}|{user_agent_count}"
)
return hashlib.sha256(fingerprint_data.encode()).hexdigest()[:16]
# Example: same bot session detected at two different providers
session_provider_a = [
{'timestamp': 1000.0, 'path': '/api/login', 'status_code': 200},
{'timestamp': 1000.5, 'path': '/api/profile', 'status_code': 200},
{'timestamp': 1001.0, 'path': '/api/match/join', 'status_code': 429},
{'timestamp': 1001.5, 'path': '/api/match/join', 'status_code': 200, 'is_retry': True},
{'timestamp': 1002.0, 'path': '/api/score', 'status_code': 200},
]
session_provider_b = [
{'timestamp': 2000.0, 'path': '/api/login', 'status_code': 200},
{'timestamp': 2000.5, 'path': '/api/profile', 'status_code': 200},
{'timestamp': 2001.0, 'path': '/api/match/join', 'status_code': 429},
{'timestamp': 2001.5, 'path': '/api/match/join', 'status_code': 200, 'is_retry': True},
{'timestamp': 2002.0, 'path': '/api/score', 'status_code': 200},
]
fp_a = fingerprint_session(session_provider_a)
fp_b = fingerprint_session(session_provider_b)
print(f"Provider A fingerprint: {fp_a}")
print(f"Provider B fingerprint: {fp_b}")
print(f"Same bot: {fp_a == fp_b}")
Quando vedi lo stesso fingerprint comportamentale apparire su nuovi IP presso un provider diverso poco dopo che il provider originale è andato giù, quella è migrazione confermata — non coincidenza.
Livello 3: Monitoraggio dello stato di autenticazione
Traccia il rapporto tra richieste autenticate e non autenticate nel tempo. Durante i guasti infrastrutturali, i giocatori legittimi faticano ad autenticarsi (spostando il rapporto verso il non autenticato), mentre i bot che non si autenticano mai continuano a colpire i tuoi endpoint senza variazioni.
Questo livello è particolarmente prezioso perché separa i giocatori reali dal traffico automatizzato indipendentemente dal provider di origine. Se stai usando le sessioni account-bound di horizOn con più metodi di autenticazione (email, Google, Apple, anonimo), puoi monitorare i tassi di successo dell'autenticazione per metodo. Quando l'autenticazione via email fallisce ma quella via Google funziona, hai identificato un guasto infrastrutturale parziale — non una migrazione di bot. Quando tutta l'autenticazione funziona ma il traffico non autenticato impenna verso un nuovo provider, quella è migrazione.
Come intervenire: strategie di failover che funzionano
La detection senza remediation significa solo guardare il tuo gioco morire in tempo reale. Ecco tre meccanismi di failover che puoi implementare questa settimana.
Failover basato su DNS
Configura il tuo DNS con TTL brevi e health check. Quando la tua regione primaria fallisce, il DNS instrada automaticamente il traffico verso le regioni secondarie. Non richiede modifiche all'applicazione.
Impostazioni consigliate:
- TTL: 60-120 secondi (più corto significa failover più rapido, ma più carico DNS)
- Intervallo health check: 10-15 secondi
- Endpoint health check:
/healthche risponde 200 con tempo di risposta < 500ms - Trigger di failover: 3 fallimenti consecutivi degli health check
Il tradeoff: TTL brevi aumentano il volume di query DNS di 10-20x durante il normale funzionamento. Per un gioco con 50.000 giocatori attivi al giorno, questo aggiunge circa $2-5/mese in costi DNS — trascurabile rispetto al downtime che previene.
Circuit breaker a livello di applicazione
Implementa circuit breaker nel tuo game client che rilevano quando un servizio sta fallendo e instradano automaticamente verso le alternative:
using System;
using System.Net.Http;
using System.Threading.Tasks;
public class ServiceCircuitBreaker
{
private readonly string _primaryEndpoint;
private readonly string[] _fallbackEndpoints;
private readonly int _failureThreshold;
private int _failureCount;
private DateTime _lastFailure;
private CircuitState _state = CircuitState.Closed;
private int _currentFallbackIndex;
private readonly object _lock = new();
public enum CircuitState { Closed, Open, HalfOpen }
public ServiceCircuitBreaker(
string primary, string[] fallbacks, int failureThreshold = 5)
{
_primaryEndpoint = primary;
_fallbackEndpoints = fallbacks;
_failureThreshold = failureThreshold;
}
public async Task<HttpResponseMessage> SendRequest(HttpRequestMessage request)
{
lock (_lock)
{
if (_state == CircuitState.Open)
{
if (DateTime.UtcNow - _lastFailure > TimeSpan.FromSeconds(30))
_state = CircuitState.HalfOpen;
else
return SendToFallback(request).GetAwaiter().GetResult();
}
}
try
{
using var client = new HttpClient();
client.Timeout = TimeSpan.FromSeconds(5);
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
lock (_lock) { _failureCount = 0; _state = CircuitState.Closed; }
return response;
}
RecordFailure();
return await SendToFallback(request);
}
catch (TaskCanceledException)
{
RecordFailure();
return await SendToFallback(request);
}
}
private void RecordFailure()
{
lock (_lock)
{
_failureCount++;
_lastFailure = DateTime.UtcNow;
if (_failureCount >= _failureThreshold)
_state = CircuitState.Open;
}
}
private async Task<HttpResponseMessage> SendToFallback(HttpRequestMessage request)
{
for (int i = 0; i < _fallbackEndpoints.Length; i++)
{
int idx = (_currentFallbackIndex + i) % _fallbackEndpoints.Length;
try
{
var fallbackRequest = new HttpRequestMessage(
request.Method,
_fallbackEndpoints[idx] + request.RequestUri.PathAndQuery
);
using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };
var response = await client.SendAsync(fallbackRequest);
if (response.IsSuccessStatusCode)
{
_currentFallbackIndex = idx;
return response;
}
}
catch { continue; }
}
throw new HttpRequestException("All endpoints failed");
}
}
// Usage in your game client
var breaker = new ServiceCircuitBreaker(
primary: "https://api-primary.game.example.com",
fallbacks: new[] {
"https://api-secondary.game.example.com",
"https://api-tertiary.game.example.com"
},
failureThreshold: 3
);
var request = new HttpRequestMessage(HttpMethod.Get, "/api/leaderboard/global");
var response = await breaker.SendRequest(request);
Questo pattern previene i guasti a cascata quando un provider va giù. Come abbiamo visto nella nostra analisi su come progettare i fallback del server, il failover automatico è la differenza tra un intoppo di 30 secondi e un outage di 4 ore.
Rate limiting distribuito
Quando il traffico viene deviato verso una nuova regione, anche il tuo rate limiting deve funzionare lì. I rate limit basati su IP, locali a ogni server, diventano inutili durante il failover perché lo stesso bot riparte da zero su ogni endpoint.
Implementa un rate limiting distribuito usando uno store condiviso accessibile da tutte le regioni:
import redis
import time
class DistributedRateLimiter:
"""Sliding window rate limiter backed by Redis.
Works identically across all deployment regions."""
def __init__(self, redis_url: str, max_requests: int = 100,
window_seconds: int = 60):
self.redis = redis.from_url(redis_url)
self.max_requests = max_requests
self.window = window_seconds
def is_allowed(self, identifier: str) -> tuple[bool, int]:
"""Returns (allowed, remaining_requests)."""
key = f"ratelimit:{identifier}"
now = time.time()
window_start = now - self.window
pipe = self.redis.pipeline()
pipe.zremrangebyscore(key, 0, window_start) # Remove expired entries
pipe.zadd(key, {f"{now}:{id(identifier)}": now}) # Add current request
pipe.zcard(key) # Count requests in window
pipe.expire(key, self.window)
results = pipe.execute()
request_count = results[2]
remaining = max(0, self.max_requests - request_count)
if request_count > self.max_requests:
# Remove the request we just added — it's over limit
pipe.zremrangebyscore(key, now, now)
pipe.execute()
return False, 0
return True, remaining
# Usage: same limiter works from any region
limiter = DistributedRateLimiter(
redis_url="redis://shared-redis.internal:6379",
max_requests=60, # 1 request per second average
window_seconds=60
)
# Identify by player ID, not IP — survives migration
allowed, remaining = limiter.is_allowed("player:8f3a2b1c")
Nota che l'identificatore usa l'ID giocatore, non l'indirizzo IP. I bot che migrano su nuovi IP mantengono lo stesso fingerprint del giocatore. Questo approccio li intercetta anche dopo la migrazione a livello di provider.
Come prevenire: pattern architetturali per la resilienza
La remediation è reattiva. La prevenzione significa costruire un'architettura che parte dal presupposto che il guasto infrastrutturale accadrà — perché accadrà.
Pattern 1: Deployment multi-provider per i servizi critici
Distribuisci autenticazione, matchmaking e bot detection su almeno due cloud provider. Questo non significa eseguire l'intero stack due volte — significa che i servizi che devono sopravvivere a un outage devono essere indipendenti dal provider.
Realtà dei costi: il deployment multi-provider per i servizi critici aggiunge in genere il 30-50% alla bolletta dell'infrastruttura. Per un backend di gioco con 10.000 giocatori concorrenti, si parla di circa $800-1.500/mese in più. Il costo di un outage di 2 ore per lo stesso gioco — considerando il mancato ricavo dagli acquisti in-app (IAP), il churn dei giocatori e i ticket di supporto — è in media di $5.000-12.000.
Multi-provider minimo praticabile:
- Autenticazione: provider primario + secondario
- Dati giocatore / stato di salvataggio: replicati tra i provider
- Matchmaking: almeno una regione di fallback
- Classifiche e analytics: possono tollerare brevi outage (coda e replay)
Pattern 2: Bot detection stateless e indipendente dal provider
Progetta la tua bot detection in modo che funzioni in modo identico indipendentemente dall'origine del traffico. L'outage di Yandex ha dimostrato che la detection basata su IP è fondamentalmente fragile — i bot ottengono nuovi IP nel momento esatto in cui migrano.
Costruisci la detection attorno a feature comportamentali che sopravvivono ai cambiamenti infrastrutturali:
from dataclasses import dataclass
from typing import List
@dataclass
class SessionFeatures:
request_count: int
duration_seconds: float
interval_variance: float # Low variance = bot-like regularity
error_retry_rate: float # Bots retry aggressively; humans quit
unique_endpoints: int # Bots hit predictable endpoint patterns
header_consistency: float # Bots reuse identical headers
class BehavioralClassifier:
"""Bot classifier based on behavioral features, not IP addresses.
Survives provider migration because behavior doesn't change."""
# Weights calibrated from production bot traffic analysis
WEIGHTS = {
'interval_variance': 0.30, # Most discriminating feature
'error_retry_rate': 0.25,
'header_consistency': 0.20,
'endpoint_diversity': 0.15,
'session_duration': 0.10,
}
def classify(self, features: SessionFeatures) -> float:
"""Returns bot probability from 0.0 (human) to 1.0 (bot)."""
score = 0.0
# Low interval variance → bot (humans are irregular)
if features.interval_variance < 0.1:
score += self.WEIGHTS['interval_variance']
elif features.interval_variance < 0.3:
score += self.WEIGHTS['interval_variance'] * 0.5
# High retry rate → bot (humans give up after 1-2 failures)
if features.error_retry_rate > 0.8:
score += self.WEIGHTS['error_retry_rate']
elif features.error_retry_rate > 0.5:
score += self.WEIGHTS['error_retry_rate'] * 0.5
# Perfect header consistency → bot (humans have browser variations)
if features.header_consistency > 0.95:
score += self.WEIGHTS['header_consistency']
# Very few unique endpoints → bot (humans explore)
if features.unique_endpoints <= 3 and features.request_count > 20:
score += self.WEIGHTS['endpoint_diversity']
# Suspiciously precise session duration (e.g., exactly 300s)
if abs(features.duration_seconds % 60) < 2:
score += self.WEIGHTS['session_duration']
return min(score, 1.0)
# Usage
classifier = BehavioralClassifier()
bot_session = SessionFeatures(
request_count=150, duration_seconds=300.0,
interval_variance=0.02, error_retry_rate=0.95,
unique_endpoints=2, header_consistency=0.99
)
human_session = SessionFeatures(
request_count=45, duration_seconds=1847.3,
interval_variance=0.67, error_retry_rate=0.15,
unique_endpoints=12, header_consistency=0.73
)
print(f"Bot score: {classifier.classify(bot_session):.2f}") # ~0.85
print(f"Human score: {classifier.classify(human_session):.2f}") # ~0.10
Questo classificatore non si cura dell'ASN da cui arriva la richiesta. Quando un bot migra da Yandex.Cloud a OVH, il suo fingerprint comportamentale rimane identico — e il classificatore lo intercetta comunque.
Pattern 3: Disaccoppia l'autenticazione dall'infrastruttura
Il tuo sistema di autenticazione dovrebbe sopravvivere a qualsiasi singolo guasto infrastrutturale. Usa più provider di autenticazione così i giocatori hanno opzioni di fallback per accedere ai loro account.
Implementa l'autenticazione con degradazione graduale:
class MultiProviderAuth:
"""Authentication with automatic provider fallback."""
def __init__(self, providers: list):
# Order by reliability — primary first
self.providers = providers
async def authenticate(self, credentials: dict) -> dict:
last_error = None
for provider in self.providers:
try:
result = await provider.authenticate(credentials)
return {
'success': True,
'provider': provider.name,
'player_id': result['player_id'],
'session_token': result['session_token']
}
except Exception as e:
last_error = e
continue
return {
'success': False,
'error': 'All authentication providers failed',
'last_error': str(last_error)
}
Questo pattern è importante perché durante l'outage di Yandex, i servizi strettamente accoppiati all'infrastruttura di identità Yandex non potevano autenticare gli utenti. I giocatori sono rimasti bloccati fuori; i bot che non si autenticano hanno continuato a girare. Una strategia di autenticazione multi-provider significa che i tuoi giocatori continuano a giocare anche quando il tuo provider di autenticazione primario è giù.
Pattern 4: Observability che copre più provider
Il tuo monitoraggio deve avere visibilità su tutti i provider contemporaneamente. Dividi il monitoraggio in ambiti separati:
Monitoraggio della distribuzione del traffico: traccia il volume di richieste per ASN, per regione, per provider. Genera un alert quando la distribuzione si sposta di oltre il 20% in una finestra di 5 minuti.
Monitoraggio della salute dell'autenticazione: traccia i tassi di successo dell'auth per metodo e per provider. Genera un alert quando il tasso di successo di un provider scende sotto il 95%.
Monitoraggio della bot detection: traccia il rapporto tra sessioni segnalate e totali per provider. Genera un alert quando il rapporto scende sotto la baseline presso un provider mentre sale presso un altro — quella è migrazione.
Correlazione cross-provider: questo è il pezzo critico. Le metriche dei singoli provider possono sembrare sane mentre l'intero sistema è sotto attacco. Crea dashboard che mostrano tutti i provider fianco a fianco, così puoi individuare i pattern di migrazione a colpo d'occhio.
Best practice: 5 passi per un'architettura backend di gioco resistente ai bot
Monitora la migrazione, non solo il volume. Configura una correlazione del traffico cross-provider che rilevi quando il traffico cala in un punto e aumenta in un altro. Le sole metriche aggregate ti avrebbero ingannato durante l'outage di Yandex — il traffico AS200350 è calato, ma l'attività malevola è continuata su OVH e Hetzner.
Implementa circuit breaker su ogni dipendenza esterna. Il tuo game client, il servizio di classifiche e la logica di matchmaking hanno tutti bisogno di failover automatico. Il pattern circuit breaker previene i guasti a cascata quando un provider va giù. Testali mensilmente mettendo fuori uso gli endpoint in staging.
Usa la detection comportamentale, non quella basata su IP. L'analisi comportamentale (intervalli di richiesta, tassi di retry, pattern di header) funziona indipendentemente da dove sono ospitati i bot. La detection basata su IP fallisce nel momento in cui i bot migrano verso un nuovo provider con una reputazione IP pulita. Il classificatore comportamentale qui sopra intercetta i bot che sopravvivono a cali di traffico del 99%.
Progetta l'autenticazione per una degradazione graduale. Usa le sessioni account-bound di horizOn con più metodi di autenticazione (email, Google, Apple, anonimo). Quando un provider di autenticazione non è disponibile, i giocatori possono usare un altro metodo per mantenere l'accesso al loro stato di gioco. Questo previene lo scenario peggiore: giocatori bloccati fuori mentre i bot continuano a girare.
Testa il failover trimestralmente. Simula gli outage dei provider in staging. Verifica che il failover DNS scatti entro il TTL, che i circuit breaker si aprano correttamente e che il rate limiting funzioni nelle regioni di fallback. Documenta il tempo dalla rilevazione del guasto al pieno ripristino. Un failover non testato non è un failover — è speranza.
Conclusione
L'outage di Yandex ha dimostrato che gli attori malintenzionati hanno una resilienza infrastrutturale migliore della maggior parte dei backend di gioco. I bot non sono morti quando i data center di Yandex sono andati in tilt — sono migrati verso altri provider in pochi minuti e hanno continuato a operare. La tua dashboard di traffico aggregato mostrava un miglioramento. La realtà era peggiorata.
Inizia distribuendo il bot detector comportamentale di questo articolo nel tuo ambiente di staging. Eseguilo per una settimana per stabilire le baseline, poi simula l'outage di un provider bloccando il traffico da un ASN e osserva se il migration detector scatta. I pattern che scoprirai riveleranno esattamente dove la tua architettura attuale è vulnerabile.
Per gli studi di gioco che costruiscono sistemi di autenticazione e dati giocatore in grado di sopravvivere ai guasti infrastrutturali, horizOn offre sessioni account-bound con cloud save consapevole delle revisioni — servizi progettati affinché i tuoi giocatori conservino i progressi anche quando accade l'imprevisto. Provalo gratuitamente oppure esplora la documentazione API per vedere come le sessioni account-bound si integrano con il tuo backend esistente.
Fonte: Outage Yandex: i dati mostrano che invece di sparire, i bot malintenzionati si sono spostati