Faultline, un harness en Go que inyecta fallos y verifica linealizabilidad contra etcd
El proyecto es open source, sigue la metodología de Jepsen para comprobar consistencia y encontró seis errores en sí mismo mientras se construía.

Faultline es un harness open source escrito en Go que inyecta fallos en sistemas distribuidos, registra cada operación concurrente que ven los clientes y comprueba si ese historial es linealizable. Sigue la metodología de Jepsen, el trabajo de Kyle Kingsbury, y la aplica sobre un clúster etcd de tres nodos como objetivo de referencia. Un segundo objetivo con arquitectura distinta, el almacén clave-valor de NATS JetStream, demuestra que el harness es reutilizable y no una herramienta hecha a medida para etcd.
En ninguno de los dos aparecieron violaciones de consistencia bajo las condiciones probadas. El autor lo presenta como evidencia válida y no como un mal resultado: cero violaciones en varias campañas, reproducibles, es trabajo real de pruebas de infraestructura.
Cómo está montado
Todo objetivo nuevo implementa un contrato de cuatro métodos: Connect, Invoke y Close por un lado; Init y Apply por otro. El inyector de fallos, el generador de carga y el verificador se escriben una sola vez y no cambian al añadir un destino. Añadir NATS no obligó a tocar ninguno de los tres.
Cada fallo aplicado se verifica de forma independiente, nunca se da por hecho: la comprobación de una partición hace ping a través del corte y confirma que el ping falla, y hasta que eso se valida contra el contenedor real el fallo no cuenta como cobertura. El verificador es una búsqueda al estilo Wing & Gong sobre ordenaciones secuenciales del historial registrado, con memoización por (operaciones restantes, estado secuencial) y un presupuesto de búsqueda acotado: si no puede resolverlo, informa de resultado no concluyente en lugar de quedarse colgado.
Dos cosas se endurecieron a propósito. La igualdad numérica exacta con big.Rat, aplicada de forma recursiva sobre valores con forma de JSON, porque cualquier cliente que haga pasar números por JSON convierte un int de Go en float64 y una comparación ingenua marcaría cada operación como violación falsa. Y el caché de estado a prueba de colisiones: la clave de memoización es solo un hash de cubo, y cada acierto se verifica con igualdad estructural completa antes de darlo por bueno. El verificador se validó antes contra siete historiales construidos a mano, unos correctos y otros no.
Los seis errores que encontró en sí mismo
La respuesta de ToyKV a una operación CAS viajaba con el campo equivocado: el servidor escribía el resultado en wireResult.OK y el cliente leía wireResult.Value, así que todo cliente veía nil pasara lo que pasara. Las pruebas unitarias no lo detectaron porque ejercitaban el almacén directamente, nunca por HTTP. containerIP leía solo .NetworkSettings.IPAddress, vacío para cualquier contenedor en una red definida por el usuario. Los inyectores de fallos abortaban en el primer error de limpieza, dejando sin limpiar el resto de nodos: eso dejó un clúster etcd de tres nodos particionado e irrecuperable durante varios días. La verificación por ping daba siempre el visto bueno porque las imágenes no traían iputils-ping y el "command not found" era indistinguible de "la partición bloquea el tráfico". Y las lecturas de JetStream KV no son linealizables por defecto: CreateKeyValue activa AllowDirect, lo que permite que responda cualquier réplica, un compromiso deliberado entre latencia y consistencia que produjo tres violaciones falsas. Desactivarlo desde el Connect de cada cliente provocó una estampida de reconfiguraciones que hacía expirar el propio Connect, así que se movió a un paso Bootstrap único.
Cien ejecuciones sobre etcd con semillas válidas distintas dejan 35.470 operaciones registradas, 35.378 con éxito, 92 ambiguas, dos comprobaciones no concluyentes y ningún fallo confirmado. Los dos intentos inválidos se conservan en el conjunto de datos en lugar de descartarse; uno de ellos, la semilla 1006, se repitió por separado y salió bien. Lo que importa para quien tenga que adoptarlo: un destino nuevo no obliga a tocar el motor, y eso separa un harness reutilizable de un script atado a un solo sistema.
