Kembali ke Blog

Bot Bertahan dari Pemadaman Yandex — Arsitektur Backend Game Anda Juga Harus Bertahan

Diterbitkan pada 20 Oktober 2026
Bot Bertahan dari Pemadaman Yandex — Arsitektur Backend Game Anda Juga Harus Bertahan Dibuat dengan bantuan AI

Ringkasnya

Pelajari cara mendeteksi migrasi bot saat pemadaman infrastruktur dan bangun backend game yang tahan banting dengan strategi failover multi-penyedia.

Dasbor pemantauan Anda menunjukkan lalu lintas turun 40% dalam semalam. Anda menghela napas lega — DDoS pasti sudah berhenti. Namun analisis Cloudflare terhadap pemadaman Yandex Oktober 2023 membuktikan asumsi ini salah secara fatal: bot berbahaya tidak mati saat pusat data Yandex padam. Mereka bermigrasi ke OVH, Hetzner, dan DigitalOcean dalam hitungan menit. Metrik agregat Anda berbohong kepada Anda sementara penyerang terus berjalan.

Jika backend game Anda berjalan di satu penyedia cloud dengan deteksi bot berbasis IP, Anda hanya berjarak satu peristiwa infrastruktur dari titik buta yang sama. Runbook ini menguraikan apa yang diungkapkan pemadaman Yandex tentang perilaku bot selama kegagalan infrastruktur, dan cara merancang backend game yang bertahan dari kegagalan tersebut.

Apa yang Rusak: Saat Penyedia Infrastruktur Anda Padam

Ketika penyedia infrastruktur besar mengalami pemadaman, empat sistem kritis gagal secara bersamaan — dan kegagalannya terlihat seperti perbaikan bagi pemantauan yang naif.

Lapisan autentikasi Anda menjadi offline. Jika autentikasi pemain Anda bergantung pada layanan identitas satu penyedia, pemain tidak bisa masuk. Selama pemadaman Yandex, layanan yang terikat erat dengan infrastruktur Yandex mengalami gangguan layanan total. Pemain mendapat layar kesalahan; bot yang tidak melakukan autentikasi terus menghantam endpoint di tempat lain.

Deteksi bot Anda kehilangan kerangka acuannya. Sebagian besar backend game melacak IP berbahaya berdasarkan ASN atau penyedia hosting. Saat IP tersebut padam, sistem deteksi Anda menandainya sebagai "selesai" dan berhenti memeriksa. Sementara itu, armada bot yang sama muncul kembali dengan IP baru di penyedia yang berbeda.

Pembatasan laju (rate limiting) Anda menjadi tidak berguna. Batasan laju berbasis IP terikat pada rentang alamat tertentu. Saat bot bermigrasi, mereka mendapatkan IP baru dengan skor reputasi bersih. Pembatas laju Anda memperlakukan mereka sebagai lalu lintas sah baru.

Agregat lalu lintas Anda menjadi misinformasi. Analisis Cloudflare terhadap Yandex menunjukkan lalu lintas AS13238 (Yandex Search) hampir seluruhnya terdiri dari crawler sah — penurunannya nyata. Namun AS200350 (Yandex.Cloud) mengalami penurunan pemindaian dan probing kerentanan berbahaya sementara otomatisasi volume tinggi hanya berpindah lokasi. Grafik lalu lintas agregat yang menunjukkan penurunan 99% memberi tahu Anda cerita yang salah.

Mode kegagalan inti: pemantauan Anda mengira keadaan membaik padahal ancaman baru saja berpindah.

Cara Mendeteksi: Melihat Migrasi Bot secara Real-Time

Deteksi memerlukan pemantauan di beberapa lapisan, bukan hanya volume lalu lintas agregat. Setiap lapisan menangkap aspek berbeda dari pola migrasi.

Lapisan 1: Korelasi Sumber Lalu Lintas

Alih-alih melacak total permintaan per detik, pantau distribusi sumber lalu lintas di seluruh nomor sistem otonom (ASN). Saat lalu lintas satu penyedia turun, periksa apakah lalu lintas penyedia lain naik secara proporsional. Pola turun-naik yang berkorelasi antar-ASN adalah tanda migrasi bot.

