Volver al Blog

Los bots sobrevivieron a la caída de Yandex — tu arquitectura de backend de juegos también debería

Publicado el 20 de octubre de 2026
Los bots sobrevivieron a la caída de Yandex — tu arquitectura de backend de juegos también debería Generado con ayuda de IA

En resumen

Aprende a proteger tu backend de juegos con detección conductual y failover multiproveedor. La caída de Yandex demostró que los bots migran.

Tu dashboard de monitoreo muestra que el tráfico cayó 40% de la noche a la mañana. Suspiras con alivio: el DDoS debe haber terminado. Pero el análisis de Cloudflare sobre la caída de Yandex de octubre de 2023 demostró que esa suposición estaba catastróficamente equivocada: los bots maliciosos no murieron cuando los centros de datos de Yandex se apagaron. Migraron a OVH, Hetzner y DigitalOcean en cuestión de minutos. Tus métricas agregadas te mintieron mientras los atacantes seguían operando.

Si tu backend de juegos depende de un solo proveedor de nube con detección de bots basada en IP, estás a un solo evento de infraestructura del mismo punto ciego. Este runbook desglosa lo que la caída de Yandex revela sobre el comportamiento de los bots durante fallos de infraestructura y cómo diseñar backends de juegos que los sobrevivan.

Qué Falla: Cuando Tu Proveedor de Infraestructura se Apaga

Cuando un proveedor importante de infraestructura sufre una caída, cuatro sistemas críticos fallan simultáneamente — y fallan de maneras que parecen mejoras para un monitoreo ingenuo.

Tu capa de autenticación se cae. Si la autenticación de tus jugadores depende del servicio de identidad de un solo proveedor, los jugadores no pueden iniciar sesión. Durante la caída de Yandex, los servicios estrechamente acoplados a la infraestructura de Yandex sufrieron una interrupción completa del servicio. Los jugadores veían pantallas de error; los bots que no se autentican seguían golpeando endpoints en otros lugares.

Tu detección de bots pierde su marco de referencia. La mayoría de los backends de juegos rastrean IPs maliciosas por ASN o proveedor de hosting. Cuando esas IPs se apagan, tus sistemas de detección las marcan como "resueltas" y dejan de buscarlas. Mientras tanto, la misma flota de bots reaparece en nuevas IPs de un proveedor diferente.

Tu rate limiting se vuelve inútil. Los límites de tasa basados en IP están atados a rangos de direcciones específicos. Cuando los bots migran, obtienen IPs nuevas con puntuaciones de reputación limpias. Tu rate limiter los trata como tráfico legítimo nuevo.

Tus agregados de tráfico se convierten en desinformación. El análisis de Cloudflare sobre Yandex mostró que el tráfico de AS13238 (Yandex Search) consistía casi por completo en crawlers legítimos — la caída allí era real. Pero en AS200350 (Yandex.Cloud) el escaneo malicioso de vulnerabilidades y los sondeos disminuyeron mientras la automatización de alto volumen simplemente se reubicaba. Las gráficas de tráfico agregado que mostraban una caída del 99% te contaban una historia equivocada.

El modo de fallo principal: tu monitoreo cree que las cosas mejoraron cuando la amenaza solo se movió.

Cómo Detectar: Identificando la Migración de Bots en Tiempo Real

La detección requiere monitoreo en múltiples capas, no solo el volumen de tráfico agregado. Cada capa captura un aspecto diferente del patrón de migración.

Capa 1: Correlación de Fuentes de Tráfico

En lugar de rastrear el total de peticiones por segundo, monitorea la distribución de las fuentes de tráfico entre números de sistema autónomo (ASNs). Cuando el tráfico de un proveedor cae, verifica si el tráfico de otros proveedores aumenta proporcionalmente. Un patrón correlacionado de caída y ascenso entre ASNs es la firma de la migración de bots.

Aquí tienes un detector en Python que identifica este patrón:

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

Este detector vigila caídas y ascensos correlacionados de tráfico entre múltiples ASNs. Cuando AS13238 cae un 92% y AS16276 simultáneamente sube un 270%, marca probable migración de bots en lugar de cambios legítimos de tráfico. La puntuación de confianza aumenta cuando las magnitudes de caída y ascenso coinciden — un signo característico de migración coordinada.

