Bots haben den Yandex-Ausfall überlebt — deine Game-Backend-Architektur sollte das auch
Kurz und knapp
Erfahre, wie du Bot-Migration bei Infrastrukturausfällen erkennst und deine Game-Backend-Architektur mit Failover-Strategien bot-resistent machst.
Dein Monitoring-Dashboard zeigt, dass der Traffic über Nacht um 40 % gesunken ist. Du atmest erleichtert auf – der DDoS muss aufgehört haben. Doch Cloudflares Analyse des Yandex-Ausfalls im Oktober 2023 hat diese Annahme katastrophal widerlegt: Die schädlichen Bots starben nicht, als die Yandex-Rechenzentren ausfielen. Sie wanderten innerhalb von Minuten zu OVH, Hetzner und DigitalOcean. Deine aggregierten Metriken haben dich belogen, während die Angreifer weiterliefen.
Wenn dein Game-Backend bei einem einzigen Cloud-Anbieter läuft und IP-basierte Bot-Erkennung nutzt, bist du nur ein Infrastruktur-Ereignis von demselben blinden Fleck entfernt. Dieses Runbook zeigt, was der Yandex-Ausfall über das Bot-Verhalten bei Infrastrukturstörungen verrät und wie du Game-Backends architektonisch so gestaltest, dass sie solche Ausfälle überleben.
Was bricht: Wenn dein Infrastrukturanbieter ausfällt
Wenn ein großer Infrastrukturanbieter einen Ausfall erleidet, fallen vier kritische Systeme gleichzeitig aus – und zwar auf eine Weise, die für naives Monitoring wie Verbesserungen aussieht.
Deine Authentifizierungsschicht geht offline. Wenn deine Spieler-Authentifizierung vom Identitätsdienst eines einzigen Anbieters abhängt, können sich Spieler nicht einloggen. Während des Yandex-Ausfalls erlebten Dienste, die eng an die Yandex-Infrastruktur gekoppelt waren, eine vollständige Dienstunterbrechung. Spieler sahen Fehlerbildschirme; Bots, die sich nicht authentifizieren, griffen weiterhin auf andere Endpunkte zu.
Deine Bot-Erkennung verliert ihren Referenzrahmen. Die meisten Game-Backends verfolgen schädliche IPs anhand von ASN oder Hosting-Anbieter. Wenn diese IPs ausfallen, markieren deine Erkennungssysteme sie als „aufgelöst" und hören auf zu suchen. In der Zwischenzeit taucht dieselbe Bot-Flotte bei einem anderen Anbieter mit neuen IPs wieder auf.
Dein Rate Limiting wird nutzlos. IP-basierte Rate Limits sind an bestimmte Adressbereiche gebunden. Wenn Bots migrieren, erhalten sie frische IPs mit sauberen Reputationswerten. Dein Rate Limiter behandelt sie wie neuen legitimen Traffic.
Deine Traffic-Aggregate werden zu Fehlinformationen. Cloudflares Yandex-Analyse zeigte, dass der Traffic von AS13238 (Yandex Search) fast vollständig aus legitimen Crawlern bestand – der Rückgang dort war real. Aber bei AS200350 (Yandex.Cloud) ging das bösartige Scannen und Sondieren von Schwachstellen zurück, während sich die Hochvolumen-Automatisierung einfach verlagerte. Aggregierte Traffic-Diagramme, die einen Rückgang von 99 % zeigten, erzählten dir die falsche Geschichte.
Der zentrale Fehlermodus: Dein Monitoring denkt, die Lage habe sich verbessert, obwohl sich die Bedrohung nur verlagert hat.
So erkennst du: Bot-Migration in Echtzeit erkennen
Erkennung erfordert Monitoring auf mehreren Ebenen, nicht nur das aggregierte Traffic-Volumen. Jede Ebene erfasst einen anderen Aspekt des Migrationsmusters.
Ebene 1: Korrelation der Traffic-Quellen
Verfolge nicht die Gesamtzahl der Requests pro Sekunde, sondern überwache die Verteilung der Traffic-Quellen über autonome Systemnummern (ASNs). Wenn der Traffic eines Anbieters sinkt, prüfe, ob der Traffic anderer Anbieter proportional steigt. Ein korreliertes Abfall-und-Anstieg-Muster über ASNs hinweg ist die Signatur einer Bot-Migration.
Hier ist ein Python-Detektor, der dieses Muster erkennt:
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}%")
Dieser Detektor beobachtet korrelierte Traffic-Abfälle und -Anstiege über mehrere ASNs. Wenn AS13238 um 92 % fällt und AS16276 gleichzeitig um 270 % steigt, markiert er das als wahrscheinliche Bot-Migration und nicht als legitime Traffic-Änderung. Der Konfidenzwert steigt, wenn Abfall- und Anstiegsgrößen übereinstimmen – ein Kennzeichen koordinierter Migration.
Ebene 2: Verhaltens-Fingerprinting
Verfolge nicht nur, woher der Traffic kommt – verfolge, wie er sich verhält. Bots, die zwischen Anbietern migrieren, behalten identische Verhaltenssignaturen: dieselben Request-Intervalle, Header-Muster, Sitzungsdauern und Fehler-Wiederholungsverhalten. Die IP-Reputation wird bei einem Wechsel zurückgesetzt; das Verhalten nicht.
Erstelle für jede Sitzung einen Verhaltens-Fingerprint und hashe ihn:
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}")
Wenn du denselben Verhaltens-Fingerprint kurz nach dem Ausfall des ursprünglichen Anbieters auf neuen IPs bei einem anderen Anbieter siehst, ist das eine bestätigte Migration – kein Zufall.
Ebene 3: Überwachung des Authentifizierungsstatus
Verfolge im Zeitverlauf das Verhältnis von authentifizierten zu nicht authentifizierten Requests. Bei Infrastrukturausfällen haben legitime Spieler Probleme, sich zu authentifizieren (das Verhältnis verschiebt sich zu nicht authentifiziert), während Bots, die sich nie authentifizieren, deine Endpunkte unverändert weiter angreifen.
Diese Ebene ist besonders wertvoll, weil sie echte Spieler unabhängig vom Quellanbieter von automatisiertem Traffic trennt. Wenn du horizOns account-gebundene Sitzungen mit mehreren Authentifizierungsmethoden (E-Mail, Google, Apple, anonym) verwendest, kannst du die Authentifizierungserfolgsraten pro Methode überwachen. Wenn die E-Mail-Authentifizierung fehlschlägt, aber die Google-Authentifizierung erfolgreich ist, hast du einen partiellen Infrastrukturausfall identifiziert – keine Bot-Migration. Wenn alle Authentifizierungen erfolgreich sind, aber der nicht authentifizierte Traffic bei einem neuen Anbieter ansteigt, ist das Migration.
So behebst du es: Failover-Strategien, die funktionieren
Erkennung ohne Behebung bedeutet nur, deinem Spiel in Echtzeit beim Sterben zuzusehen. Hier sind drei Failover-Mechanismen, die du diese Woche umsetzen kannst.
DNS-basiertes Failover
Konfiguriere dein DNS mit kurzen TTLs und Health Checks. Wenn deine primäre Region ausfällt, leitet DNS den Traffic automatisch an sekundäre Regionen weiter. Das erfordert keine Änderungen an der Anwendung.
Empfohlene Einstellungen:
- TTL: 60-120 Sekunden (kürzer bedeutet schnelleres Failover, aber mehr DNS-Last)
- Health-Check-Intervall: 10-15 Sekunden
- Health-Check-Endpunkt:
/healthmit 200 und < 500 ms Antwortzeit - Failover-Auslöser: 3 aufeinanderfolgende fehlgeschlagene Health Checks
Der Kompromiss: Kurze TTLs erhöhen das DNS-Query-Volumen im Normalbetrieb um das 10- bis 20-fache. Für ein Spiel mit 50.000 täglich aktiven Spielern bedeutet das zusätzliche DNS-Kosten von etwa 2 bis 5 US-Dollar pro Monat – vernachlässigbar im Vergleich zu der Ausfallzeit, die es verhindert.
Circuit Breaker auf Anwendungsebene
Implementiere Circuit Breaker in deinem Game-Client, die erkennen, wenn ein Dienst ausfällt, und automatisch auf Alternativen ausweichen:
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);
Dieses Muster verhindert kaskadierende Ausfälle, wenn ein Anbieter ausfällt. Wie wir in unserer Analyse zur Architektur von Server-Fallbacks behandelt haben, ist automatisches Failover der Unterschied zwischen einem 30-Sekunden-Aussetzer und einem 4-stündigen Ausfall.
Verteiltes Rate Limiting
Wenn Traffic in eine neue Region umgeleitet wird, muss dein Rate Limiting auch dort funktionieren. IP-basierte Rate Limits, die lokal auf jedem Server liegen, werden beim Failover nutzlos, weil derselbe Bot an jedem Endpunkt eine saubere Weste bekommt.
Implementiere verteiltes Rate Limiting mit einem gemeinsamen Store, der von allen Regionen aus erreichbar ist:
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")
Beachte, dass der Identifier die Spieler-ID verwendet, nicht die IP-Adresse. Bots, die auf neue IPs migrieren, tragen weiterhin denselben Spieler-Fingerprint. Dieser Ansatz erwischt sie auch nach einer Migration auf Anbieterebene.
So verhinderst du es: Architekturmuster für Resilienz
Behebung ist reaktiv. Prävention bedeutet, eine Architektur zu bauen, die davon ausgeht, dass Infrastrukturausfälle passieren – denn sie werden passieren.
Muster 1: Multi-Provider-Deployment für kritische Dienste
Setze deine Authentifizierung, dein Matchmaking und deine Bot-Erkennung über mindestens zwei Cloud-Anbieter hinweg ein. Das bedeutet nicht, deinen gesamten Stack doppelt zu betreiben – sondern dass die Dienste, die einen Ausfall überleben müssen, anbieterunabhängig sind.
Kostenrealität: Multi-Provider-Deployment für kritische Dienste erhöht die Infrastrukturrechnung typischerweise um 30-50 %. Für ein Game-Backend mit 10.000 gleichzeitigen Spielern sind das etwa 800-1.500 US-Dollar zusätzlich pro Monat. Die Kosten eines 2-stündigen Ausfalls für dasselbe Spiel – unter Berücksichtigung verlorener IAP-Umsätze, Spielerabwanderung und Support-Tickets – liegen durchschnittlich bei 5.000-12.000 US-Dollar.
Minimal umsetzbares Multi-Provider-Setup:
- Authentifizierung: primärer + sekundärer Anbieter
- Spielerdaten / Spielstand: über Anbieter hinweg repliziert
- Matchmaking: mindestens eine Fallback-Region
- Leaderboards und Analytics: können kurze Ausfälle tolerieren (Queue und Replay)
Muster 2: Zustandslose, anbieterunabhängige Bot-Erkennung
Gestalte deine Bot-Erkennung so, dass sie unabhängig davon, woher der Traffic kommt, identisch funktioniert. Der Yandex-Ausfall hat bewiesen, dass IP-basierte Erkennung grundlegend fragil ist – Bots bekommen in dem Moment, in dem sie migrieren, neue IPs.
Baue die Erkennung um Verhaltensmerkmale auf, die Infrastrukturänderungen überleben:
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
Dieser Klassifikator kümmert sich nicht darum, von welcher ASN der Request kommt. Wenn ein Bot von Yandex.Cloud zu OVH migriert, bleibt sein Verhaltens-Fingerprint gleich – und der Klassifikator erwischt ihn trotzdem.
Muster 3: Authentifizierung von der Infrastruktur entkoppeln
Dein Authentifizierungssystem sollte jeden einzelnen Infrastrukturausfall überleben. Verwende mehrere Authentifizierungsanbieter, damit Spieler Fallback-Optionen für den Zugriff auf ihre Konten haben.
Implementiere Authentifizierung mit 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)
}
Dieses Muster ist wichtig, weil Dienste, die während des Yandex-Ausfalls eng an die Yandex-Identitätsinfrastruktur gekoppelt waren, Benutzer überhaupt nicht authentifizieren konnten. Spieler wurden ausgesperrt; Bots, die sich nicht authentifizieren, liefen weiter. Eine Multi-Provider-Auth-Strategie bedeutet, dass deine Spieler weiterspielen können, selbst wenn dein primärer Auth-Anbieter ausfällt.
Muster 4: Observability über Anbieter hinweg
Dein Monitoring muss gleichzeitig über alle Anbieter hinweg sehen können. Teile das Monitoring in getrennte Bereiche auf:
Traffic-Verteilungs-Monitoring: Verfolge das Request-Volumen pro ASN, pro Region, pro Anbieter. Warne, wenn sich die Verteilung in einem 5-Minuten-Fenster um mehr als 20 % verschiebt.
Authentifizierungs-Health-Monitoring: Verfolge die Auth-Erfolgsraten pro Methode und pro Anbieter. Warne, wenn die Erfolgsrate eines Anbieters unter 95 % fällt.
Bot-Erkennungs-Monitoring: Verfolge das Verhältnis von markierten zu allen Sitzungen pro Anbieter. Warne, wenn das Verhältnis bei einem Anbieter unter die Baseline fällt, während es bei einem anderen steigt – das ist Migration.
Anbieterübergreifende Korrelation: Das ist der entscheidende Teil. Einzelne Anbieter-Metriken können gesund aussehen, während das Gesamtsystem angegriffen wird. Baue Dashboards, die alle Anbieter nebeneinander zeigen, damit du Migrationsmuster auf einen Blick erkennst.
Best Practices: 5 Schritte zu einer bot-resistenten Game-Backend-Architektur
Überwache Migration, nicht nur Volumen. Richte anbieterübergreifende Traffic-Korrelation ein, die erkennt, wenn Traffic an einem Ort sinkt und an einem anderen steigt. Aggregierte Metriken allein hätten dich während des Yandex-Ausfalls getäuscht – der Traffic von AS200350 sank, aber die schädliche Aktivität ging bei OVH und Hetzner weiter.
Implementiere Circuit Breaker für jede externe Abhängigkeit. Dein Game-Client, der Leaderboard-Dienst und die Matchmaking-Logik benötigen alle automatisches Failover. Das Circuit-Breaker-Muster verhindert kaskadierende Ausfälle, wenn ein Anbieter ausfällt. Teste sie monatlich, indem du Endpunkte im Staging abschaltest.
Nutze Verhaltenserkennung statt IP-Erkennung. Verhaltensanalyse (Request-Intervalle, Retry-Raten, Header-Muster) funktioniert unabhängig davon, wo Bots gehostet werden. IP-basierte Erkennung versagt in dem Moment, in dem Bots zu einem neuen Anbieter mit sauberer IP-Reputation migrieren. Der obige Verhaltens-Klassifikator erwischt Bots, die 99-prozentige Traffic-Einbrüche überleben.
Gestalte Authentifizierung für Graceful Degradation. Nutze horizOns account-gebundene Sitzungen mit mehreren Authentifizierungsmethoden (E-Mail, Google, Apple, anonym). Wenn ein Auth-Anbieter nicht verfügbar ist, können Spieler eine andere Methode nutzen, um den Zugriff auf ihren Spielstand zu behalten. Das verhindert das Worst-Case-Szenario: Spieler werden ausgesperrt, während Bots weiterlaufen.
Teste dein Failover vierteljährlich. Simuliere Anbieterausfälle im Staging. Prüfe, ob das DNS-Failover innerhalb deiner TTL auslöst, Circuit Breaker korrekt öffnen und Rate Limiting in Fallback-Regionen funktioniert. Dokumentiere die Zeit von der Fehlererkennung bis zur vollständigen Wiederherstellung. Ungetestetes Failover ist kein Failover – es ist Hoffnung.
Fazit
Der Yandex-Ausfall hat bewiesen, dass Angreifer eine bessere Infrastruktur-Resilienz haben als die meisten Game-Backends. Bots starben nicht, als die Yandex-Rechenzentren ausfielen – sie wanderten innerhalb von Minuten zu anderen Anbietern und liefen weiter. Dein aggregiertes Traffic-Dashboard zeigte Verbesserung. In Wirklichkeit wurde es schlimmer.
Beginne damit, den Verhaltens-Bot-Detektor aus diesem Artikel in deiner Staging-Umgebung einzusetzen. Lass ihn eine Woche laufen, um Baselines zu etablieren, und simuliere dann einen Anbieterausfall, indem du den Traffic einer ASN blockierst und beobachtest, ob der Migrationsdetektor auslöst. Die Muster, die du entdeckst, zeigen dir genau, wo deine aktuelle Architektur verwundbar ist.
Für Spielestudios, die Authentifizierungs- und Spielerdatensysteme bauen, die Infrastrukturausfälle überleben, bietet horizOn account-gebundene Sitzungen mit revisionsbewusstem Cloud-Save – Dienste, die dafür entwickelt wurden, dass deine Spieler ihren Fortschritt behalten, auch wenn das Unerwartete passiert. Teste es kostenlos oder sieh dir die API-Dokumentation an, um zu erfahren, wie account-gebundene Sitzungen in dein bestehendes Backend integriert werden.