Berikut detektor Python yang mengidentifikasi pola ini:

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}%")

Detektor ini memantau penurunan dan kenaikan lalu lintas yang berkorelasi di beberapa ASN. Saat AS13238 turun 92% dan AS16276 naik 270% secara bersamaan, detektor menandai kemungkinan migrasi bot, bukan perubahan lalu lintas sah. Skor kepercayaan meningkat saat besaran penurunan dan kenaikan cocok — ciri khas migrasi terkoordinasi.

Lapisan 2: Sidik Jari Perilaku (Behavioral Fingerprinting)

Jangan hanya lacak dari mana lalu lintas berasal — lacak bagaimana perilakunya. Bot yang bermigrasi antar penyedia mempertahankan tanda tangan perilaku yang identik: interval permintaan yang sama, pola header, durasi sesi, dan perilaku coba-ulang saat error. Reputasi IP direset saat mereka pindah; perilaku tidak.

Buat sidik jari perilaku untuk setiap sesi dan 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}")

Saat Anda melihat sidik jari perilaku yang sama muncul di IP baru di penyedia berbeda tak lama setelah penyedia asli padam, itu adalah migrasi yang terkonfirmasi — bukan kebetulan.

Lapisan 3: Pemantauan Status Autentikasi

Lacak rasio permintaan terautentikasi vs. tidak terautentikasi dari waktu ke waktu. Selama kegagalan infrastruktur, pemain sah kesulitan melakukan autentikasi (menggeser rasio ke arah tidak terautentikasi), sementara bot yang tidak pernah autentikasi terus menghantam endpoint Anda tanpa perubahan.

Lapisan ini sangat berharga karena memisahkan pemain asli dari lalu lintas otomatis apa pun penyedia sumbernya. Jika Anda menggunakan sesi terikat akun horizOn dengan beberapa metode autentikasi (email, Google, Apple, anonim), Anda dapat memantau tingkat keberhasilan autentikasi per metode. Saat autentikasi email gagal tetapi autentikasi Google berhasil, Anda telah mengidentifikasi kegagalan infrastruktur parsial — bukan migrasi bot. Saat semua autentikasi berhasil tetapi lalu lintas tidak terautentikasi melonjak di penyedia baru, itu adalah migrasi.

Cara Memperbaiki: Strategi Failover yang Berhasil

Deteksi tanpa perbaikan sama saja dengan menonton game Anda mati secara real-time. Berikut tiga mekanisme failover yang dapat Anda terapkan minggu ini.

Failover Berbasis DNS

Konfigurasikan DNS Anda dengan TTL pendek dan health check. Saat region utama gagal, DNS secara otomatis merutekan lalu lintas ke region sekunder. Ini tidak memerlukan perubahan aplikasi.

Pengaturan yang disarankan:

  • TTL: 60-120 detik (lebih pendek berarti failover lebih cepat, tetapi beban DNS lebih besar)
  • Interval health check: 10-15 detik
  • Endpoint health check: /health mengembalikan 200 dengan waktu respons < 500ms
  • Pemicu failover: 3 kegagalan health check berturut-turut

Tradeoff: TTL pendek meningkatkan volume kueri DNS sebesar 10-20x selama operasi normal. Untuk game dengan 50.000 pemain aktif harian, ini menambah biaya DNS sekitar $2-5/bulan — tidak signifikan dibandingkan downtime yang dicegahnya.

Circuit Breaker di Tingkat Aplikasi

