¿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

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).
![]()
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.

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.