1 puntos por GN⁺ 2025-12-09 | 1 comentarios | Compartir por WhatsApp
  • Jepsen verificó la durabilidad y la consistencia del sistema de mensajería distribuida NATS JetStream en diversos escenarios de fallos
  • En los resultados de las pruebas se detectó pérdida de datos y split-brain durante la corrupción de archivos (.blk, snapshot) y la simulación de cortes de energía
  • JetStream ejecuta fsync de forma predeterminada cada 2 minutos, por lo que algunos mensajes acknowledged recientemente pueden quedar sin registrar en disco
  • Un crash de OS en un solo nodo puede disparar pérdida de datos y divergencias de réplicas
  • Jepsen recomienda que NATS cambie el valor predeterminado a fsync=always o documente explícitamente el riesgo de pérdida de datos

1. Contexto

  • NATS es un sistema de streaming popular para publicar y suscribirse a mensajes en forma de stream
    • JetStream replica datos usando un algoritmo de consenso Raft y garantiza entrega al menos una vez (at-least-once)
  • JetStream afirma en su documentación brindar consistencia linearizable y disponibilidad siempre, pero por el teorema CAP no puede cumplir ambos simultáneamente
  • Según la documentación de NATS, un stream de 3 nodos tolera la pérdida de 1 servidor y uno de 5 nodos tolera la pérdida de 2
  • Un mensaje se considera “almacenado correctamente” cuando el servidor responde con acknowledge a una solicitud de publish
  • Para la consistencia de datos se necesita una mayoría (quorum) de nodos: en un clúster de 5 nodos, al menos 3 deben estar activos para guardar un nuevo mensaje

2. Diseño de pruebas

  • Jepsen ejecutó las pruebas con el cliente JNATS 2.24.0 en un entorno de contenedores Debian 12 LXC
    • Algunas pruebas usaron la imagen oficial de Docker de NATS en el entorno de Antithesis
  • Se configuró un único stream de JetStream (réplica 5) e inyectando parada de proceso, crashes, particiones de red, pérdida de paquetes y corrupción de archivos
  • Se realizó una simulación de caída de energía con el sistema de archivos LazyFS para perder escrituras no fsynceadas
  • Cada proceso publicó mensajes únicos, y tras finalizar la prueba se verificó la existencia de mensajes acknowledged en todos los nodos
  • Cuando un mensaje solo existe en algunos nodos, se clasificó como divergence (inconsistencia de replicación)

3. Resultados principales