Capa 2: Huella Conductual

No solo rastrees de dónde viene el tráfico — rastrea cómo se comporta. Los bots que migran entre proveedores conservan firmas conductuales idénticas: los mismos intervalos de petición, patrones de headers, duraciones de sesión y comportamiento de reintento ante errores. La reputación de IP se reinicia cuando se mueven; el comportamiento no.

Construye una huella conductual para cada sesión y aplícale un hash:

import hashlib
import statistics

def fingerprint_session(requests: list[dict]) -> str:
    """Generate a behavioral fingerprint from a session's request pattern."""
    intervals = []
    for i in range(1, len(requests)):
        delta = requests[i]['timestamp'] - requests[i-1]['timestamp']
        intervals.append(round(delta, 3))

    if len(intervals) < 2:
        return "insufficient_data"

    # Key behavioral features
    interval_mean = round(statistics.mean(intervals), 3)
    interval_stdev = round(statistics.stdev(intervals), 3)
    total_duration = round(requests[-1]['timestamp'] - requests[0]['timestamp'], 3)
    error_count = sum(1 for r in requests if r.get('status_code', 200) >= 400)
    retry_count = sum(1 for r in requests if r.get('is_retry', False))
    unique_paths = len(set(r['path'] for r in requests))
    user_agent_count = len(set(r.get('user_agent', '') for r in requests))

    fingerprint_data = (
        f"{interval_mean}|{interval_stdev}|{total_duration}|"
        f"{error_count}|{retry_count}|{unique_paths}|{user_agent_count}"
    )

    return hashlib.sha256(fingerprint_data.encode()).hexdigest()[:16]


# Example: same bot session detected at two different providers
session_provider_a = [
    {'timestamp': 1000.0, 'path': '/api/login', 'status_code': 200},
    {'timestamp': 1000.5, 'path': '/api/profile', 'status_code': 200},
    {'timestamp': 1001.0, 'path': '/api/match/join', 'status_code': 429},
    {'timestamp': 1001.5, 'path': '/api/match/join', 'status_code': 200, 'is_retry': True},
    {'timestamp': 1002.0, 'path': '/api/score', 'status_code': 200},
]

session_provider_b = [
    {'timestamp': 2000.0, 'path': '/api/login', 'status_code': 200},
    {'timestamp': 2000.5, 'path': '/api/profile', 'status_code': 200},
    {'timestamp': 2001.0, 'path': '/api/match/join', 'status_code': 429},
    {'timestamp': 2001.5, 'path': '/api/match/join', 'status_code': 200, 'is_retry': True},
    {'timestamp': 2002.0, 'path': '/api/score', 'status_code': 200},
]

fp_a = fingerprint_session(session_provider_a)
fp_b = fingerprint_session(session_provider_b)

print(f"Provider A fingerprint: {fp_a}")
print(f"Provider B fingerprint: {fp_b}")
print(f"Same bot: {fp_a == fp_b}")

Cuando ves la misma huella conductual apareciendo en IPs nuevas de un proveedor diferente poco después de que el proveedor original se cayera, eso es migración confirmada — no coincidencia.

Capa 3: Monitoreo del Estado de Autenticación

Rastrea la proporción de peticiones autenticadas vs. no autenticadas a lo largo del tiempo. Durante fallos de infraestructura, los jugadores legítimos tienen problemas para autenticarse (desplazando la proporción hacia no autenticadas), mientras que los bots que nunca se autentican siguen golpeando tus endpoints sin cambios.

Esta capa es especialmente valiosa porque separa a los jugadores reales del tráfico automatizado sin importar el proveedor de origen. Si usas sesiones vinculadas a cuenta de horizOn con múltiples métodos de autenticación (email, Google, Apple, anónimo), puedes monitorear las tasas de éxito de autenticación por método. Cuando la autenticación por email falla pero la de Google funciona, has identificado un fallo parcial de infraestructura — no una migración de bots. Cuando toda la autenticación funciona pero el tráfico no autenticado se dispara en un proveedor nuevo, eso es migración.

Cómo Remediar: Estrategias de Failover Que Funcionan

La detección sin remediación es solo ver morir tu juego en tiempo real. Aquí tienes tres mecanismos de failover que puedes implementar esta semana.

Failover Basado en DNS

