Bots overleefden de Yandex-storing — jouw game-backendarchitectuur zou dat ook moeten doen
Kort samengevat
Ontdek hoe jouw game-backend botmigratie overleeft tijdens infrastructuurstoringen en leer welke failoverstrategieën écht werken in de praktijk.
Je monitoringdashboard laat zien dat het verkeer 's nachts met 40% is gedaald. Je slaakt een zucht van verlichting — de DDoS is vast gestopt. Maar de analyse van Cloudflare van de Yandex-storing van oktober 2023 bewees dat deze aanname catastrofaal fout was: kwaadaardige bots stierven niet toen de datacenters van Yandex uitvielen. Ze migreerden binnen enkele minuten naar OVH, Hetzner en DigitalOcean. Je geaggregeerde metrics logen tegen je terwijl aanvallers gewoon doorliepen.
Als je game-backend draait op één cloudprovider met IP-gebaseerde botdetectie, ben je één infrastructuurincident verwijderd van dezelfde blinde vlek. Deze runbook legt uit wat de Yandex-storing onthult over botgedrag tijdens infrastructuurstoringen en hoe je game-backends ontwerpt die ze overleven.
Wat er kapotgaat: wanneer je infrastructuurprovider uitvalt
Wanneer een grote infrastructuurprovider een storing doormaakt, falen er tegelijkertijd vier kritieke systemen — en ze falen op manieren die er voor naïeve monitoring uitzien als verbeteringen.
Je authenticatielaag gaat offline. Als je spelersauthenticatie afhankelijk is van de identiteitsdienst van één provider, kunnen spelers niet inloggen. Tijdens de Yandex-storing zagen services die nauw gekoppeld waren aan de Yandex-infrastructuur een volledige onderbreking van de dienstverlening. Spelers kregen foutmeldingen; bots die niet authenticeren bleven elders endpoints bestoken.
Je botdetectie verliest zijn referentiekader. De meeste game-backends volgen kwaadaardige IP's op basis van ASN of hostingprovider. Wanneer die IP's uitvallen, markeren je detectiesystemen ze als "opgelost" en stoppen ze met zoeken. Ondertussen verschijnt dezelfde botvloot op nieuwe IP's bij een andere provider.
Je rate limiting wordt nutteloos. IP-gebaseerde rate limits zijn gekoppeld aan specifieke adresreeksen. Wanneer bots migreren, krijgen ze nieuwe IP's met een schone reputatiescore. Je rate limiter behandelt ze als nieuw legitiem verkeer.
Je verkeersaggregaten worden desinformatie. Cloudflares Yandex-analyse toonde aan dat het verkeer van AS13238 (Yandex Search) bijna volledig bestond uit legitieme crawlers — de daling daar was echt. Maar bij AS200350 (Yandex.Cloud) namen kwaadaardige kwetsbaarheidsscans en probes af, terwijl grootschalige automatisering simpelweg verhuisde. Geaggregeerde verkeersgrafieken die een daling van 99% lieten zien, vertelden je het verkeerde verhaal.
De kern van het falen: je monitoring denkt dat alles beter is geworden terwijl de dreiging alleen maar is verplaatst.
Hoe detecteer je: botmigratie in realtime herkennen
Detectie vereist monitoring op meerdere lagen, niet alleen het totale verkeersvolume. Elke laag vangt een ander aspect van het migratiepatroon.
Laag 1: correlatie van verkeersbronnen
Monitor in plaats van het totale aantal requests per seconde de verdeling van verkeersbronnen over autonome systeemnummers (ASN's). Wanneer het verkeer van één provider daalt, controleer dan of het verkeer van andere providers evenredig stijgt. Een gecorreleerd dalings- en stijgingspatroon over ASN's heen is de handtekening van botmigratie.
Hier is een Python-detector die dit patroon identificeert:
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}%")
Deze detector let op gecorreleerde verkeersdalingen en -stijgingen over meerdere ASN's. Wanneer AS13238 met 92% daalt en AS16276 tegelijkertijd met 270% stijgt, markeert hij waarschijnlijke botmigratie in plaats van legitieme verkeerswijzigingen. De betrouwbaarheidsscore stijgt wanneer de omvang van daling en stijging overeenkomen — een kenmerk van gecoördineerde migratie.
Laag 2: gedragsfingerprinting
Volg niet alleen waar verkeer vandaan komt — volg hoe het zich gedraagt. Bots die tussen providers migreren behouden identieke gedragskenmerken: dezelfde requestintervallen, headerpatronen, sessieduren en fout-retry-gedrag. IP-reputatie wordt gereset wanneer ze verhuizen; gedrag niet.
Bouw een gedragsfingerprint voor elke sessie en hash deze:
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}")
Wanneer je dezelfde gedragsfingerprint kort nadat de oorspronkelijke provider uitviel op nieuwe IP's bij een andere provider ziet verschijnen, is dat bevestigde migratie — geen toeval.
Laag 3: monitoring van authenticatiestatus
Volg de verhouding tussen geauthenticeerde en niet-geauthenticeerde requests in de tijd. Tijdens infrastructuurstoringen hebben legitieme spelers moeite met authenticeren (waardoor de verhouding verschuift naar niet-geauthenticeerd), terwijl bots die nooit authenticeren gewoon je endpoints blijven bestoken.
Deze laag is bijzonder waardevol omdat hij echte spelers scheidt van geautomatiseerd verkeer, ongeacht de bronprovider. Als je accountgebonden sessies van horizOn gebruikt met meerdere authenticatiemethoden (e-mail, Google, Apple, anoniem), kun je het authenticatiesucces per methode monitoren. Wanneer e-mailauthenticatie faalt maar Google-authenticatie slaagt, heb je een gedeeltelijke infrastructuurstoring geïdentificeerd — geen botmigratie. Wanneer alle authenticatie slaagt maar niet-geauthenticeerd verkeer piekt bij een nieuwe provider, is dat migratie.
Hoe verhelp je het: failoverstrategieën die werken
Detectie zonder herstel is gewoon toekijken hoe je game in realtime sterft. Hier zijn drie failovermechanismen die je deze week kunt implementeren.
DNS-gebaseerde failover
Configureer je DNS met korte TTL's en healthchecks. Wanneer je primaire regio uitvalt, routeert DNS automatisch verkeer naar secundaire regio's. Dit vereist geen wijzigingen aan de applicatie.
Aanbevolen instellingen:
- TTL: 60-120 seconden (korter betekent snellere failover, maar meer DNS-belasting)
- Healthcheck-interval: 10-15 seconden
- Healthcheck-endpoint:
/healthdat 200 retourneert met een responstijd < 500 ms - Failover-trigger: 3 opeenvolgende healthcheck-fouten
De afweging: korte TTL's verhogen het DNS-queryvolume tijdens normaal gebruik met 10-20x. Voor een game met 50.000 dagelijks actieve spelers kost dit ruwweg $2-5 per maand aan DNS-kosten — verwaarloosbaar vergeleken met de downtime die het voorkomt.
Circuit breakers op applicatieniveau
Implementeer circuit breakers in je gameclient die detecteren wanneer een service faalt en automatisch naar alternatieven routeren:
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);
Dit patroon voorkomt cascadestoringen wanneer één provider uitvalt. Zoals we behandelden in onze analyse van serverfallbacks ontwerpen, is automatische failover het verschil tussen een onderbreking van 30 seconden en een storing van 4 uur.
Gedistribueerde rate limiting
Wanneer verkeer overgaat naar een nieuwe regio, moet je rate limiting daar ook werken. IP-gebaseerde rate limits die lokaal zijn voor elke server worden nutteloos tijdens failover, omdat dezelfde bot bij elk endpoint een schone lei krijgt.
Implementeer gedistribueerde rate limiting met behulp van een gedeelde store die toegankelijk is vanuit alle regio's:
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")
Let op: de identifier gebruikt het spelers-ID, niet het IP-adres. Bots die naar nieuwe IP's migreren dragen nog steeds dezelfde spelerfingerprint. Deze aanpak vangt ze zelfs na migratie op provider-niveau.
Hoe voorkom je het: architectuurpatronen voor veerkracht
Herstel is reactief. Preventie betekent architectuur bouwen die ervan uitgaat dat infrastructuurstoringen zullen gebeuren — want dat zullen ze.
Patroon 1: multi-providerimplementatie voor kritieke services
Implementeer je authenticatie, matchmaking en botdetectie over ten minste twee cloudproviders. Dit betekent niet dat je je hele stack twee keer draait — het betekent dat de services die een storing moeten overleven provideronafhankelijk zijn.
Kostenrealiteit: Multi-providerimplementatie voor kritieke services voegt doorgaans 30-50% toe aan je infrastructuurrekening. Voor een game-backend die 10.000 gelijktijdige spelers bedient, is dat ruwweg $800-1.500 per maand extra. De kosten van een storing van 2 uur voor dezelfde game — rekening houdend met verloren IAP-inkomsten, spelersverloop en supporttickets — liggen gemiddeld tussen $5.000 en $12.000.
Minimaal haalbare multi-provider:
- Authenticatie: primaire + secundaire provider
- Spelerdata / opslagstatus: gerepliceerd over providers
- Matchmaking: ten minste één fallback-regio
- Leaderboards en analytics: kunnen korte storingen verdragen (in de wachtrij en replay)
Patroon 2: stateless, provideragnostische botdetectie
Ontwerp je botdetectie zodat deze identiek werkt, ongeacht waar verkeer vandaan komt. De Yandex-storing bewees dat IP-gebaseerde detectie fundamenteel fragiel is — bots krijgen nieuwe IP's op het moment dat ze migreren.
Bouw detectie rond gedragskenmerken die infrastructuurwijzigingen overleven:
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
Deze classifier maakt niet uit van welke ASN het request komt. Wanneer een bot migreert van Yandex.Cloud naar OVH, blijft zijn gedragsfingerprint hetzelfde — en de classifier vangt hem nog steeds.
Patroon 3: ontkoppel authenticatie van infrastructuur
Je authenticatiesysteem moet elke afzonderlijke infrastructuurstoring overleven. Gebruik meerdere authenticatieproviders zodat spelers fallbackopties hebben om toegang te krijgen tot hun accounts.
Implementeer authenticatie met graceful degradation:
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)
}
Dit patroon is belangrijk omdat tijdens de Yandex-storing services die nauw gekoppeld waren aan de identiteitsinfrastructuur van Yandex gebruikers helemaal niet konden authenticeren. Spelers werden buitengesloten; bots die niet authenticeren bleven draaien. Een multi-provider authenticatiestrategie betekent dat je spelers blijven spelen, zelfs wanneer je primaire authenticatieprovider uitvalt.
Patroon 4: observeerbaarheid over providers heen
Je monitoring moet tegelijkertijd over alle providers heen kunnen kijken. Verdeel monitoring in afzonderlijke aandachtsgebieden:
Monitoring van verkeersverdeling: Volg het requestvolume per ASN, per regio, per provider. Waarschuw wanneer de verdeling binnen een venster van 5 minuten met meer dan 20% verschuift.
Monitoring van authenticatiegezondheid: Volg het authenticatiesucces per methode en per provider. Waarschuw wanneer het succespercentage van een provider onder de 95% zakt.
Monitoring van botdetectie: Volg de verhouding tussen gemarkeerde en totale sessies per provider. Waarschuw wanneer de verhouding bij de ene provider onder de basislijn zakt terwijl deze bij een andere stijgt — dat is migratie.
Cross-providercorrelatie: Dit is het cruciale onderdeel. Afzonderlijke providermetrics kunnen er gezond uitzien terwijl het hele systeem wordt aangevallen. Bouw dashboards die alle providers naast elkaar tonen, zodat je migratiepatronen in één oogopslag kunt zien.
Best practices: 5 stappen naar botbestendige game-backendarchitectuur
Monitor migratie, niet alleen volume. Zet cross-provider verkeerscorrelatie op die detecteert wanneer verkeer op de ene plek daalt en op de andere stijgt. Alleen geaggregeerde metrics zouden je tijdens de Yandex-storing hebben misleid — AS200350-verkeer daalde, maar kwaadaardige activiteit ging door bij OVH en Hetzner.
Implementeer circuit breakers op elke externe afhankelijkheid. Je gameclient, leaderboardservice en matchmakinglogica hebben allemaal automatische failover nodig. Het circuit breaker-patroon voorkomt cascadestoringen wanneer één provider uitvalt. Test ze maandelijks door endpoints in staging te laten crashen.
Gebruik gedragsdetectie, geen IP-detectie. Gedragsanalyse (requestintervallen, retrypercentages, headerpatronen) werkt ongeacht waar bots worden gehost. IP-gebaseerde detectie faalt op het moment dat bots migreren naar een nieuwe provider met een schone IP-reputatie. De bovenstaande gedragsclassifier vangt bots die een verkeersdaling van 99% overleven.
Ontwerp authenticatie voor graceful degradation. Gebruik accountgebonden sessies van horizOn met meerdere authenticatiemethoden (e-mail, Google, Apple, anoniem). Wanneer een authenticatieprovider niet beschikbaar is, kunnen spelers een andere methode gebruiken om toegang te houden tot hun gamestatus. Dit voorkomt het worstcasescenario: spelers die buitengesloten worden terwijl bots blijven draaien.
Test je failover elk kwartaal. Simuleer providerstoringen in staging. Controleer of DNS-failover binnen je TTL activeert, circuit breakers correct openen en rate limiting werkt in fallback-regio's. Documenteer de tijd van foutdetectie tot volledig herstel. Ongeteste failover is geen failover — het is hoop.
Conclusie
De Yandex-storing bewees dat kwaadwillende actoren een betere infrastructuurveerkracht hebben dan de meeste game-backends. Bots stierven niet toen de datacenters van Yandex uitvielen — ze migreerden binnen enkele minuten naar andere providers en bleven draaien. Je geaggregeerde verkeersdashboard liet verbetering zien. De realiteit werd erger.
Begin met het implementeren van de gedragsbotdetector uit dit artikel in je stagingomgeving. Draai hem een week om basislijnen vast te stellen en simuleer daarna een providerstoring door verkeer van één ASN te blokkeren en te observeren of de migratiedetector afgaat. De patronen die je ontdekt, onthullen precies waar je huidige architectuur kwetsbaar is.
Voor gamestudio's die authenticatie- en spelersdatasystemen bouwen die infrastructuurstoringen overleven, biedt horizOn accountgebonden sessies met revisiebewuste cloudopslag — services die zijn ontworpen zodat je spelers hun voortgang behouden, zelfs wanneer het onverwachte gebeurt. Probeer het gratis of bekijk de API-documentatie om te zien hoe accountgebonden sessies integreren met je bestaande backend.
Bron: Yandex-storing: data toont aan dat kwaadaardige bots in plaats van uit te vallen, in beweging kwamen