3.1 Pérdida total de datos en NATS 2.10.22 (#6888)

  • Se detectó que un crash de proceso simple podía causar la desaparición de todo el stream de JetStream
  • Tras el error "No matching streams for subject", no se recuperó durante varias horas
  • La causa fue inversión de snapshot de líder, borrado de estado de Raft, entre otros, y se corrigió en la versión 2.10.23

3.2 Pérdida de datos por corrupción de archivo .blk (#7549)

  • Cuando hay un error de un solo bit o truncation en el archivo .blk de JetStream, se pierden cientos de miles de escrituras acknowledged
    • Ejemplo: 679,153 de 1,367,069 registros perdidos
  • Incluso si solo algunos nodos se corrompen, ocurre pérdida masiva de datos y split-brain
    • Ejemplo: pérdida de hasta el 78% de los mensajes en n1, n3 y n5
  • NATS aún está investigando este problema

3.3 Eliminación total de datos por corrupción de archivo snapshot (#7556)

  • Si un archivo snapshot en data/jetstream/$SYS/_js_/ se corrompe, el nodo marca el stream como huérfano (orphaned) y borra todos los datos
  • Aun si solo se afecta a unos pocos nodos, puede haber falta de mayoría del clúster y jepsen-stream queda permanentemente no disponible
  • Ejemplo: al corromperse n3 y n5, n3 fue elegido líder y se borró todo jepsen-stream
  • Jepsen señala el riesgo de que un nodo corrupto sea electo como líder

3.4 Pérdida de datos por configuración predeterminada de fsync (#7564)

  • JetStream hace fsync de forma predeterminada solo cada 2 minutos, mientras los mensajes se reconocen de inmediato
    • Como resultado, los mensajes recién acknowledged pueden quedar sin persistir en disco
  • En cortes de energía o crashes de kernel se perdieron decenas de segundos de mensajes acknowledged
    • Ejemplo: 131,418 de 930,005 mensajes perdidos
  • La eliminación completa del stream es posible incluso con fallos consecutivos de un solo nodo
  • Esta conducta casi no se menciona en la documentación
  • Jepsen recomienda cambiar el valor predeterminado de NATS a fsync=always o incorporar una advertencia explícita sobre el riesgo de pérdida de datos

3.5 Split-brain por crash de OS en un solo nodo (#7567)

  • La pérdida de datos y la inconsistencia de replicación pueden ocurrir por un corte de energía o crash de kernel de un único nodo
  • En la arquitectura líder-seguidor, si algunos nodos ackean una escritura solo comprometida en memoria y luego fallan,
    la mayoría de los nodos pierde esa escritura y avanza con un estado nuevo
  • En las pruebas, tras un único corte de energía se observó split-brain persistente
    • Se verificó pérdida de mensajes acknowledged en rangos distintos según el nodo
  • Jepsen cita un caso similar de Kafka y enfatiza que el mismo riesgo también existe en sistemas basados en Raft

4. Discusión y conclusión

  • El problema de pérdida total de datos en 2.10.22 fue resuelto en 2.10.23
  • En 2.12.1, la pérdida de datos y split-brain por corrupción de archivos y crash de OS todavía ocurren
  • Con corrupción de archivos .blk y snapshot, pueden ocurrir mensajes faltantes en algunos nodos o eliminación total del stream
  • Un periodo de fsync predeterminado largo implica riesgo de pérdida de datos acknowledged si fallan simultáneamente varios nodos
  • Jepsen propone fsync=always o una advertencia clara de riesgo en la documentación
  • La promesa de “disponibilidad siempre” de JetStream es imposible por CAP, y requiere ajustes en la documentación
  • Jepsen especifica que se puede demostrar la existencia de bugs, pero no se puede probar la ausencia de inseguridad

4.1 Rol de LazyFS

  • Se simula la pérdida de escrituras sin fsync usando LazyFS
  • En una caída de energía permiten reproducir diversos errores de almacenamiento, como escrituras parciales (torn write)
  • El trabajo relacionado When Amnesia Strikes (VLDB 2024) reporta bugs similares en PostgreSQL, Redis, ZooKeeper, entre otros

4.2 Próximos pasos

  • Aún no se verificó la pérdida de mensajes por consumidor único, el orden de mensajes ni la garantía Linearizable/Serializable
  • La garantía exactly-once también queda como un tema para trabajo futuro
  • Se detectó un error documental y omisión de steps obligatorios de health check al añadir/eliminar nodos (#7545)
  • Los procedimientos seguros de reconfiguración de clúster siguen siendo ambiguos

1 comentarios

 
GN⁺ 2025-12-09
Opiniones de Hacker News
  • Cada vez que alguien se salta la teoría compleja y construye un sistema así, veo cómo aphyr lo hace pedazos
    Ahora hasta me pregunto si una IA podría leer la documentación de un proyecto y predecir la posibilidad de pérdida de datos solo por el lenguaje de marketing
    • Se siente como asentir mientras te acaricias una barba larga
      La gente siempre dice que “la teoría está sobrevalorada” o que “hackear es mejor que la educación formal”, pero al final terminan tropezando solos en un espacio de problemas ya documentado
    • Yo también le he pedido algo parecido a un LLM, y el resultado fue bastante útil
  • Da la impresión de que NATS ignora el teorema CAP
    • Parece un comentario subestimado
  • Yo he usado NATS para pub/sub en memoria, y en eso fue excelente
    También resolvía bien los detalles sutiles de escalado
    Pero nunca usé la persistencia, y no imaginaba que fuera tan frágil
    Sorprende que sea vulnerable incluso a la corrupción de un solo bit en un archivo
  • Como material relacionado, Jepsen y Antithesis publicaron hace poco un glosario de sistemas distribuidos
    Es una referencia excelente → Jepsen Glossary
  • Tenía curiosidad por la diferencia de contenido entre aphyr.com/tags/jepsen y jepsen.io/analyses
    Descubrí aphyr.com hace poco y tengo expectativas de encontrar muchas ideas valiosas
    • Jepsen empezó originalmente como una serie de blog personal
      Después jepsen.io evolucionó hasta convertirse en un proyecto profesional, y empezó a operar en serio hace unos 10 años
  • Me pregunto por qué existe la opción “Lazy fsync by Default”
    ¿Es para inflar el rendimiento en benchmarks? En clústeres pequeños, este tipo de configuración suele ser la causa del problema
    • No solo mejora la latencia, también el throughput
      Muchas aplicaciones no necesitan durabilidad total, así que lazy fsync puede ser útil
      Aun así, dejarlo como valor predeterminado es discutible
    • Siempre me he preguntado por qué habría que retrasar fsync a la fuerza
      Parecería que se podría resolver con procesamiento por lotes (batch), como TCP corking
    • Es una de esas cosas que se pueden hacer en un sistema distribuido
      Los fallos provocados por lazy fsync casi nunca ocurren al mismo tiempo en la mayoría de los nodos
    • Sí, es una decisión para mejorar el rendimiento
    • Buscan combinar la durabilidad obtenida por replicación y distribución con el throughput ganado gracias a lazy fsync
  • Recomiendo s2.dev como alternativa serverless a JetStream
    Ventaja: soporta streams ilimitados con durabilidad al nivel de object storage
    Desventaja: todavía no tiene consumer groups
    • Me pregunto si alguna vez le han corrido pruebas de Jepsen
  • El problema es que NATS por defecto solo hace fsync cada 2 minutos y devuelve ack de inmediato
    Si varios nodos fallan al mismo tiempo, puede haber pérdida de datos ya comprometidos
    Me recuerda al marketing de “web scale” de los primeros tiempos de MongoDB
    Creo que el valor predeterminado siempre debería ser la opción más segura
    • NATS deja claro que solo garantiza la disponibilidad del clúster
      Justamente eso me gustaba, porque permitía diseñar sistemas encima con esa premisa
      Cuando lo usé en 2018, también era rápido y fácil de administrar
    • La mayoría de las bases de datos modernas tampoco son completamente seguras por defecto
      Por ejemplo, el nivel de aislamiento predeterminado de PostgreSQL es read committed
      Redis también hace fsync por defecto cada 1 segundo
    • Redis Cluster solo devuelve ack después de replicar a varios nodos
      Incluso en Redis standalone se puede configurar ack después de fsync, pero por el buffering del SO sigue siendo difícil garantizarlo por completo
      Al final, lo importante es entender con precisión qué significa un ack
    • La mayoría de los sistemas eligen un equilibrio entre velocidad y durabilidad
      Si uno insiste solo en valores predeterminados seguros, el rendimiento cae mucho y además se le deja al usuario la carga de afinar todo manualmente
      Por ejemplo, el nivel de aislamiento predeterminado de Postgres también es débil y puede producir race conditions
      Referencia: artículo sobre pruebas Hermitage
    • El problema es que fsync solo ofrece opciones extremas
      En la era de los SSD desaparecieron puntos intermedios como group commit, y ahora el cuello de botella es el costo del cambio de syscall
      2 minutos es un intervalo demasiado largo (también hay que considerar la diferencia entre fdatasync y fsync)
  • La verdad, lo sospechaba, pero no pensé que fuera tan grave
    Mejor usar Redpanda
  • Me preguntaba si se podría mejorar la advertencia de rendimiento de fsync en NATS
    Si se hace un batch flush periódico, la latencia subiría, pero tal vez se mantendría el throughput
    • En lugar de un intervalo fijo, bastaría con poner en cola las escrituras mientras fsync está en curso y procesarlas juntas en el siguiente lote
      Esto es similar a agrupar rondas de Paxos
      Cuando termina una ronda, hay que arrancar de inmediato el siguiente lote