¿Por qué ZippyDB necesitaba una capa de proxy?

ZippyDB es el key-value store más usado de Meta, respaldando metadatos de productos, contadores y configuración a miles de millones de operaciones por segundo. Pero su modelo de acceso directo creaba una malla densa de conexiones TLS muchos-a-muchos: un cliente típico mantenía decenas de miles de conexiones salientes, y un host de base de datos típico aceptaba decenas de miles de entrantes. Cada nuevo grupo de clientes empeoraba cada host de base de datos.

Los proxies aparecen donde una población grande y diversa de clientes habla con un backend compartido—connection poolers, sidecars de service mesh, edges de CDN, API gateways. Interponer una capa entre muchos llamadores y un recurso compartido trae tres beneficios: acota el problema, crea un hogar para trabajo compartido y crea un punto de control. El tradeoff es un hop extra y una capa más que operar, pero compensa cuando la población de clientes es grande, diversa y no es tuya para cambiar.

La victoria real no es la reducción puntual de conexiones—es el cambio en el comportamiento de escala. El fan-in se vuelve independiente de la población de clientes.

Este es un patrón clásico de platform engineering: cuando no puedes cambiar un millón de clientes, inserta una capa que sí puedes cambiar. Para un caso de estudio relacionado sobre platform engineering a escala, checa cómo Santander redujo el provisioning de infraestructura de 90 días a horas.

Fuente: Meta Engineering Blog

Server rack representing ZippyDB backend fleet with proxy layer managing connections Development Concept Image

Colapsando el flujo de requests: batching y coalescing

El punto de ventaja compartido del proxy permite algo que ninguna librería de cliente logra: combinar trabajo entre llamadores no relacionados. Un batcher compartido en cada host agrupa requests destinados al mismo destino, con clave por caso de uso y shard físico, y los fusiona en un solo RPC de backend.

# Ilustración conceptual de coalescing de requests en la capa de proxy
# (La implementación de Meta es en C++, pero el patrón es agnóstico de lenguaje)

import asyncio
from collections import defaultdict

class BatchCoalescer:
    def __init__(self, linger_ms=2, max_size=100):
        self.linger_ms = linger_ms
        self.max_size = max_size
        # Mapa: (caso_de_uso, shard) -> lista de futures pendientes
        self.batches = defaultdict(list)

    async def request(self, use_case: str, shard: int, key: bytes):
        loop = asyncio.get_event_loop()
        fut = loop.create_future()
        bucket = self.batches[(use_case, shard)]
        bucket.append((key, fut))

        if len(bucket) >= self.max_size:
            await self._flush(use_case, shard)
        else:
            # Agenda un flush después de la ventana de linger
            loop.call_later(self.linger_ms / 1000, self._schedule_flush, use_case, shard)
        return await fut

    async def _flush(self, use_case, shard):
        batch = self.batches.pop((use_case, shard), [])
        if not batch:
            return
        # Coalesce claves duplicadas: busca una vez, distribuye el resultado
        unique_keys = {k for k, _ in batch}
        results = await self._backend_fetch(use_case, shard, unique_keys)
        for key, fut in batch:
            if not fut.done():
                fut.set_result(results[key])

Dos beneficios no son sobre eficiencia. Primero, coalescing bajo una hot key: miles de llamadores simultáneos colapsan en una sola lectura de backend, así una hot key nunca se convierte en una estampida contra una réplica. Segundo, retira librerías de batching client-side frágiles, que consumían CPU y eran fuente constante de incidentes.

El batching viene con dos protecciones: idle eviction (entradas del mapa de batch ociosas más allá de un TTL se borran en el próximo flush) y un cap de in-flight (rechaza nuevas ejecuciones cuando las corrutinas se acumulan más rápido de lo que drenan).

Network topology diagram showing fan-in reduction from clients to proxy to database hosts Coding Session Visual

La aritmética de la reducción de fan-in

Modela la flota como bolas en cajas. Lanza B bolas (shards que toca un host) en H cajas (hosts) y cuenta cajas distintas alcanzadas. El número esperado es:

E[cajas distintas] = H * (1 - (1 - 1/H)^B)

Y una caja específica es alcanzada con probabilidad:

p = 1 - (1 - 1/H)^B

Fan-out son cajas distintas alcanzadas; fan-in es esa probabilidad por la población de llamadores. Con números ficticios—20 regiones, 500,000 hosts de base de datos, 30,000 hosts de proxy, 1,000,000 de clientes, 50,000 shards por cliente—el modelo da ~19x de reducción en el total de conexiones persistentes, porque cada conexión de backend multiplexa muchos clientes.

Pero la reducción puntual no es el punto principal. En el modelo directo, el fan-in del host de base de datos es lineal en la población de clientes: cada nuevo grupo empeora cada host. Con ZGateway, el fan-in baja a aproximadamente R · S_host—regiones por densidad de shards por host—independiente de ambas flotas. Un número ilimitado manejado por todos los demás se vuelve un número acotado que tú controlas.

Ojo con esto

  • Hop extra y capa operativa: El proxy agrega latencia y una flota que operar. Compensa solo cuando la población de clientes es grande, diversa y no es tuya para cambiar.
  • Riesgo de OOM en batching: Mantener requests en memoria es peligroso. El TTL de idle eviction y el cap de in-flight no son opcionales—son lo que hace el batching seguro por defecto.
  • Costo de reshuffling en la capa de cache: Mover un peso en una capa de cache significa mover claves. El load balancer debe limitar cambios para evitar reshuffling caro.
  • Hosts heterogéneos: Tratar igual a hosts desiguales produce outliers calientes. Una política fija no sirve tanto para una capa tranquila como para una en shock.

Siguientes pasos

Si estás diseñando una capa similar, empieza midiendo la distribución de fan-in. Luego estudia weighted consistent hashing estilo ServiceRouter y control de concurrencia AIMD. Para el lado del cliente, checa tendencias de adopción de typing en Python y los retos que quedan.

Cloud infrastructure architecture with regional proxy tiers and cross-region failover System Abstract Visual

Operando ZGateway en producción

ZGateway corre en decenas de regiones en unas pocas capas: una capa grande de propósito general para la cola larga, capas dedicadas para los clientes más grandes y una capa separada de proxy de alto throughput. Difieren en footprint por más de un orden de magnitud, y las capas individuales no son uniformes debido al stacking. Observabilidad rica por caso de uso es lo que hace seguros el control de admisión y el load balancing en infraestructura compartida.

Aislamiento de tenants y control de admisión

Discriminant Load Shedding (DLS) mapea cada request a un bucket por tenant, con clave por caso de uso y dividido por prioridad. Cuando un tenant inunda la capa, su propio bucket se llena y el exceso se descarta mientras todos los demás buckets siguen drenando. En una sobrecarga controlada a >90% de CPU en ~1,350 buckets activos, solo 6—los verdaderos vecinos ruidosos—estaban shedding; los otros ~1,344 ejecutaron 99.9% de sus requests sin rechazos.

Resiliencia cross-region

Tres mecanismos extienden ZGateway más allá de la regionalidad estricta: global routing (tabla de ruteo entre regiones), mega-regions (agrupar regiones geográficamente cercanas en una localidad) y rings (declarar exactamente qué regiones se respaldan, en qué proporción). La señal de failover importa tanto como el ruteo—un promedio simple de CPU regional suaviza exactamente las condiciones calientes que necesitas atrapar.

El camino adelante

Tres direcciones destacan: heurísticas operadas por agentes (exponer control loops como superficie estructurada para agentes de IA), co-location (empujar parte del gateway junto al host ZServer para workloads críticos de latencia) y un gateway multi-proceso (dividir responsabilidades para aislamiento duro de fallas). Juntas, convierten a ZGateway de una capa inteligente en una capa programable.

La lección: Cuando no puedes cambiar un millón de clientes, construye la capa que sí puedes cambiar. Batching, coalescing, control de admisión y failover cross-region se vuelven posibles de una vez—resueltos una vez por el equipo que conoce mejor el backend.

Este contenido fue redactado con la asistencia de herramientas de IA, basándose en fuentes confiables, y fue revisado por nuestro equipo editorial antes de su publicación. No reemplaza el asesoramiento de un profesional especializado.