Bloga Dön

Botlar Yandex Kesintisinden Sağ Çıktı — Oyun Backend Mimarınız da Sağ Çıkmalı

Yayınlanma tarihi 20 Ekim 2026
Botlar Yandex Kesintisinden Sağ Çıktı — Oyun Backend Mimarınız da Sağ Çıkmalı Yapay zekâ yardımıyla oluşturuldu

Özet olarak

Hemen bot göçünü gerçek zamanlı tespit edin ve oyun backend mimarinizi Yandex kesintisi gibi altyapı arızalarına dayanıklı hale getirmeyi öğrenin.

İzleme paneliniz trafiğin bir gecede %40 düştüğünü gösteriyor. Rahat bir nefes alıyorsunuz — DDoS durmuş olmalı. Ancak Cloudflare'in Ekim 2023 Yandex kesintisi analizi bu varsayımın felaket derecede yanlış olduğunu kanıtladı: Yandex veri merkezleri karardığında kötü niyetli botlar ölmedi. Dakikalar içinde OVH, Hetzner ve DigitalOcean'a göç ettiler. Toplam metrikleriniz size yalan söylerken saldırganlar çalışmaya devam etti.

Oyun backend'iniz tek bir bulut sağlayıcıda ve IP tabanlı bot tespitiyle çalışıyorsa, aynı kör noktadan yalnızca bir altyapı olayı uzaktasınız. Bu runbook, Yandex kesintisinin altyapı arızaları sırasında bot davranışı hakkında neyi ortaya çıkardığını ve bu arızalardan sağ çıkan oyun backend'lerinin nasıl tasarlanacağını adım adım açıklıyor.

Ne Bozulur: Altyapı Sağlayıcınız Devre Dışı Kaldığında

Büyük bir altyapı sağlayıcısı kesinti yaşadığında, dört kritik sistem aynı anda çöker — ve bu çöküşler saf izlemeye iyileştirme gibi görünecek şekilde gerçekleşir.

Kimlik doğrulama katmanınız çevrimdışı kalır. Oyuncu kimlik doğrulamanız tek bir sağlayıcının kimlik hizmetine bağlıysa, oyuncular giriş yapamaz. Yandex kesintisi sırasında Yandex altyapısına sıkı sıkıya bağlı hizmetler tamamen kesintiye uğradı. Oyuncular hata ekranlarıyla karşılaştı; kimlik doğrulamayan botlar başka uç noktalara istek atmaya devam etti.

Bot tespitiniz referans çerçevesini kaybeder. Çoğu oyun backend'i kötü niyetli IP'leri ASN veya barındırma sağlayıcısına göre takip eder. Bu IP'ler devre dışı kaldığında, tespit sistemleriniz onları "çözüldü" olarak işaretler ve bakmayı bırakır. Bu sırada aynı bot filosu farklı bir sağlayıcıda yeni IP'lerle yeniden ortaya çıkar.

Rate limiting'iniz işe yaramaz hale gelir. IP tabanlı rate limit'ler belirli adres aralıklarına bağlıdır. Botlar göç ettiğinde temiz itibar puanlarına sahip yeni IP'ler alır. Rate limiter'ınız onları yeni meşru trafik olarak değerlendirir.

Trafik toplamlarınız yanlış bilgiye dönüşür. Cloudflare'in Yandex analizi, AS13238 (Yandex Search) trafiğinin neredeyse tamamen meşru tarayıcılardan oluştuğunu gösterdi — oradaki düşüş gerçekti. Ancak AS200350 (Yandex.Cloud) üzerinde kötü niyetli güvenlik açığı taraması ve yoklama azalırken, yüksek hacimli otomasyon basitçe başka yere taşındı. %99'luk düşüş gösteren toplam trafik grafikleri size yanlış hikayeyi anlattı.

Temel arıza modu: izleme sisteminiz, tehdit sadece yer değiştirdiğinde her şeyin daha iyi olduğunu düşünür.

Nasıl Tespit Edilir: Bot Göçünü Gerçek Zamanlı Fark Etme

Tespit, yalnızca toplam trafik hacmini değil, birden çok katmanı izlemeyi gerektirir. Her katman göç deseninin farklı bir yönünü yakalar.

Katman 1: Trafik Kaynağı Korelasyonu