Terapkan circuit breaker di klien game Anda yang mendeteksi saat layanan gagal dan secara otomatis merutekan ke alternatif:

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&lt;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&lt;HttpResponseMessage> SendToFallback(HttpRequestMessage request)
    {
        for (int i = 0; i &lt; _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);

Pola ini mencegah kegagalan beruntun saat satu penyedia padam. Seperti yang kami bahas dalam analisis kami tentang merancang fallback server, failover otomatis adalah perbedaan antara gangguan 30 detik dan pemadaman 4 jam.

Pembatasan Laju Terdistribusi

Saat lalu lintas gagal pindah ke region baru, pembatasan laju Anda juga harus berfungsi di sana. Batasan laju berbasis IP yang lokal di setiap server menjadi tidak berguna selama failover karena bot yang sama mendapat awal bersih di setiap endpoint.

Terapkan pembatasan laju terdistribusi menggunakan penyimpanan bersama yang dapat diakses dari semua region:

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")

Perhatikan bahwa pengidentifikasi menggunakan ID pemain, bukan alamat IP. Bot yang bermigrasi ke IP baru tetap membawa sidik jari pemain yang sama. Pendekatan ini menangkap mereka bahkan setelah migrasi tingkat penyedia.

Cara Mencegah: Pola Arsitektur untuk Ketahanan

Perbaikan bersifat reaktif. Pencegahan berarti membangun arsitektur yang mengasumsikan kegagalan infrastruktur akan terjadi — karena memang akan terjadi.

Pola 1: Deployment Multi-Penyedia untuk Layanan Kritis

Terapkan autentikasi, matchmaking, dan deteksi bot Anda di setidaknya dua penyedia cloud. Ini tidak berarti menjalankan seluruh stack Anda dua kali — ini berarti layanan yang harus bertahan dari pemadaman bersifat independen terhadap penyedia.

Realitas biaya: Deployment multi-penyedia untuk layanan kritis biasanya menambah 30-50% pada tagihan infrastruktur Anda. Untuk backend game yang melayani 10.000 pemain bersamaan, itu sekitar $800-1.500/bulan tambahan. Biaya pemadaman 2 jam untuk game yang sama — dengan memperhitungkan pendapatan IAP yang hilang, churn pemain, dan tiket dukungan — rata-rata $5.000-12.000.

Multi-penyedia minimum yang layak:

  • Autentikasi: penyedia primer + sekunder
  • Data pemain / status simpan: direplikasi di seluruh penyedia
  • Matchmaking: setidaknya satu region fallback
  • Papan peringkat dan analitik: dapat mentolerir pemadaman singkat (antrekan dan putar ulang)

Pola 2: Deteksi Bot Stateless dan Agnostic Penyedia

Rancang deteksi bot Anda agar bekerja identik di mana pun lalu lintas berasal. Pemadaman Yandex membuktikan bahwa deteksi berbasis IP pada dasarnya rapuh — bot mendapatkan IP baru begitu mereka bermigrasi.

Bangun deteksi di sekitar fitur perilaku yang bertahan dari perubahan infrastruktur:

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 &lt; 0.1:
            score += self.WEIGHTS['interval_variance']
        elif features.interval_variance &lt; 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 &lt;= 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) &lt; 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

Klasifikasi ini tidak peduli dari ASN mana permintaan berasal. Saat bot bermigrasi dari Yandex.Cloud ke OVH, sidik jari perilakunya tetap sama — dan pengklasifikasi tetap menangkapnya.

Pola 3: Pisahkan Autentikasi dari Infrastruktur

Sistem autentikasi Anda harus bertahan dari kegagalan infrastruktur tunggal apa pun. Gunakan beberapa penyedia autentikasi sehingga pemain memiliki opsi fallback untuk mengakses akun mereka.

Terapkan autentikasi dengan degradasi yang mulus:

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)
        }

Pola ini penting karena selama pemadaman Yandex, layanan yang terikat erat dengan infrastruktur identitas Yandex tidak dapat mengautentikasi pengguna sama sekali. Pemain terkunci; bot yang tidak melakukan autentikasi terus berjalan. Strategi autentikasi multi-penyedia berarti pemain Anda tetap bermain bahkan saat penyedia autentikasi utama Anda down.

Pola 4: Observabilitas yang Menjangkau Penyedia

Pemantauan Anda perlu melihat di semua penyedia secara bersamaan. Pisahkan pemantauan menjadi beberapa perhatian terpisah:

Pemantauan distribusi lalu lintas: Lacak volume permintaan per ASN, per region, per penyedia. Beri peringatan saat distribusi bergeser lebih dari 20% dalam jendela 5 menit.