Configura tu DNS con TTLs cortos y health checks. Cuando tu región principal falla, el DNS enruta el tráfico automáticamente a regiones secundarias. Esto no requiere ningún cambio en la aplicación.

Configuración recomendada:

  • TTL: 60-120 segundos (más corto significa failover más rápido, pero más carga DNS)
  • Intervalo de health check: 10-15 segundos
  • Endpoint de health check: /health devolviendo 200 con tiempo de respuesta < 500ms
  • Disparador de failover: 3 fallos consecutivos de health check

El tradeoff: los TTLs cortos aumentan el volumen de consultas DNS entre 10 y 20 veces durante la operación normal. Para un juego con 50,000 jugadores activos diarios, esto añade aproximadamente $2-5/mes en costos de DNS — insignificante comparado con el downtime que previene.

Circuit Breakers a Nivel de Aplicación

Implementa circuit breakers en tu cliente de juego que detecten cuándo un servicio está fallando y enruten a alternativas automáticamente:

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

Este patrón previene fallos en cascada cuando un proveedor se cae. Como cubrimos en nuestro análisis de arquitectura de fallbacks de servidor, el failover automático es la diferencia entre un contratiempo de 30 segundos y una caída de 4 horas.

Rate Limiting Distribuido

Cuando el tráfico hace failover a una región nueva, tu rate limiting necesita funcionar allí también. Los límites de tasa basados en IP que son locales a cada servidor se vuelven inútiles durante el failover porque el mismo bot obtiene un historial limpio en cada endpoint.

Implementa rate limiting distribuido usando un almacén compartido accesible desde todas las regiones:

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

Observa que el identificador usa el ID del jugador, no la dirección IP. Los bots que migran a IPs nuevas siguen llevando la misma huella de jugador. Este enfoque los atrapa incluso después de una migración a nivel de proveedor.

Cómo Prevenir: Patrones de Arquitectura para la Resiliencia

La remediación es reactiva. La prevención significa construir arquitectura que asume que los fallos de infraestructura ocurrirán — porque ocurrirán.

Patrón 1: Despliegue Multi-Proveedor para Servicios Críticos

Despliega tu autenticación, matchmaking y detección de bots en al menos dos proveedores de nube. Esto no significa ejecutar todo tu stack dos veces — significa que los servicios que deben sobrevivir a una caída son independientes del proveedor.

Realidad de costos: El despliegue multi-proveedor para servicios críticos típicamente añade 30-50% a tu factura de infraestructura. Para un backend de juegos sirviendo a 10,000 jugadores concurrentes, eso es aproximadamente $800-1,500/mes adicionales. El costo de una caída de 2 horas para el mismo juego — considerando ingresos IAP perdidos, churn de jugadores y tickets de soporte — promedia $5,000-12,000.

Multi-proveedor mínimo viable:

  • Autenticación: proveedor primario + secundario
  • Datos del jugador / estado guardado: replicados entre proveedores
  • Matchmaking: al menos una región de respaldo
  • Leaderboards y analíticas: pueden tolerar caídas breves (cola y reproducción)

Patrón 2: Detección de Bots Stateless e Independiente del Proveedor

Diseña tu detección de bots para que funcione de manera idéntica sin importar de dónde se origine el tráfico. La caída de Yandex demostró que la detección basada en IP es fundamentalmente frágil — los bots obtienen IPs nuevas en el momento en que migran.

Construye la detección alrededor de características conductuales que sobreviven cambios de infraestructura:

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

A este clasificador no le importa de qué ASN proviene la petición. Cuando un bot migra de Yandex.Cloud a OVH, su huella conductual sigue siendo la misma — y el clasificador lo atrapa de todos modos.

Patrón 3: Desacopla la Autenticación de la Infraestructura

Tu sistema de autenticación debería sobrevivir a cualquier fallo individual de infraestructura. Usa múltiples proveedores de autenticación para que los jugadores tengan opciones de respaldo para acceder a sus cuentas.

Implementa autenticación con degradación elegante:

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

Este patrón importa porque durante la caída de Yandex, los servicios estrechamente acoplados a la infraestructura de identidad de Yandex no podían autenticar usuarios en absoluto. Los jugadores quedaron bloqueados; los bots que no se autentican siguieron operando. Una estrategia de autenticación multi-proveedor significa que tus jugadores siguen jugando incluso cuando tu proveedor principal de autenticación está caído.

