{"schemaVersion":1,"id":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle","url":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle","title":"Deterministic Simulation Testing: Metodología de Inyección de Fallas en TigerBeetle y FoundationDB","description":"Inyección de fallas basada en seeds, scheduling determinístico, y la metodología detrás de encontrar bugs imposibles en sistemas distribuidos. Técnicas reales de los frameworks de testing de TigerBeetle y FoundationDB.","language":"es","datePublished":"2025-11-29","dateModified":"2026-11-10","contentFormat":"text/markdown","content":"Testing\n\n# Deterministic Simulation Testing: Metodología de Inyección de Fallas en TigerBeetle y FoundationDB\n\n2025-11-29Actualizado: 2026-11-108 min lectura\n\n[Lucio Durán](<https://lucioduran.com/es>)\nC.T.O. en LOi\n\nTambién disponible en: [English](<https://lucioduran.com/blog/deterministic-simulation-testing-tigerbeetle>)\n\n## Por Qué Testear Sistemas Distribuidos Es Tan Difícil\n\nEl problema fundamental es el no-determinismo. Un sistema distribuido tiene múltiples fuentes:\n\n1. **Scheduling de threads**: El OS decide qué thread corre cuándo\n2. **Timing de red**: Los paquetes llegan en orden impredecible con latencia variable\n3. **Timing de I/O de disco**: Lecturas y escrituras se completan a velocidades variables\n4. **Skew de reloj**: Los relojes del sistema en diferentes máquinas derivan independientemente\n5. **Timing de fallas**: Crashes, particiones de red, y errores de disco pasan en momentos arbitrarios\n\nUn bug que requiere una combinación específica de estos eventos no-determinísticos podría tomar billones de ejecuciones de test para aparecer con testing random. Y aunque lo encuentres, no es posible reproducirlo porque no controlás el scheduler del OS, la red, ni el disco.\n\nLos integration tests tradicionales intentan manejar esto con sleeps y retries:\n\n```python\n# Así testea la mayoría la gente los sistemas distribuidos (no hagas esto)\ndef test_leader_election():\n cluster.start(3)\n time.sleep(5) # \"esperar a que complete la elección\" 🤞\n leader = cluster.get_leader()\n assert leader is not None\n\n cluster.kill(leader)\n time.sleep(10) # \"esperar a la re-elección\" 🤞🤞\n new_leader = cluster.get_leader()\n assert new_leader is not None\n assert new_leader != leader\n```\n\n## La Metodología de FoundationDB\n\n[FoundationDB](<https://www.foundationdb.org/>) fue pionero en simulation testing en bases de datos de producción. Su insight clave: si controlás todas las fuentes de no-determinismo, es posible explorar todo el espacio de estados sistemáticamente.\n\nLa arquitectura tiene tres pilares:\n\n### 1. Core Single-Threaded, Event-Driven\n\nLa base de datos entera corre en un solo thread (o múltiples threads determinísticos con scheduling explícito). No hay concurrencia a nivel del OS. Todas las operaciones asíncronas se modelan como eventos en una cola de prioridad:\n\n```\nCola de Eventos (ordenada por tiempo simulado):\n t=100ms: Nodo A recibe AppendEntries del Nodo B\n t=102ms: Timeout de elección del Nodo C se dispara\n t=103ms: Escritura de disco en Nodo A completa\n t=105ms: Timer de heartbeat del Nodo B se dispara\n t=108ms: Partición de red comienza (falla inyectada)\n```\n\nEl simulador saca eventos en orden, los ejecuta, y cualquier evento resultante vuelve a la cola. Porque la cola es determinística (mismo seed → mismo estado inicial → mismo orden de eventos), la ejecución es perfectamente reproducible.\n\n### 2. Capa de I/O Abstracta\n\nCada interacción con el mundo exterior pasa por una interfaz:\n\n```cpp\n// El runtime flow de FoundationDB abstrae todo\nclass INetwork {\n virtual Future<Void> connect(NetworkAddress addr) = 0;\n virtual Future<Void> send(Connection conn, Bytes data) = 0;\n virtual Future<Bytes> receive(Connection conn) = 0;\n};\n\nclass IDisk {\n virtual Future<Void> write(FileHandle fh, Offset off, Bytes data) = 0;\n virtual Future<Bytes> read(FileHandle fh, Offset off, Size len) = 0;\n virtual Future<Void> sync(FileHandle fh) = 0;\n};\n\nclass IClock {\n virtual double now() = 0;\n virtual Future<Void> delay(double seconds) = 0;\n};\n```\n\nEn producción, estos llaman syscalls reales. En simulación, se reemplazan con fakes que introducen delays controlados, fallas, y reordenamientos.\n\n### 3. Inyección de Fallas Basada en Seed\n\nDado un seed random, el simulador decide:\n\n- Cuándo disparar timers (con jitter)\n- Cuándo entregar o dropear paquetes de red\n- Cuándo inyectar errores de disco\n- Cuándo simular crashes y restarts de procesos\n- Cuándo crear y curar particiones de red\n\n```python\n# Pseudocódigo para el inyector de fallas de la simulación\nclass FaultInjector:\n def __init__(self, seed):\n self.rng = Random(seed)\n\n def should_drop_packet(self) -> bool:\n return self.rng.random() < 0.01 # 1% packet loss\n\n def network_delay_ms(self) -> float:\n return self.rng.exponential(scale=5.0) # avg 5ms de delay\n\n def should_inject_partition(self, time) -> bool:\n return self.rng.random() < (1.0 / 3600000.0) * time_step_ms\n\n def should_crash_node(self, node_id) -> bool:\n return self.rng.random() < 0.0001 # raro pero devastador\n\n def disk_write_should_fail(self) -> bool:\n return self.rng.random() < 0.001 # errores de disco ocasionales\n```\n\n## El Approach de TigerBeetle: VOPR\n\n[TigerBeetle](<https://tigerbeetle.com/>) (una base de datos de transacciones financieras escrita en Zig) tomó las ideas de FoundationDB y construyó VOPR, el simulador de Viewstamped Operation Replication. Lo que hace distintivo el approach de TigerBeetle es cuán profundamente la simulación está integrada en el codebase.\n\nEn TigerBeetle, la abstracción de I/O no es un afterthought, es el cimiento. Cada syscall pasa por ` IO `, que tiene dos implementaciones:\n\n```zig\n// I/O de producción, syscalls reales de io_uring\nconst IO = if (is_simulation)\n @import(\"io_simulation.zig\").IO\nelse\n @import(\"io_uring.zig\").IO;\n```\n\nEl I/O de simulación reemplaza io\\_uring con un event loop determinístico:\n\n```zig\n// I/O de simulación simplificado\npub const IO = struct {\n prng: std.rand.DefaultPrng,\n event_queue: PriorityQueue(Event),\n tick: u64,\n\n pub fn submit_write(self: *IO, buffer: []const u8, offset: u64) Completion {\n const delay = self.prng.random_int(u64) % 10 + 1; // 1-10 ticks\n const completion = Completion{\n .result = if (self.should_inject_fault())\n error.DiskError\n else\n .success,\n };\n self.event_queue.insert(.{\n .tick = self.tick + delay,\n .completion = completion,\n });\n return completion;\n }\n\n fn should_inject_fault(self: *IO) bool {\n return self.prng.random_int(u64) % 1000 == 0;\n }\n};\n```\n\nEl runner de VOPR ejecuta el cluster entero de TigerBeetle, múltiples réplicas, clientes, y la red, en un solo proceso:\n\n```zig\n// VOPR corre una simulación de cluster completo\npub fn run(seed: u64) !void {\n var prng = std.rand.DefaultPrng.init(seed);\n\n var cluster = Cluster.init(&prng, .{\n .replica_count = 6,\n .standby_count = 2,\n .client_count = 3,\n });\n\n var tick: u64 = 0;\n while (tick < 10_000_000) : (tick += 1) {\n if (prng.random_int(u64) % 100 == 0) {\n const fault = random_fault(&prng);\n cluster.inject_fault(fault);\n }\n\n cluster.tick();\n cluster.check_invariants();\n }\n}\n```\n\nLa llamada a ` check_invariants() ` es la clave. Después de cada tick de la simulación, no solo al final, TigerBeetle verifica que todas las propiedades de seguridad se mantengan: sin doble-gasto, sin transacciones perdidas, sin divergencia del estado committeado.\n\n## Construyendo Tu Propio Harness de Simulación\n\nEsta metodología puede aplicarse a proyectos de menor escala también. A continuación se presenta la arquitectura práctica:\n\n### Paso 1: Definí Tu Boundary de Determinismo\n\nListá todas las fuentes de no-determinismo en tu sistema:\n\n```\nFuentes de no-determinismo:\n ✅ Reloj del sistema → wrappear con interfaz Clock inyectable\n ✅ Números random → usar PRNG seeded en todos lados\n ✅ I/O de red → wrappear con interfaz Network inyectable\n ✅ I/O de disco → wrappear con interfaz Storage inyectable\n ✅ Scheduling de threads → usar event loop single-threaded en simulación\n ❌ Orden de asignación de memoria → generalmente determinístico, pero ojo con hash maps\n ❌ Manejo de señales → deshabilitar en simulación\n```\n\n### Paso 2: Construí la Capa de I/O Abstracta\n\n```go\n// Ejemplo en Go: I/O abstracto para una implementación de Raft\ntype Clock interface {\n Now() time.Time\n After(d time.Duration) <-chan time.Time\n}\n\ntype Network interface {\n Send(to NodeID, msg Message) error\n Receive() <-chan Message\n}\n\ntype Storage interface {\n Append(entries []LogEntry) error\n ReadRange(start, end uint64) ([]LogEntry, error)\n Sync() error\n}\n\n// Implementaciones de simulación son determinísticas\ntype SimClock struct {\n current time.Time\n}\nfunc (c *SimClock) Now() time.Time { return c.current }\nfunc (c *SimClock) Advance(d time.Duration) { c.current = c.current.Add(d) }\n```\n\n### Paso 3: El Runner de Simulación\n\n```go\ntype Simulator struct {\n seed int64\n rng *rand.Rand\n clock *SimClock\n network *SimNetwork\n nodes []*RaftNode\n events *PriorityQueue\n}\n\nfunc (s *Simulator) Run(ticks int) error {\n for i := 0; i < ticks; i++ {\n s.clock.Advance(time.Millisecond)\n s.maybeInjectFault()\n s.network.DeliverPending(s.rng)\n\n for _, node := range s.nodes {\n node.Tick()\n }\n\n if err := s.checkInvariants(); err != nil {\n return fmt.Errorf(\"invariante violada en tick %d (seed %d): %w\",\n i, s.seed, err)\n }\n }\n return nil\n}\n```\n\n### Paso 4: Integración con CI\n\n```yaml\nsimulation-test:\n strategy:\n matrix:\n seed-range: [\"0-100\", \"100-200\", \"200-300\", \"300-400\", \"400-500\"]\n steps:\n - run: |\n START=$(echo ${{ matrix.seed-range }} | cut -d- -f1)\n END=$(echo ${{ matrix.seed-range }} | cut -d- -f2)\n for seed in $(seq $START $END); do\n echo \"Corriendo simulación con seed $seed\"\n ./bin/simulation-test --seed=$seed --ticks=1000000\n done\n```\n\n## Bugs que Esta Metodología Ha Encontrado\n\nEn la práctica, el simulation testing consistentemente encuentra bugs en estas categorías:\n\n**Escrituras de líder stale**: Un nodo cree que es el líder pero fue depuesto. Acepta una escritura que se pierde cuando el log del líder real la sobrescribe. Solo pasa cuando el mensaje de deposición se delay-ea exactamente la cantidad justa.\n\n**Split-brain durante curación de partición**: Cuando una partición de red se cura, dos sub-clusters tienen estado divergente. El protocolo de reconciliación tiene una ventana donde ambos creen que tienen quórum.\n\n**Reordenamiento de escrituras de disco**: En hardware real, ` write(A); write(B); ` no garantiza que A se persista antes que B. La simulación inyecta reordenamiento de escrituras para encontrar suposiciones sobre el orden de persistencia.\n\n**Condiciones de carrera en timers**: Cuando dos timers se disparan en el mismo instante simulado, el orden importa. La simulación explora ambos ordenes para cada seed.\n\nUn harness de simulación puede encontrar bugs complejos de interleaving en segundos que tomarían semanas descubrir mediante debugging manual. El contraste entre semanas de investigación manual y segundos de ejecución de simulación subraya el valor de la metodología.\n\nPara sistemas que necesitan ser correctos bajo fallas, bases de datos, protocolos de consenso, procesadores de transacciones, locks distribuidos, el deterministic simulation testing no es opcional. Es la única metodología que sistemáticamente encuentra los bugs que más importan: los que corrompen datos.\n\n> Actualización editorial (10 de noviembre de 2026): se retiraron experiencias y resultados numéricos sin respaldo. Los ejemplos son didácticos.\n\n## Preguntas frecuentes\n\n### ¿En qué se diferencia el deterministic simulation testing del chaos engineering?\n\nEl chaos engineering (Chaos Monkey, Litmus, etc.) inyecta fallas en sistemas reales desplegados y observa resultados. Es no-determinístico, no es posible reproducir una falla exactamente. El simulation testing corre todo el sistema en un solo proceso con un scheduler falso, red falsa, y disco falso, controlados por un seed. Mismo seed = misma ejecución = mismo bug, siempre.\n\n### ¿Puedo aplicar simulation testing a un codebase existente?\n\nEs extremadamente difícil de hacer retrofit. El simulation testing requiere que TODAS las fuentes de no-determinismo (tiempo del sistema, threading, I/O, números random) estén abstraídas detrás de interfaces inyectables. La mayoría de los codebases existentes tienen no-determinismo incorporado en cada capa. Lo mejor es adoptarlo como constraint fundacional de diseño para sistemas nuevos.\n\n## Tips\n\n1. **Tip:** Comenzar haciendo tu capa de I/O inyectable. Wrappeá cada syscall, ` read() `, ` write() `, ` clock_gettime() `, ` recv() `, detrás de una interfaz. En modo simulación, reemplazalos con fakes determinísticos. Esta es la decisión arquitectural más importante para la testeabilidad.\n2. **Tip:** Cuando se encuentre un seed que falla, **nunca** arregles el bug antes de escribir un test de regresión dirigido que lo reproduce con ese seed exacto. Los seeds son efímeros si tu código de simulación cambia, asegurar la reproducción primero, después corregir.\n\n- simulación-determinística\n- testing\n- sistemas-distribuidos\n- tigerbeetle\n- foundationdb\n- inyección-de-fallas\n- correctitud\n- zig\n\n[Volver al Blog](<https://lucioduran.com/es/blog>)","headings":[{"level":1,"text":"Deterministic Simulation Testing: Metodología de Inyección de Fallas en TigerBeetle y FoundationDB","url":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#title"},{"level":2,"text":"Por Qué Testear Sistemas Distribuidos Es Tan Difícil","url":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#por-qu-testear-sistemas-distribuidos-es-tan-difcil"},{"level":2,"text":"La Metodología de FoundationDB","url":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#la-metodologa-de-foundationdb"},{"level":3,"text":"1. Core Single-Threaded, Event-Driven","url":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#1-core-single-threaded-event-driven"},{"level":3,"text":"2. Capa de I/O Abstracta","url":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#2-capa-de-io-abstracta"},{"level":3,"text":"3. Inyección de Fallas Basada en Seed","url":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#3-inyeccin-de-fallas-basada-en-seed"},{"level":2,"text":"El Approach de TigerBeetle: VOPR","url":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#el-approach-de-tigerbeetle-vopr"},{"level":2,"text":"Construyendo Tu Propio Harness de Simulación","url":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#construyendo-tu-propio-harness-de-simulacin"},{"level":3,"text":"Paso 1: Definí Tu Boundary de Determinismo","url":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#paso-1-defin-tu-boundary-de-determinismo"},{"level":3,"text":"Paso 2: Construí la Capa de I/O Abstracta","url":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#paso-2-constru-la-capa-de-io-abstracta"},{"level":3,"text":"Paso 3: El Runner de Simulación","url":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#paso-3-el-runner-de-simulacin"},{"level":3,"text":"Paso 4: Integración con CI","url":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#paso-4-integracin-con-ci"},{"level":2,"text":"Bugs que Esta Metodología Ha Encontrado","url":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#bugs-que-esta-metodologa-ha-encontrado"},{"level":2,"text":"Preguntas frecuentes","url":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#questions"},{"level":3,"text":"¿En qué se diferencia el deterministic simulation testing del chaos engineering?","url":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#question-1"},{"level":3,"text":"¿Puedo aplicar simulation testing a un codebase existente?","url":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#question-2"},{"level":2,"text":"Tips","url":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#article-tips"}],"sections":[{"heading":"Deterministic Simulation Testing: Metodología de Inyección de Fallas en TigerBeetle y FoundationDB","level":1,"url":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle","content":"Testing"},{"heading":"Deterministic Simulation Testing: Metodología de Inyección de Fallas en TigerBeetle y FoundationDB","level":1,"url":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#title","content":"2025-11-29Actualizado: 2026-11-108 min lectura\n\n[Lucio Durán](<https://lucioduran.com/es>)\nC.T.O. en LOi\n\nTambién disponible en: [English](<https://lucioduran.com/blog/deterministic-simulation-testing-tigerbeetle>)"},{"heading":"Por Qué Testear Sistemas Distribuidos Es Tan Difícil","level":2,"url":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#por-qu-testear-sistemas-distribuidos-es-tan-difcil","content":"El problema fundamental es el no-determinismo. Un sistema distribuido tiene múltiples fuentes:\n\n1. **Scheduling de threads**: El OS decide qué thread corre cuándo\n2. **Timing de red**: Los paquetes llegan en orden impredecible con latencia variable\n3. **Timing de I/O de disco**: Lecturas y escrituras se completan a velocidades variables\n4. **Skew de reloj**: Los relojes del sistema en diferentes máquinas derivan independientemente\n5. **Timing de fallas**: Crashes, particiones de red, y errores de disco pasan en momentos arbitrarios\n\nUn bug que requiere una combinación específica de estos eventos no-determinísticos podría tomar billones de ejecuciones de test para aparecer con testing random. Y aunque lo encuentres, no es posible reproducirlo porque no controlás el scheduler del OS, la red, ni el disco.\n\nLos integration tests tradicionales intentan manejar esto con sleeps y retries:\n\n```python\n# Así testea la mayoría la gente los sistemas distribuidos (no hagas esto)\ndef test_leader_election():\n cluster.start(3)\n time.sleep(5) # \"esperar a que complete la elección\" 🤞\n leader = cluster.get_leader()\n assert leader is not None\n\n cluster.kill(leader)\n time.sleep(10) # \"esperar a la re-elección\" 🤞🤞\n new_leader = cluster.get_leader()\n assert new_leader is not None\n assert new_leader != leader\n```"},{"heading":"La Metodología de FoundationDB","level":2,"url":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#la-metodologa-de-foundationdb","content":"[FoundationDB](<https://www.foundationdb.org/>) fue pionero en simulation testing en bases de datos de producción. Su insight clave: si controlás todas las fuentes de no-determinismo, es posible explorar todo el espacio de estados sistemáticamente.\n\nLa arquitectura tiene tres pilares:"},{"heading":"1. Core Single-Threaded, Event-Driven","level":3,"url":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#1-core-single-threaded-event-driven","content":"La base de datos entera corre en un solo thread (o múltiples threads determinísticos con scheduling explícito). No hay concurrencia a nivel del OS. Todas las operaciones asíncronas se modelan como eventos en una cola de prioridad:\n\n```\nCola de Eventos (ordenada por tiempo simulado):\n t=100ms: Nodo A recibe AppendEntries del Nodo B\n t=102ms: Timeout de elección del Nodo C se dispara\n t=103ms: Escritura de disco en Nodo A completa\n t=105ms: Timer de heartbeat del Nodo B se dispara\n t=108ms: Partición de red comienza (falla inyectada)\n```\n\nEl simulador saca eventos en orden, los ejecuta, y cualquier evento resultante vuelve a la cola. Porque la cola es determinística (mismo seed → mismo estado inicial → mismo orden de eventos), la ejecución es perfectamente reproducible."},{"heading":"2. Capa de I/O Abstracta","level":3,"url":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#2-capa-de-io-abstracta","content":"Cada interacción con el mundo exterior pasa por una interfaz:\n\n```cpp\n// El runtime flow de FoundationDB abstrae todo\nclass INetwork {\n virtual Future<Void> connect(NetworkAddress addr) = 0;\n virtual Future<Void> send(Connection conn, Bytes data) = 0;\n virtual Future<Bytes> receive(Connection conn) = 0;\n};\n\nclass IDisk {\n virtual Future<Void> write(FileHandle fh, Offset off, Bytes data) = 0;\n virtual Future<Bytes> read(FileHandle fh, Offset off, Size len) = 0;\n virtual Future<Void> sync(FileHandle fh) = 0;\n};\n\nclass IClock {\n virtual double now() = 0;\n virtual Future<Void> delay(double seconds) = 0;\n};\n```\n\nEn producción, estos llaman syscalls reales. En simulación, se reemplazan con fakes que introducen delays controlados, fallas, y reordenamientos."},{"heading":"3. Inyección de Fallas Basada en Seed","level":3,"url":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#3-inyeccin-de-fallas-basada-en-seed","content":"Dado un seed random, el simulador decide:\n\n- Cuándo disparar timers (con jitter)\n- Cuándo entregar o dropear paquetes de red\n- Cuándo inyectar errores de disco\n- Cuándo simular crashes y restarts de procesos\n- Cuándo crear y curar particiones de red\n\n```python\n# Pseudocódigo para el inyector de fallas de la simulación\nclass FaultInjector:\n def __init__(self, seed):\n self.rng = Random(seed)\n\n def should_drop_packet(self) -> bool:\n return self.rng.random() < 0.01 # 1% packet loss\n\n def network_delay_ms(self) -> float:\n return self.rng.exponential(scale=5.0) # avg 5ms de delay\n\n def should_inject_partition(self, time) -> bool:\n return self.rng.random() < (1.0 / 3600000.0) * time_step_ms\n\n def should_crash_node(self, node_id) -> bool:\n return self.rng.random() < 0.0001 # raro pero devastador\n\n def disk_write_should_fail(self) -> bool:\n return self.rng.random() < 0.001 # errores de disco ocasionales\n```"},{"heading":"El Approach de TigerBeetle: VOPR","level":2,"url":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#el-approach-de-tigerbeetle-vopr","content":"[TigerBeetle](<https://tigerbeetle.com/>) (una base de datos de transacciones financieras escrita en Zig) tomó las ideas de FoundationDB y construyó VOPR, el simulador de Viewstamped Operation Replication. Lo que hace distintivo el approach de TigerBeetle es cuán profundamente la simulación está integrada en el codebase.\n\nEn TigerBeetle, la abstracción de I/O no es un afterthought, es el cimiento. Cada syscall pasa por ` IO `, que tiene dos implementaciones:\n\n```zig\n// I/O de producción, syscalls reales de io_uring\nconst IO = if (is_simulation)\n @import(\"io_simulation.zig\").IO\nelse\n @import(\"io_uring.zig\").IO;\n```\n\nEl I/O de simulación reemplaza io\\_uring con un event loop determinístico:\n\n```zig\n// I/O de simulación simplificado\npub const IO = struct {\n prng: std.rand.DefaultPrng,\n event_queue: PriorityQueue(Event),\n tick: u64,\n\n pub fn submit_write(self: *IO, buffer: []const u8, offset: u64) Completion {\n const delay = self.prng.random_int(u64) % 10 + 1; // 1-10 ticks\n const completion = Completion{\n .result = if (self.should_inject_fault())\n error.DiskError\n else\n .success,\n };\n self.event_queue.insert(.{\n .tick = self.tick + delay,\n .completion = completion,\n });\n return completion;\n }\n\n fn should_inject_fault(self: *IO) bool {\n return self.prng.random_int(u64) % 1000 == 0;\n }\n};\n```\n\nEl runner de VOPR ejecuta el cluster entero de TigerBeetle, múltiples réplicas, clientes, y la red, en un solo proceso:\n\n```zig\n// VOPR corre una simulación de cluster completo\npub fn run(seed: u64) !void {\n var prng = std.rand.DefaultPrng.init(seed);\n\n var cluster = Cluster.init(&prng, .{\n .replica_count = 6,\n .standby_count = 2,\n .client_count = 3,\n });\n\n var tick: u64 = 0;\n while (tick < 10_000_000) : (tick += 1) {\n if (prng.random_int(u64) % 100 == 0) {\n const fault = random_fault(&prng);\n cluster.inject_fault(fault);\n }\n\n cluster.tick();\n cluster.check_invariants();\n }\n}\n```\n\nLa llamada a ` check_invariants() ` es la clave. Después de cada tick de la simulación, no solo al final, TigerBeetle verifica que todas las propiedades de seguridad se mantengan: sin doble-gasto, sin transacciones perdidas, sin divergencia del estado committeado."},{"heading":"Construyendo Tu Propio Harness de Simulación","level":2,"url":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#construyendo-tu-propio-harness-de-simulacin","content":"Esta metodología puede aplicarse a proyectos de menor escala también. A continuación se presenta la arquitectura práctica:"},{"heading":"Paso 1: Definí Tu Boundary de Determinismo","level":3,"url":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#paso-1-defin-tu-boundary-de-determinismo","content":"Listá todas las fuentes de no-determinismo en tu sistema:\n\n```\nFuentes de no-determinismo:\n ✅ Reloj del sistema → wrappear con interfaz Clock inyectable\n ✅ Números random → usar PRNG seeded en todos lados\n ✅ I/O de red → wrappear con interfaz Network inyectable\n ✅ I/O de disco → wrappear con interfaz Storage inyectable\n ✅ Scheduling de threads → usar event loop single-threaded en simulación\n ❌ Orden de asignación de memoria → generalmente determinístico, pero ojo con hash maps\n ❌ Manejo de señales → deshabilitar en simulación\n```"},{"heading":"Paso 2: Construí la Capa de I/O Abstracta","level":3,"url":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#paso-2-constru-la-capa-de-io-abstracta","content":"```go\n// Ejemplo en Go: I/O abstracto para una implementación de Raft\ntype Clock interface {\n Now() time.Time\n After(d time.Duration) <-chan time.Time\n}\n\ntype Network interface {\n Send(to NodeID, msg Message) error\n Receive() <-chan Message\n}\n\ntype Storage interface {\n Append(entries []LogEntry) error\n ReadRange(start, end uint64) ([]LogEntry, error)\n Sync() error\n}\n\n// Implementaciones de simulación son determinísticas\ntype SimClock struct {\n current time.Time\n}\nfunc (c *SimClock) Now() time.Time { return c.current }\nfunc (c *SimClock) Advance(d time.Duration) { c.current = c.current.Add(d) }\n```"},{"heading":"Paso 3: El Runner de Simulación","level":3,"url":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#paso-3-el-runner-de-simulacin","content":"```go\ntype Simulator struct {\n seed int64\n rng *rand.Rand\n clock *SimClock\n network *SimNetwork\n nodes []*RaftNode\n events *PriorityQueue\n}\n\nfunc (s *Simulator) Run(ticks int) error {\n for i := 0; i < ticks; i++ {\n s.clock.Advance(time.Millisecond)\n s.maybeInjectFault()\n s.network.DeliverPending(s.rng)\n\n for _, node := range s.nodes {\n node.Tick()\n }\n\n if err := s.checkInvariants(); err != nil {\n return fmt.Errorf(\"invariante violada en tick %d (seed %d): %w\",\n i, s.seed, err)\n }\n }\n return nil\n}\n```"},{"heading":"Paso 4: Integración con CI","level":3,"url":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#paso-4-integracin-con-ci","content":"```yaml\nsimulation-test:\n strategy:\n matrix:\n seed-range: [\"0-100\", \"100-200\", \"200-300\", \"300-400\", \"400-500\"]\n steps:\n - run: |\n START=$(echo ${{ matrix.seed-range }} | cut -d- -f1)\n END=$(echo ${{ matrix.seed-range }} | cut -d- -f2)\n for seed in $(seq $START $END); do\n echo \"Corriendo simulación con seed $seed\"\n ./bin/simulation-test --seed=$seed --ticks=1000000\n done\n```"},{"heading":"Bugs que Esta Metodología Ha Encontrado","level":2,"url":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#bugs-que-esta-metodologa-ha-encontrado","content":"En la práctica, el simulation testing consistentemente encuentra bugs en estas categorías:\n\n**Escrituras de líder stale**: Un nodo cree que es el líder pero fue depuesto. Acepta una escritura que se pierde cuando el log del líder real la sobrescribe. Solo pasa cuando el mensaje de deposición se delay-ea exactamente la cantidad justa.\n\n**Split-brain durante curación de partición**: Cuando una partición de red se cura, dos sub-clusters tienen estado divergente. El protocolo de reconciliación tiene una ventana donde ambos creen que tienen quórum.\n\n**Reordenamiento de escrituras de disco**: En hardware real, ` write(A); write(B); ` no garantiza que A se persista antes que B. La simulación inyecta reordenamiento de escrituras para encontrar suposiciones sobre el orden de persistencia.\n\n**Condiciones de carrera en timers**: Cuando dos timers se disparan en el mismo instante simulado, el orden importa. La simulación explora ambos ordenes para cada seed.\n\nUn harness de simulación puede encontrar bugs complejos de interleaving en segundos que tomarían semanas descubrir mediante debugging manual. El contraste entre semanas de investigación manual y segundos de ejecución de simulación subraya el valor de la metodología.\n\nPara sistemas que necesitan ser correctos bajo fallas, bases de datos, protocolos de consenso, procesadores de transacciones, locks distribuidos, el deterministic simulation testing no es opcional. Es la única metodología que sistemáticamente encuentra los bugs que más importan: los que corrompen datos.\n\n> Actualización editorial (10 de noviembre de 2026): se retiraron experiencias y resultados numéricos sin respaldo. Los ejemplos son didácticos."},{"heading":"Preguntas frecuentes","level":2,"url":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#questions","content":""},{"heading":"¿En qué se diferencia el deterministic simulation testing del chaos engineering?","level":3,"url":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#question-1","content":"El chaos engineering (Chaos Monkey, Litmus, etc.) inyecta fallas en sistemas reales desplegados y observa resultados. Es no-determinístico, no es posible reproducir una falla exactamente. El simulation testing corre todo el sistema en un solo proceso con un scheduler falso, red falsa, y disco falso, controlados por un seed. Mismo seed = misma ejecución = mismo bug, siempre."},{"heading":"¿Puedo aplicar simulation testing a un codebase existente?","level":3,"url":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#question-2","content":"Es extremadamente difícil de hacer retrofit. El simulation testing requiere que TODAS las fuentes de no-determinismo (tiempo del sistema, threading, I/O, números random) estén abstraídas detrás de interfaces inyectables. La mayoría de los codebases existentes tienen no-determinismo incorporado en cada capa. Lo mejor es adoptarlo como constraint fundacional de diseño para sistemas nuevos."},{"heading":"Tips","level":2,"url":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#article-tips","content":"1. **Tip:** Comenzar haciendo tu capa de I/O inyectable. Wrappeá cada syscall, ` read() `, ` write() `, ` clock_gettime() `, ` recv() `, detrás de una interfaz. En modo simulación, reemplazalos con fakes determinísticos. Esta es la decisión arquitectural más importante para la testeabilidad.\n2. **Tip:** Cuando se encuentre un seed que falla, **nunca** arregles el bug antes de escribir un test de regresión dirigido que lo reproduce con ese seed exacto. Los seeds son efímeros si tu código de simulación cambia, asegurar la reproducción primero, después corregir.\n\n- simulación-determinística\n- testing\n- sistemas-distribuidos\n- tigerbeetle\n- foundationdb\n- inyección-de-fallas\n- correctitud\n- zig\n\n[Volver al Blog](<https://lucioduran.com/es/blog>)"}],"links":[{"text":"Lucio Durán","url":"https://lucioduran.com/es"},{"text":"English","url":"https://lucioduran.com/blog/deterministic-simulation-testing-tigerbeetle"},{"text":"FoundationDB","url":"https://www.foundationdb.org/"},{"text":"TigerBeetle","url":"https://tigerbeetle.com/"},{"text":"Volver al Blog","url":"https://lucioduran.com/es/blog"}],"structuredData":[{"@context":"https://schema.org","@graph":[{"@type":"Person","@id":"https://lucioduran.com/#person","name":"Lucio Durán","url":"https://lucioduran.com/"},{"@type":"WebSite","@id":"https://lucioduran.com/#website","url":"https://lucioduran.com/","name":"Lucio Durán","inLanguage":["en","es"],"publisher":{"@id":"https://lucioduran.com/#person"}},{"@type":"WebPage","@id":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#page","url":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle","name":"Deterministic Simulation Testing: Metodología de Inyección de Fallas en TigerBeetle y FoundationDB | Lucio Durán","description":"Inyección de fallas basada en seeds, scheduling determinístico, y la metodología detrás de encontrar bugs imposibles en sistemas distribuidos. Técnicas reales de los frameworks de testing de TigerBeetle y FoundationDB.","inLanguage":"es","author":{"@id":"https://lucioduran.com/#person"},"publisher":{"@id":"https://lucioduran.com/#person"},"isPartOf":{"@id":"https://lucioduran.com/#website"},"isAccessibleForFree":true,"datePublished":"2025-11-29","dateModified":"2026-11-10","mainEntity":{"@id":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#article"},"breadcrumb":{"@id":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#breadcrumb"}},{"@type":"BlogPosting","@id":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#article","mainEntityOfPage":{"@id":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#page"},"headline":"Deterministic Simulation Testing: Metodología de Inyección de Fallas en TigerBeetle y FoundationDB","description":"Inyección de fallas basada en seeds, scheduling determinístico, y la metodología detrás de encontrar bugs imposibles en sistemas distribuidos. Técnicas reales de los frameworks de testing de TigerBeetle y FoundationDB.","datePublished":"2025-11-29","dateModified":"2026-11-10","author":{"@type":"Person","@id":"https://lucioduran.com/#person","name":"Lucio Durán","url":"https://lucioduran.com"},"publisher":{"@id":"https://lucioduran.com/#person"},"url":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle","inLanguage":"es","isPartOf":{"@id":"https://lucioduran.com/es/blog#blog"},"articleSection":"Testing","keywords":"simulación-determinística, testing, sistemas-distribuidos, tigerbeetle, foundationdb, inyección-de-fallas, correctitud, zig","wordCount":1467,"timeRequired":"PT8M","workTranslation":[{"@type":"BlogPosting","@id":"https://lucioduran.com/blog/deterministic-simulation-testing-tigerbeetle#article","inLanguage":"en","url":"https://lucioduran.com/blog/deterministic-simulation-testing-tigerbeetle"}],"isAccessibleForFree":true},{"@type":"Blog","@id":"https://lucioduran.com/es/blog#blog","url":"https://lucioduran.com/es/blog","name":"Blog","inLanguage":"es","author":{"@type":"Person","@id":"https://lucioduran.com/#person","name":"Lucio Durán","url":"https://lucioduran.com/"},"publisher":{"@id":"https://lucioduran.com/#person"},"isPartOf":{"@id":"https://lucioduran.com/#website"}},{"@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Inicio","item":"https://lucioduran.com/es"},{"@type":"ListItem","position":2,"name":"Blog","item":"https://lucioduran.com/es/blog"},{"@type":"ListItem","position":3,"name":"Deterministic Simulation Testing: Metodología de Inyección de Fallas en TigerBeetle y FoundationDB","item":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle"}],"@id":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#breadcrumb"},{"@type":"FAQPage","mainEntity":[{"@type":"Question","name":"¿En qué se diferencia el deterministic simulation testing del chaos engineering?","acceptedAnswer":{"@type":"Answer","text":"El chaos engineering (Chaos Monkey, Litmus, etc.) inyecta fallas en sistemas reales desplegados y observa resultados. Es no-determinístico, no es posible reproducir una falla exactamente. El simulation testing corre todo el sistema en un solo proceso con un scheduler falso, red falsa, y disco falso, controlados por un seed. Mismo seed = misma ejecución = mismo bug, siempre."}},{"@type":"Question","name":"¿Puedo aplicar simulation testing a un codebase existente?","acceptedAnswer":{"@type":"Answer","text":"Es extremadamente difícil de hacer retrofit. El simulation testing requiere que TODAS las fuentes de no-determinismo (tiempo del sistema, threading, I/O, números random) estén abstraídas detrás de interfaces inyectables. La mayoría de los codebases existentes tienen no-determinismo incorporado en cada capa. Lo mejor es adoptarlo como constraint fundacional de diseño para sistemas nuevos."}}],"@id":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#questions","url":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#questions","inLanguage":"es","isPartOf":{"@id":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle#page"},"author":{"@id":"https://lucioduran.com/#person"}}]}],"contentHash":"bf7aac9c4f23f8719199abc95d9ade299dcdafe26580279b3d6c29ad63a02d1e","provenance":{"method":"Deterministic serialization of the server-rendered main element","source":"https://lucioduran.com/es/blog/deterministic-simulation-testing-tigerbeetle","hashAlgorithm":"sha256","hashScope":"JSON document fields before contentHash and provenance","sourceHTMLBytes":72866,"contentBytes":12759}}