Saniyedeki toplam istekleri takip etmek yerine, trafik kaynaklarının otonom sistem numaraları (ASN'ler) arasındaki dağılımını izleyin. Bir sağlayıcının trafiği düştüğünde, diğer sağlayıcıların trafiğinin orantılı olarak artıp artmadığını kontrol edin. ASN'ler arasındaki ilişkili düşüş ve yükseliş deseni, bot göçünün imzasıdır.

Bu deseni tanımlayan bir Python detektörü:

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

Bu detektör, birden çok ASN arasındaki ilişkili trafik düşüşlerini ve yükselişlerini izler. AS13238 %92 düştüğünde ve AS16276 aynı anda %270 yükseldiğinde, meşru trafik değişiklikleri yerine olası bot göçünü işaretler. Düşüş ve yükseliş büyüklükleri eşleştiğinde güven puanı artar — bu, koordineli göçün ayırt edici özelliğidir.

Katman 2: Davranışsal Parmak İzi

Trafiğin nereden geldiğini değil, nasıl davrandığını takip edin. Sağlayıcılar arasında göç eden botlar aynı davranışsal imzaları korur: aynı istek aralıkları, başlık desenleri, oturum süreleri ve hata-yeniden deneme davranışı. IP itibarı taşındıklarında sıfırlanır; davranış sıfırlanmaz.

Her oturum için bir davranışsal parmak izi oluşturun ve hash'leyin:

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

Orijinal sağlayıcı devre dışı kaldıktan kısa bir süre sonra aynı davranışsal parmak izinin farklı bir sağlayıcıdaki yeni IP'lerde göründüğünü fark ettiğinizde, bu doğrulanmış göçtür — tesadüf değil.

Katman 3: Kimlik Doğrulama Durumu İzleme

Zaman içinde kimliği doğrulanmış ve doğrulanmamış isteklerin oranını takip edin. Altyapı arızaları sırasında meşru oyuncular kimlik doğrulamakta zorlanır (oranı doğrulanmamışa kaydırır), hiç kimlik doğrulamayan botlar ise uç noktalarınıza değişmeden istek atmaya devam eder.

Bu katman özellikle değerlidir çünkü kaynak sağlayıcıdan bağımsız olarak gerçek oyuncuları otomatik trafikten ayırır. horizOn'un hesaba bağlı oturumlarını birden çok kimlik doğrulama yöntemiyle (e-posta, Google, Apple, anonim) kullanıyorsanız, kimlik doğrulama başarı oranlarını yöntem bazında izleyebilirsiniz. E-posta kimlik doğrulaması başarısız olurken Google kimlik doğrulaması başarılıysa, kısmi bir altyapı arızası tespit ettiniz — bot göçü değil. Tüm kimlik doğrulamalar başarılıyken yeni bir sağlayıcıda doğrulanmamış trafik artışı varsa, bu göçtür.

Nasıl Giderilir: İşe Yarayan Failover Stratejileri

Tespit, giderme olmadan sadece oyununuzun gerçek zamanlı ölümünü izlemektir. İşte bu hafta uygulayabileceğiniz üç failover mekanizması.

DNS Tabanlı Failover

DNS'inizi kısa TTL'ler ve sağlık kontrolleriyle yapılandırın. Birincil bölgeniz arızalandığında DNS, trafiği otomatik olarak ikincil bölgelere yönlendirir. Bu, herhangi bir uygulama değişikliği gerektirmez.

Önerilen ayarlar:

  • TTL: 60-120 saniye (daha kısa, daha hızlı failover ancak daha fazla DNS yükü demektir)
  • Sağlık kontrolü aralığı: 10-15 saniye
  • Sağlık kontrolü uç noktası: 200 döndüren ve < 500ms yanıt süresine sahip /health
  • Failover tetikleyicisi: 3 ardışık sağlık kontrolü hatası

Ödünleşim: kısa TTL'ler normal çalışma sırasında DNS sorgu hacmini 10-20 kat artırır. Günlük 50.000 aktif oyuncusu olan bir oyun için bu, DNS maliyetlerine ayda yaklaşık 2-5 $ ekler — önlediği kesinti süresiyle karşılaştırıldığında ihmal edilebilir.

Uygulama Seviyesinde Circuit Breaker'lar

Oyun istemcinizde, bir hizmetin ne zaman başarısız olduğunu algılayan ve otomatik olarak alternatiflere yönlendiren circuit breaker'lar uygulayın:

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

Bu desen, bir sağlayıcı devre dışı kaldığında basamaklı hataları önler. Sunucu fallback mimarisi analizimizde ele aldığımız gibi, otomatik failover 30 saniyelik bir aksaklık ile 4 saatlik bir kesinti arasındaki farktır.

Dağıtık Rate Limiting

Trafik yeni bir bölgeye failover olduğunda, rate limiting'inizin de orada çalışması gerekir. Her sunucuya yerel olan IP tabanlı rate limit'ler failover sırasında işe yaramaz hale gelir çünkü aynı bot her uç noktada temiz bir sayfa alır.

Tüm bölgelerden erişilebilen paylaşımlı bir depo kullanarak dağıtık rate limiting uygulayın:

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

Dikkat edin, tanımlayıcı IP adresi yerine oyuncu kimliğini kullanıyor. Yeni IP'lere göç eden botlar hâlâ aynı oyuncu parmak izini taşır. Bu yaklaşım, sağlayıcı düzeyinde göçten sonra bile onları yakalar.

Nasıl Önlenir: Dayanıklılık için Mimari Desenler

Giderme tepkiseldir. Önleme, altyapı arızasının olacağını varsayan bir mimari kurmak demektir — çünkü olacaktır.

Desen 1: Kritik Hizmetler için Çoklu Sağlayıcı Dağıtımı

Kimlik doğrulama, matchmaking ve bot tespiti hizmetlerinizi en az iki bulut sağlayıcısına dağıtın. Bu, tüm yığınınızı iki kez çalıştırmak anlamına gelmez — kesintiden sağ çıkması gereken hizmetlerin sağlayıcıdan bağımsız olması demektir.

Maliyet gerçeği: Kritik hizmetler için çoklu sağlayıcı dağıtımı, altyapı faturanıza tipik olarak %30-50 ekler. 10.000 eşzamanlı oyuncuya hizmet veren bir oyun backend'i için bu, ayda yaklaşık 800-1.500 $ ek maliyet demektir. Aynı oyun için 2 saatlik bir kesintinin maliyeti — kaybedilen IAP geliri, oyuncu kaybı ve destek biletleri hesaba katıldığında — ortalama 5.000-12.000 $'dır.

Minimum uygulanabilir çoklu sağlayıcı:

  • Kimlik doğrulama: birincil + ikincil sağlayıcı
  • Oyuncu verisi / kayıt durumu: sağlayıcılar arasında çoğaltılmış
  • Matchmaking: en az bir fallback bölgesi
  • Lider tabloları ve analitik: kısa kesintilere dayanabilir (kuyruk ve tekrar oynatma)

Desen 2: Durumsuz, Sağlayıcıdan Bağımsız Bot Tespiti

Bot tespitinizi, trafiğin nereden geldiğinden bağımsız olarak aynı şekilde çalışacak şekilde tasarlayın. Yandex kesintisi, IP tabanlı tespitin temelde kırılgan olduğunu kanıtladı — botlar göç ettikleri anda yeni IP'ler alır.

Tespiti, altyapı değişikliklerinden sağ çıkan davranışsal özellikler etrafında kurun:

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

Bu sınıflandırıcı, isteğin hangi ASN'den geldiğini umursamaz. Bir bot Yandex.Cloud'dan OVH'a göç ettiğinde davranışsal parmak izi aynı kalır — ve sınıflandırıcı onu yine de yakalar.

Desen 3: Kimlik Doğrulamayı Altyapıdan Bağımsızlaştırın

Kimlik doğrulama sisteminiz herhangi bir tek altyapı arızasından sağ çıkmalı. Oyuncuların hesaplarına erişmek için fallback seçenekleri olması amacıyla birden çok kimlik doğrulama sağlayıcısı kullanın.

Kademeli bozulmayla kimlik doğrulama uygulayın:

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

Bu desen önemlidir çünkü Yandex kesintisi sırasında Yandex kimlik altyapısına sıkı sıkıya bağlı hizmetler kullanıcıların kimliğini hiç doğrulayamadı. Oyuncular dışarıda kaldı; kimlik doğrulamayan botlar çalışmaya devam etti. Çoklu sağlayıcı kimlik doğrulama stratejisi, birincil kimlik doğrulama sağlayıcınız kapalıyken bile oyuncularınızın oynamaya devam etmesini sağlar.

Desen 4: Sağlayıcıları Kapsayan Gözlemlenebilirlik

İzleme sisteminizin tüm sağlayıcıları aynı anda görmesi gerekir. İzlemeyi ayrı sorumluluk alanlarına bölün:

Trafik dağılımı izleme: ASN, bölge ve sağlayıcı başına istek hacmini takip edin. Dağılım 5 dakikalık bir pencerede %20'den fazla değiştiğinde uyarı verin.

Kimlik doğrulama sağlığı izleme: Yöntem ve sağlayıcı başına kimlik doğrulama başarı oranlarını takip edin. Herhangi bir sağlayıcının başarı oranı %95'in altına düştüğünde uyarı verin.

Bot tespiti izleme: Sağlayıcı başına işaretlenen-toplam oturum oranını takip edin. Bir sağlayıcıda oran taban çizgisinin altına düşerken diğerinde yükseliyorsa uyarı verin — bu göçtür.

Sağlayıcılar arası korelasyon: Bu kritik parçadır. Bireysel sağlayıcı metrikleri sağlıklı görünebilirken genel sistem saldırı altında olabilir. Tüm sağlayıcıları yan yana gösteren panolar oluşturun, böylece göç desenlerini bir bakışta fark edebilirsiniz.

En İyi Uygulamalar: Botlara Dayanıklı Oyun Backend Mimarisi için 5 Adım

  1. Yalnızca hacmi değil, göçü izleyin. Trafiğin bir yerde düşüp başka bir yerde yükseldiğini tespit eden sağlayıcılar arası trafik korelasyonu kurun. Yandex kesintisi sırasında yalnızca toplam metrikler sizi yanıltırdı — AS200350 trafiği düştü, ancak kötü niyetli etkinlik OVH ve Hetzner'da devam etti.

  2. Her harici bağımlılıkta circuit breaker uygulayın. Oyun istemciniz, lider tablosu hizmetiniz ve matchmaking mantığınızın tümü otomatik failover'a ihtiyaç duyar. Circuit breaker deseni, bir sağlayıcı devre dışı kaldığında basamaklı hataları önler. Staging'de uç noktaları kapatarak ayda bir test edin.

  3. IP tespiti değil, davranışsal tespit kullanın. Davranışsal analiz (istek aralıkları, yeniden deneme oranları, başlık desenleri) botların nerede barındırıldığından bağımsız çalışır. IP tabanlı tespit, botlar temiz IP itibarına sahip yeni bir sağlayıcıya göç ettiği anda başarısız olur. Yukarıdaki davranışsal sınıflandırıcı, %99 trafik düşüşünden sağ çıkan botları yakalar.

  4. Kimlik doğrulamayı kademeli bozulma için tasarlayın. Birden çok kimlik doğrulama yöntemiyle (e-posta, Google, Apple, anonim) horizOn'un hesaba bağlı oturumlarını kullanın. Bir kimlik doğrulama sağlayıcısı kullanılamadığında, oyuncular oyun durumlarına erişimi sürdürmek için başka bir yöntem kullanabilir. Bu, en kötü senaryoyu önler: botlar çalışmaya devam ederken oyuncuların dışarıda kalması.

  5. Failover'ınızı üç ayda bir test edin. Staging'de sağlayıcı kesintileri simüle edin. DNS failover'ının TTL süreniz içinde tetiklendiğini, circuit breaker'ların doğru açıldığını ve rate limiting'in fallback bölgelerinde çalıştığını doğrulayın. Arıza tespitinden tam kurtarmaya kadar geçen süreyi belgeleyin. Test edilmemiş failover, failover değildir — umuttur.

Sonuç

Yandex kesintisi, kötü niyetli aktörlerin çoğu oyun backend'inden daha iyi altyapı dayanıklılığına sahip olduğunu kanıtladı. Yandex veri merkezleri karardığında botlar ölmedi — dakikalar içinde diğer sağlayıcılara göç ettiler ve çalışmaya devam ettiler. Toplam trafik paneliniz iyileşme gösterdi. Gerçeklik daha da kötüleşti.

Bu makaledeki davranışsal bot detektörünü staging ortamınıza dağıtarak başlayın. Taban çizgileri oluşturmak için bir hafta çalıştırın, ardından bir ASN'den gelen trafiği engelleyerek bir sağlayıcı kesintisi simüle edin ve göç detektörünün tetiklenip tetiklenmediğini gözlemleyin. Keşfettiğiniz desenler, mevcut mimarinizin tam olarak nerede savunmasız olduğunu ortaya çıkaracaktır.

Altyapı arızalarından sağ çıkan kimlik doğrulama ve oyuncu verisi sistemleri kuran oyun stüdyoları için horizOn, sürüm geçmişini koruyan bulut kaydetmeyle hesaba bağlı oturumlar sunar — oyuncularınızın beklenmedik bir şey olduğunda bile ilerlemelerini koruması için tasarlanmış hizmetler. Ücretsiz deneyin veya hesaba bağlı oturumların mevcut backend'inizle nasıl entegre olduğunu görmek için API dokümantasyonunu inceleyin.


Kaynak: Yandex kesintisi: Veriler, kötü niyetli botların kaybolmak yerine harekete geçtiğini gösteriyor