El sábado 4 de abril de 2026, el plano de datos del AppView de Bluesky —el backend que sirve las lecturas de la aplicación social— empezó a registrar errores como 'failed to set post cache item: dial tcp: bind: address already in use'. El plano de datos depende en gran medida de memcached para mantener la carga fuera de su base de datos principal Scylla, y Jim Calabro, el ingeniero de infraestructura de guardia, vio que los picos de registros coincidían con caídas en el tráfico visible para el usuario. El monitoreo de red estaba limpio, así que la causa permaneció oculta durante el fin de semana mientras el servicio funcionaba a duras penas.

La causa raíz fue un nuevo servicio interno desplegado esa semana: menos de tres solicitudes GetPostRecord por segundo, pero 15.000–20.000 URI de publicaciones por lote (lo normal es de 1 a 50). Todos los manejadores RPC del plano de datos acotan la concurrencia con errgroup.SetLimit —solo a este endpoint le faltaba esa línea. Una sola llamada generaba hasta 20.000 goroutines, marcando una tormenta de conexiones a memcached; una vez que el pool inactivo de 1.000 sockets se desbordó, se acumularon en TCP TIME_WAIT y agotaron todos los puertos efímeros. El cliente se ejecutaba en un solo centro de datos y, sin métricas por cliente, los errores de caché de todo tipo enmascararon al culpable.

El lunes 6 de abril se precipitó en una espiral de muerte: con millones de solicitudes a memcached fallando por segundo, la política de Bluesky de registrar cada error intentó millones de escrituras de registro write(2) bloqueantes por segundo. La avalancha de llamadas al sistema bloqueantes llevó al runtime de Go de unos 150 a 1.500 hilos del sistema operativo, dejando sin recursos al GC y provocando largas pausas stop-the-world. Los valores GOGC y GOMEMLIMIT ajustados de forma agresiva hicieron que el plano de datos sufriera fallos periódicos por OOM —activo unos 30 minutos, caído, y repetir—. Cada reinicio profundizaba el problema: los sockets en TIME_WAIT dejaban al proceso reiniciado incapaz de recrear su pool de memcached.

El parche del lunes fue gloriosamente absurdo: un dialer personalizado que elegía una IP de loopback 127.x.y.z aleatoria para cada conexión saliente, ampliando el espacio de ip+puerto más allá del rango agotado. Eso detuvo la espiral, y solo el miércoles el equipo encontró la verdadera causa raíz y restauró el SetLimit(50) faltante. El post-mortem público de Calabro también reconoció una falla de comunicación: la página de estado había culpado a un proveedor de red externo, lo cual 'claramente no era así'.

Tiempo de inactividad intermitente para aproximadamente la mitad de todos los usuarios de Bluesky durante unas ocho horas el 6 de abril de 2026 —la peor caída de la plataforma en el tiempo que lleva su responsable de infraestructura en el puesto

La concurrencia acotada se aplicaba por convención, no por estructura: todos los manejadores tenían SetLimit excepto uno, y un solo endpoint sin límite bastó para agotar los recursos a nivel de host.

El contrato de tráfico no tenía límites de forma: nada rechazaba un lote de 20.000 elementos, así que un cliente interno silencioso —3 solicitudes por segundo— superó a millones de clientes normales como fuente de carga.

El manejo de fallos amplificó el fallo: registrar cada error de memcached a millones de errores por segundo convirtió un problema de caché en una explosión de hilos, pausas del GC y fallos por OOM.

La observabilidad asumía solicitudes pequeñas y no tenía métricas por cliente, así que los errores de todos los tipos de caché ocultaron al único llamador y al único endpoint responsables durante días.

El reinicio como recuperación estaba roto: los sockets en TIME_WAIT del proceso muerto impedían que el nuevo proceso reconstruyera sus pools de memcached, convirtiendo los reinicios por OOM en más agotamiento de puertos.

Un solo bucle de goroutines sin límite superó a todas las demás salvaguardas. Los límites de concurrencia deben ser estructurales, no un hábito por endpoint: el único endpoint al que le faltaba uno se convirtió en todo el incidente.

El servicio se estabilizó el lunes con el dialer de loopback aleatorio, que se retiró una vez que la solución real —restaurar el límite de concurrencia— se implementó el miércoles 8 de abril. La exposición total fue de unas ocho horas de inactividad intermitente para aproximadamente la mitad de los usuarios. El post-mortem, publicado el 10 de abril de 2026, prometió observabilidad por cliente, detección de clientes que envían pequeñas cantidades de solicitudes sobredimensionadas, y un cambio del registro de alto volumen hacia métricas de Prometheus y trazas de OpenTelemetry; atrajo un análisis independiente detallado del investigador de confiabilidad Lorin Hochstein dos días después.

CONSULTA LAS PRUEBAS

Las fuentes

  1. April 2026 Outage Post-Mortem Jim Calabro (Bluesky infrastructure engineer), pckt.blog · pckt.blog ·
  2. Thoughts on the Bluesky public incident write-up Surfing Complexity (Lorin Hochstein) · surfingcomplexity.blog ·
  3. Bluesky April 2026 Outage Post-Mortem (discussion, 141 points) Hacker News · news.ycombinator.com ·