Patrón 4: Observabilidad Que Abarca Proveedores

Tu monitoreo necesita abarcar todos los proveedores simultáneamente. Divide el monitoreo en ámbitos separados:

Monitoreo de distribución de tráfico: Rastrea el volumen de peticiones por ASN, por región, por proveedor. Alerta cuando la distribución se desvíe más del 20% en una ventana de 5 minutos.

Monitoreo de salud de autenticación: Rastrea las tasas de éxito de autenticación por método y por proveedor. Alerta cuando la tasa de éxito de cualquier proveedor caiga por debajo del 95%.

Monitoreo de detección de bots: Rastrea la proporción de sesiones marcadas respecto al total por proveedor. Alerta cuando la proporción cae por debajo de la línea base en un proveedor mientras sube en otro — eso es migración.

Correlación entre proveedores: Esta es la pieza crítica. Las métricas individuales de cada proveedor pueden verse saludables mientras el sistema completo está bajo ataque. Construye dashboards que muestren todos los proveedores lado a lado para que puedas detectar patrones de migración de un vistazo.

Mejores Prácticas: 5 Pasos para una Arquitectura de Backend de Juegos Resiliente a Bots

  1. Monitorea la migración, no solo el volumen. Configura correlación de tráfico entre proveedores que detecte cuándo el tráfico cae en un lugar y sube en otro. Las métricas agregadas por sí solas te habrían engañado durante la caída de Yandex — el tráfico de AS200350 cayó, pero la actividad maliciosa continuó en OVH y Hetzner.

  2. Implementa circuit breakers en cada dependencia externa. Tu cliente de juego, servicio de leaderboard y lógica de matchmaking necesitan failover automático. El patrón de circuit breaker previene fallos en cascada cuando un proveedor se cae. Pruébalos mensualmente matando endpoints en staging.

  3. Usa detección conductual, no detección por IP. El análisis conductual (intervalos de petición, tasas de reintento, patrones de headers) funciona sin importar dónde estén alojados los bots. La detección basada en IP falla en el momento en que los bots migran a un proveedor nuevo con reputación de IP limpia. El clasificador conductual descrito arriba atrapa bots que sobreviven caídas de tráfico del 99%.

  4. Diseña la autenticación para degradación elegante. Usa sesiones vinculadas a cuenta de horizOn con múltiples métodos de autenticación (email, Google, Apple, anónimo). Cuando un proveedor de autenticación no está disponible, los jugadores pueden usar otro método para mantener acceso a su estado de juego. Esto previene el peor escenario: jugadores bloqueados mientras los bots siguen operando.

  5. Prueba tu failover trimestralmente. Simula caídas de proveedores en staging. Verifica que el failover de DNS se active dentro de tu TTL, que los circuit breakers se abran correctamente y que el rate limiting funcione en las regiones de respaldo. Documenta el tiempo desde la detección del fallo hasta la recuperación completa. Un failover no probado no es failover — es esperanza.

Conclusión

La caída de Yandex demostró que los actores maliciosos tienen mejor resiliencia de infraestructura que la mayoría de los backends de juegos. Los bots no murieron cuando los centros de datos de Yandex se apagaron — migraron a otros proveedores en minutos y siguieron operando. Tu dashboard de tráfico agregado mostraba una mejora. La realidad empeoraba.

Empieza desplegando el detector conductual de bots de este artículo en tu entorno de staging. Ejecútalo durante una semana para establecer líneas base, luego simula una caída de proveedor bloqueando el tráfico de un ASN y observa si el detector de migración se activa. Los patrones que descubras revelarán exactamente dónde es vulnerable tu arquitectura actual.

Para estudios de juegos que construyen sistemas de autenticación y datos de jugador que sobreviven fallos de infraestructura, horizOn ofrece sesiones vinculadas a cuenta con guardado en la nube con control de revisiones — servicios diseñados para que tus jugadores conserven su progreso incluso cuando ocurre lo inesperado. Pruébalo gratis o explora la documentación de la API para ver cómo las sesiones vinculadas a cuenta se integran con tu backend existente.


Fuente: Caída de Yandex: Los datos muestran que en lugar de apagarse, los bots maliciosos se pusieron en movimiento