Pemantauan kesehatan autentikasi: Lacak tingkat keberhasilan autentikasi per metode dan per penyedia. Beri peringatan saat tingkat keberhasilan penyedia mana pun turun di bawah 95%.

Pemantauan deteksi bot: Lacak rasio sesi yang ditandai terhadap total per penyedia. Beri peringatan saat rasio turun di bawah garis dasar di satu penyedia sementara naik di penyedia lain — itu adalah migrasi.

Korelasi lintas penyedia: Ini adalah bagian penting. Metrik penyedia individual bisa terlihat sehat sementara sistem secara keseluruhan sedang diserang. Bangun dasbor yang menampilkan semua penyedia berdampingan sehingga Anda dapat melihat pola migrasi sekilas.

Praktik Terbaik: 5 Langkah Menuju Arsitektur Backend Game yang Tahan Bot

  1. Pantau migrasi, bukan hanya volume. Siapkan korelasi lalu lintas lintas penyedia yang mendeteksi saat lalu lintas turun di satu tempat dan naik di tempat lain. Metrik agregat saja akan menipu Anda selama pemadaman Yandex — lalu lintas AS200350 turun, tetapi aktivitas berbahaya berlanjut di OVH dan Hetzner.

  2. Terapkan circuit breaker pada setiap dependensi eksternal. Klien game, layanan papan peringkat, dan logika matchmaking Anda semuanya memerlukan failover otomatis. Pola circuit breaker mencegah kegagalan beruntun saat satu penyedia padam. Uji mereka bulanan dengan mematikan endpoint di staging.

  3. Gunakan deteksi perilaku, bukan deteksi IP. Analisis perilaku (interval permintaan, tingkat percobaan ulang, pola header) berfungsi di mana pun bot dihosting. Deteksi berbasis IP gagal saat bot bermigrasi ke penyedia baru dengan reputasi IP bersih. Pengklasifikasi perilaku di atas menangkap bot yang bertahan dari penurunan lalu lintas 99%.

  4. Rancang autentikasi untuk degradasi yang mulus. Gunakan sesi terikat akun horizOn dengan beberapa metode autentikasi (email, Google, Apple, anonim). Saat satu penyedia autentikasi tidak tersedia, pemain dapat menggunakan metode lain untuk mempertahankan akses ke status game mereka. Ini mencegah skenario terburuk: pemain terkunci sementara bot terus berjalan.

  5. Uji failover Anda setiap kuartal. Simulasikan pemadaman penyedia di staging. Verifikasi bahwa failover DNS memicu dalam TTL Anda, circuit breaker terbuka dengan benar, dan pembatasan laju berfungsi di region fallback. Dokumentasikan waktu dari deteksi kegagalan hingga pemulihan penuh. Failover yang tidak diuji bukanlah failover — itu harapan.

Kesimpulan

Pemadaman Yandex membuktikan bahwa aktor jahat memiliki ketahanan infrastruktur yang lebih baik daripada kebanyakan backend game. Bot tidak mati saat pusat data Yandex padam — mereka bermigrasi ke penyedia lain dalam hitungan menit dan terus berjalan. Dasbor lalu lintas agregat Anda menunjukkan perbaikan. Kenyataannya memburuk.

Mulailah dengan menerapkan detektor bot perilaku dari artikel ini di lingkungan staging Anda. Jalankan selama satu minggu untuk menetapkan garis dasar, lalu simulasikan pemadaman penyedia dengan memblokir lalu lintas dari satu ASN dan amati apakah detektor migrasi aktif. Pola yang Anda temukan akan mengungkapkan dengan tepat di mana arsitektur Anda saat ini rentan.

Untuk studio game yang membangun sistem autentikasi dan data pemain yang bertahan dari kegagalan infrastruktur, horizOn menyediakan sesi terikat akun dengan cloud save yang sadar revisi — layanan yang dirancang agar pemain Anda tetap mempertahankan progres mereka bahkan saat hal tak terduga terjadi. Coba gratis atau jelajahi dokumentasi API untuk melihat bagaimana sesi terikat akun terintegrasi dengan backend Anda yang ada.


Sumber: Pemadaman Yandex: Data menunjukkan bahwa alih-alih padam, bot berbahaya justru berpindah