Jepsen halla 96 fallos de corrección en ParticleDB
ParticleDB prometía ser "Full ACID" con 159.000 transacciones por segundo en la portada de su sitio. Jepsen, la consultora que Kyle Kingsbury dedica a destrozar bases de datos distribuidas con pruebas de caos, encontró 96 fallos de corrección entre las versiones 1.1.4 y 1.1.18.11.rc-22 de la base de datos ParticleDB. La mayoría de esos fallos aparecía con un solo servidor, un solo cliente y cero fallas de red.
El reporte, publicado el 31 de octubre de 2026 y financiado por Orkes, la empresa detrás del motor, es el típico análisis Jepsen: mezcla hallazgos cómicos (filas parcialmente invisibles que no se podían borrar, tablas eliminadas que reaparecían distorsionadas) con otros serios, como pérdida de escrituras confirmadas incluso en el modo de aislamiento serializable, el más estricto que ofrece el motor.
TL;DR
- Jepsen halló 96 fallos en la base de datos ParticleDB, de Orkes, entre las versiones 1.1.4 y 1.1.18.11.rc-22- El modo serializable, el más estricto de ParticleDB, permitió anomalías internas y Lost Update- La configuración groupsync perdía escrituras confirmadas pese a anunciar la misma durabilidad que sync- Orkes lanzó la versión 1.1.19-rc.13 y afirma haber resuelto todos los fallos restantes- Su propia portada prometía "Full ACID" aunque el texto debajo describía snapshot isolation
Qué es la base de datos ParticleDB
La base de datos ParticleDB es un motor multimodal que Orkes construyó para cargas de inteligencia artificial: combina SQL transaccional, almacenamiento clave-valor, búsqueda vectorial y analítica en vivo mediante funciones definidas por el usuario llamadas Particles. Escrita en Rust, expone protocolos Postgres, Redis, gRPC y HTTP2 para integrarse con sistemas existentes.
El objetivo declarado es reemplazar el típico mosaico de cachés, colas y almacenes separados que usan las aplicaciones de IA actuales. Orkes afirma que ParticleDB es órdenes de magnitud más rápido que Postgres mientras conserva las mismas garantías de seguridad transaccional.
Qué pasó
Orkes contrató a Jepsen para auditar las versiones refactorizadas de ParticleDB antes de un lanzamiento más amplio. El resultado fueron 96 problemas, desde los pedestres (caídas del servidor, deadlocks, pérdida de datos) hasta los más curiosos: filas parcialmente invisibles que no se podían eliminar, consultas WHERE 4 = 5 que igual devolvían filas, tablas borradas que reaparecían con forma distinta, columnas confundidas entre sí y strings que se convertían en enteros.
La mayoría de esos 96 casos no necesitó un clúster bajo estrés: bastó un solo servidor, un solo cliente y ninguna falla de red para reproducirlos. Para la versión 1.1.18.11.rc-22, Orkes había resuelto todos los problemas menos diez. Pero entre esos diez quedaban los más graves: ParticleDB seguía perdiendo escrituras confirmadas durante operación normal con occ, su modo de transacciones por defecto, y permitía anomalías internas y Lost Update incluso con serializable, el ajuste más fuerte disponible.
Orkes ya respondió. Según el propio reporte de Jepsen, la empresa lanzó la versión 1.1.19-rc.13 y asegura que ahí quedan resueltos todos los problemas que seguían abiertos.
Jepsen reprodujo la mayoría de los 96 fallos con un solo servidor, un solo cliente y cero fallas de red.
Contexto e historia
Orkes no construyó ParticleDB como un producto aislado. La empresa opera Conductor, una plataforma de orquestación de flujos de trabajo que originalmente guardaba su estado en Redis y Postgres. Para mejorar el rendimiento, Orkes decidió escribir su propia base de datos en lugar de seguir combinando dos motores distintos.
La versión 1.0.0 de ParticleDB tenía un set de funciones limitado, pensado específicamente para las consultas de Conductor, y se desplegó como backend de algunos clústeres de producción. Varios clientes de Orkes siguieron el mismo camino. Antes de un lanzamiento más amplio al público, Orkes hizo un refactor mayor: agregó un protocolo de cable propio, más tipos de datos y consultas OLAP adicionales, y lanzó esas versiones de forma silenciosa en particledb.ai. Jepsen empezó a probar justamente esas versiones refactorizadas, a partir de la 1.1.4.
Un detalle que el reporte deja claro: ParticleDB también ofrece un clúster replicado usando el protocolo de consenso Raft, pero esa función solo está disponible en despliegues de nube empresarial. Las builds que Orkes entregó a Jepsen no soportaban replicación, así que todo lo analizado corresponde únicamente a comportamiento de un solo nodo.
El mecanismo detrás de los fallos
La página de inicio de ParticleDB aseguraba estar "formalmente probada" con una especificación en TLA+. Durante la colaboración con Jepsen, sin embargo, no existía ninguna especificación TLA+ disponible, así que no queda claro qué propiedades se habrían demostrado ni si describían al ParticleDB real.
La mayoría de los motores SQL controlan el nivel de aislamiento de una transacción con la instrucción SET TRANSACTION ISOLATION LEVEL. ParticleDB la ignora o la rechaza. A veces le dice al usuario que use READ COMMITTED, un nivel relativamente débil; otras veces acepta el pedido de otro nivel y lo ignora en silencio, sin avisar.
En cambio, el aislamiento real depende de un flag de arranque, --txn-mode, con cuatro modos documentados:
ModoComportamientoCuándo usarlofastSin locks; commit al escribirCargas masivas, benchmarks de un solo escritorocc (default)Overlay más validación OCC al hacer commitOLTP general, pensado para ser seguro bajo concurrenciazero-copy-occOCC anclado a partición; transacciones de una sola partición evitan coordinación cruzadaOLTP de alto rendimiento sobre cargas afines a particióntable-2plLocks exclusivos de tabla al primer toqueSerialización más estricta, semántica simple
El problema es que la documentación nunca explicó qué garantizaba cada modo en términos formales. ¿fast equivalía a Read Atomic, a Read Uncommitted o a otra cosa? Jepsen tampoco pudo confirmarlo del todo con occ, el modo que usa cualquier instalación nueva por defecto.
La confusión se repite con la durabilidad del Write-Ahead Log. ParticleDB ofrece tres tácticas: sync hace fsync en cada commit, groupsync agrupa varias transacciones en un solo fsync, y nosync no hace fsync en absoluto. La documentación del motor de transacciones dice que sync ofrece "mayor durabilidad" que groupsync.
$ particledb start --help
--txn-mode groupsync # "COMMIT waits for flush"
Pero la ayuda de línea de comandos asegura que groupsync garantiza que "COMMIT espera al flush", y la guía interna de operación de Orkes coincide: según ese documento, groupsync ofrece "la misma durabilidad que sync". Tres fuentes oficiales, tres versiones distintas de la misma garantía.
> ⚠️ Ojo: la portada de ParticleDB decía literalmente "Full ACID. 159K TPS." y, justo debajo, el texto describía "snapshot isolation", un nivel de aislamiento que por definición no es completamente serializable.
Impacto y análisis
Para equipos que evalúan la base de datos ParticleDB en producción, el reporte es una señal de alerta clara. No es que ParticleDB no sirva: la mayoría de los 96 fallos ya estaban resueltos para la última versión probada. El problema es que los que quedaban afectaban justo el modo por defecto (occ) y el modo que, en teoría, ofrece más garantías (serializable).
Jepsen aclara que financió el estudio la propia Orkes, siguiendo la política de ética de Jepsen, que exige publicar los resultados sin filtrar hallazgos negativos. Eso le da credibilidad al informe, aunque también explica por qué Orkes respondió tan rápido con una nueva versión.
La limitación más importante del estudio es que solo cubre comportamiento de un solo nodo. ParticleDB vende replicación vía Raft para sus clientes de nube empresarial, pero esa parte del sistema nunca se probó porque las builds entregadas a Jepsen no la soportaban. Cualquier equipo que piense correr un clúster replicado en producción debería pedir una auditoría separada antes de confiar en esa capa.
Qué sigue
Orkes ya lanzó la versión 1.1.19-rc.13 y sostiene que resuelve todos los fallos que seguían abiertos al cierre del reporte. Jepsen no confirma de forma independiente esa afirmación en el documento publicado, así que por ahora es la palabra de Orkes contra sus propios resultados anteriores.
Para cualquier equipo que ya use o esté evaluando ParticleDB, el paso práctico es elegir --txn-mode con el mismo cuidado que recomienda la propia documentación del proyecto: la mejor práctica publicada advierte que el modo "cambia la corrección y el rendimiento más que el texto SQL", así que no conviene dejarlo en el valor por defecto sin entender qué cubre.
📖 Resumen en Telegram: Ver resumen
Probalo vos: antes de poner la base de datos ParticleDB en producción, corré tu propio set de pruebas de concurrencia con el --txn-mode que pensás usar y compará el resultado contra el reporte de Jepsen en jepsen.io.
El modo serializable, el más estricto que ofrece ParticleDB, también mostró Lost Update según Jepsen.
Preguntas frecuentes
¿ParticleDB cumple de verdad con ACID?
No de forma completa en las versiones auditadas. Jepsen encontró pérdida de escrituras confirmadas en el modo por defecto y anomalías incluso en serializable, el nivel más estricto que ofrece ParticleDB.
¿Qué modo de transacción de ParticleDB es más seguro?
Según la documentación, table-2pl ofrece la serialización más estricta con locks exclusivos de tabla, aunque el reporte de Jepsen no confirma que ningún modo esté completamente libre de fallos en todas las versiones probadas.
¿ParticleDB soporta replicación?
Sí, mediante el protocolo Raft, pero solo en despliegues de nube empresarial. Jepsen no evaluó esa capa porque las builds que usó no la incluían.
¿Qué es Orkes y por qué construyó ParticleDB?
Orkes es la empresa detrás de Conductor, una plataforma de orquestación de flujos de trabajo. Construyó ParticleDB para reemplazar la combinación de Redis y Postgres que usaba Conductor y mejorar el rendimiento.
¿Ya se resolvieron los fallos que halló Jepsen en ParticleDB?
Orkes dice que la versión 1.1.19-rc.13 resuelve todos los problemas que seguían abiertos al cierre del reporte, aunque Jepsen no lo confirma de forma independiente en el documento publicado.
Referencias
- Jepsen: ParticleDB 1.1.4: el reporte original de Kyle Kingsbury con los 96 fallos encontrados.- Jepsen en GitHub: el framework y la metodología que usa Jepsen para auditar bases de datos distribuidas.- Raft Consensus Algorithm: el protocolo de replicación que ParticleDB ofrece en su nube empresarial.- Conductor OSS: la plataforma de orquestación de Orkes que originó el desarrollo de ParticleDB.- ACID (Wikipedia): definición de las propiedades de atomicidad, consistencia, aislamiento y durabilidad.
📱 ¿Te gusta este contenido? Únete a nuestro canal de Telegram @programacion donde publicamos a diario lo más relevante de tecnología, IA y desarrollo. Resúmenes rápidos, contenido fresco todos los días.
I wanted Jepsen's Elle in CI without a JVM, so I wrote adya
Your database documentation says REPEATABLE READ. Your code assumes it. Have you ever checked?
I spent the last stretch building adya, a black-box checker for transactional isolation. You give it a log of transactions a database ran, and it tells you which isolation guarantees held. For each one that didn't, it prints the transactions involved and the chain of reads and writes that proves the violation.
It's an independent Rust implementation of the approach from Elle (Kingsbury and Alvaro, VLDB 2020), the checker behind Jepsen's database analyses. If you know Elle, adya reads the same history formats and takes the same flags. If you don't, keep reading.
The problem with "it passed the tests"
Isolation bugs don't show up in unit tests. They need two or more transactions to interleave in one particular way, and when they happen nothing crashes. You just get a balance that's off, or two people booked into the same seat.
The classic example is write skew. Two doctors are on call, and the rule says at least one must stay on call. Each doctor's transaction checks "is the other one still on call?", sees yes, and takes themselves off. Both commit. Now nobody is on call.
Snapshot isolation allows this. Postgres's REPEATABLE READ is snapshot isolation, so it allows this. If you thought REPEATABLE READ meant "safe enough", the bug ships.
Elle's insight was that you can catch this from the outside. Record what every client asked for and what it got back, infer the dependencies between transactions, and look for cycles that a given isolation level forbids. Atul Adya's 1999 thesis catalogued those cycles (G0, G1c, G-single, G2 and so on), which is where the name comes from.
Why another implementation
Elle is excellent. It's also a Clojure library on the JVM. People who want it outside a Jepsen test end up shelling out to elle-cli from a Go or Python harness. I found two open-source projects doing exactly that in CI (barn, bytecaskdb), and the bytecaskdb PR lists what went wrong: a blocked Clojars mirror, a crash when graphviz was missing, log lines corrupting the JSON output.
On my Windows machine, elle-cli 0.1.11 hung with no output on every anomalous history I gave it. On Linux it worked, but in my CI comparison it needed more than five minutes on five of forty random 300-transaction histories, and ran out of a 6 GB heap on one more. adya checked all forty in 0.2 seconds.
Elle also leaves the workload to you. You have to write the client that generates transactions, runs them against your database and records the history. That's the part most people never get to.
So adya ships both halves in one binary:
cargo install adya
# Run a workload against Postgres at REPEATABLE READ,
# then check the history against serializability.
adya run postgres --url postgres://localhost/test -i repeatable-read -c serializable
What it looks like
You don't need a database to try it. adya has a built-in simulated database that implements isolation levels the textbook way:
$ adya run sim -i snapshot-isolation -c serializable -n 300 -p 4 --seed 1
history.jsonl false
G2-item #0
Let:
T234 = {"index":234,"process":1,"type":"ok","value":[["r",10,[4]],["append",3,6],["r",3,[4,5,6]]]}
T237 = {"index":237,"process":3,"type":"ok","value":[["append",9,13],["append",10,5],["r",3,[4,5]],["r",10,[4,5]]]}
Then:
- T234 < T237, because T234 did not observe T237's append of 5 to 10.
- However, T237 < T234, because T237 did not observe T234's append of 6 to 3: a contradiction!
That's the doctors problem with list keys. T234 read key 10 before T237 appended to it, so T234 has to come first. T237 read key 3 before T234 appended to it, so T237 has to come first. Both can't be true, so no serial order exists. Snapshot isolation allows that cycle. Serializability doesn't.
How it works
The trick that makes this tractable is the workload. adya's default, borrowed from Elle, is list-append: every key holds a list, transactions append unique numbers to lists and read whole lists back. Each read then tells you the order of every append before it. If one client read [1, 2] and another read [1, 2, 5], you know 5 came after 2, without asking the database anything.
From those orders adya builds a dependency graph over transactions, using Adya's three edge types:
- ww: T2 appended right after T1's append to the same key.
- wr: T2 read a list ending in T1's append.
- rw: T1 read a list that T2 later appended to, so T1 didn't see T2's write.
If you're checking a model with real-time guarantees (strict serializability), it adds edges for "T1 finished before T2 started", using a transitive reduction so the graph stays roughly linear in the history.
Then it looks for cycles, one strongly connected component at a time. Each anomaly class is a cycle with constraints. G-single has exactly one rw edge. G2-item has at least two, with two of them adjacent. G1c has no rw edges and at least one wr. Instead of enumerating cycles and classifying them afterwards, adya runs a breadth-first search over pairs of (transaction, path state). The path state is seven bits: how many rw edges so far (0, 1, or 2+), whether the last edge was rw, whether the first one was, whether two were adjacent, whether it has seen a wr, and whether it has used a real-time edge. One BFS then returns the shortest cycle of exactly the shape you asked for.
Before searching, it runs cheaper existence checks. For each model it asks whether the subgraph that model forbids cycles in has a nontrivial SCC at all. If the ww/wr-plus-one-rw subgraph is acyclic, snapshot isolation holds as far as cycles go, and adya skips every search that could only find SI violations. It also searches the most severe anomalies first and skips anything they imply. A 100,000-transaction history (30 MB of JSON) checks in about 1.6 seconds on my laptop.
Is it right?
A checker that's wrong is worse than none, so this took most of the work.
1. Elle's own expected results. elle-cli's test suite ships 56 list-append and rw-register histories along with the JSON verdicts Elle produced for them. adya matches all 56: same verdict, same anomaly types, and the same weakest models ruled out. Getting there taught me two Elle details I would never have guessed. It labels an edge that carries several relations by a fixed priority (ww before wr before rw before real-time), but tests whether a cycle exists using any relation the edge carries. And it counts a composed edge that loops back to the same transaction as a cycle.
2. Elle itself, live. On every push, CI generates random histories with adya's simulator and checks each with both tools. In the first full run Elle finished 34 of 40, and adya agreed on all 34.
3. Databases with known answers. The simulator implements serializable, snapshot isolation, read committed (with write locks), read uncommitted, and a deliberately broken "snapshot" that writes back stale state. Tests assert that correct runs produce zero anomalies at their own level and the expected ones above it. That test caught a bug in my simulator, not in the checker: my first read committed took no write locks, which is weaker than any real database.
What Postgres and MySQL did
CI runs 4,000 transactions from 10 clients over 6 hot keys against Postgres 17 and MySQL 8.4, at each isolation level, and checks every history against a ladder of models. List-append results:
| database, level | serializable | snapshot isolation | read committed |
|---|---|---|---|
| Postgres READ COMMITTED | G-single, G2-item, internal, lost update | G-single, internal, lost update | valid |
| Postgres REPEATABLE READ | G2-item | valid | valid |
| Postgres SERIALIZABLE | valid | valid | valid |
| MySQL REPEATABLE READ | G-single, G2-item, internal, lost update | G-single, internal, lost update | valid |
| MySQL SERIALIZABLE | valid | valid | valid |
Postgres did what its docs say. REPEATABLE READ is snapshot isolation, write skew included, and SERIALIZABLE came out strict serializable. I also restarted Postgres every few seconds during a 6,000-transaction SERIALIZABLE run (--fault "docker restart -t 0 pg"). Nine restarts, 2,957 commits, 3,043 failures, and the history still checked clean.
MySQL's REPEATABLE READ is weaker than its name. It lets a transaction lose another's update and see part of another transaction's writes, so it isn't snapshot isolation. Jepsen reported the same thing about MySQL 8.0.34 in 2023. adya reproduces it from a cold start in a few seconds.
Testing your own database
The built-in drivers cover SQLite, Postgres and MySQL. For anything else there's adya run exec: adya starts your client once per process, writes one JSON line per transaction to its stdin, and reads back one line saying what happened.
{"value":[["append",3,7],["r",4,null]]}
{"type":"ok","value":[["append",3,7],["r",4,[1,7]]]}
The repo has a 59-line Python client for SQLite as a template. Swap the SQL and you're testing your own database.
Limits
adya covers list-append and read-write register workloads. It doesn't do predicate reads, or Elle's bank, set and counter checkers. It prints text proofs instead of Graphviz plots. A clean result means it found no anomaly in that history. It doesn't prove your database is correct, so run it longer, with fewer keys for more contention, and with faults.
If you run it against something interesting, I'd like to hear what it found. Issues and PRs are open.
- GitHub: zaydmulani09/adya
- crates.io: crates.io
2 comments
Mentions
Jepsen halla 96 fallos de corrección en ParticleDB ParticleDB prometía ser "Full ACID" con 159.000 transacciones por segundo en la portada de su sitio. Jepsen, la consultora que Kyle Kingsbury dedica a destrozar bases de datos distribuidas con pruebas de caos, encontró 96 fallos de corrección entre las versiones 1.1.4 y 1.1.18.11.rc-22 de la base de datos ParticleDB. La mayoría de esos fallos aparecía con un solo servidor, un solo cliente y cero fallas de red. El reporte, publicado el 31 de octubre de 2026 y financiado por Orkes, la empresa detrás del motor, es el típico análisis Jepsen: mezcla hallazgos cómicos (filas parcialmente invisibles que no se podían borrar, tablas eliminadas que reaparecían distorsionadas) con otros serios, como pérdida de escrituras confirmadas incluso en el modo de aislamiento serializable, el más estricto que ofrece el motor. TL;DR - Jepsen halló 96 fallos en la base de datos ParticleDB, de Orkes, entre las versiones 1.1.4 y 1.1.18.11.rc-22- El modo serializable, el más estricto de ParticleDB, permitió anomalías internas y Lost Update- La configuración groupsync perdía escrituras confirmadas pese a anunciar la misma durabilidad que sync- Orkes lanzó la versión 1.1.19-rc.13 y afirma haber resuelto todos los fallos restantes- Su propia portada prometía "Full ACID" aunque el texto debajo describía snapshot isolation Qué es la base de datos ParticleDB La base de datos ParticleDB es un motor multimodal que Orkes construyó para cargas de inteligencia artificial: combina SQL transaccional, almacenamiento clave-valor, búsqueda vectorial y analítica en vivo mediante funciones definidas por el usuario llamadas Particles. Escrita en Rust, expone protocolos Postgres, Redis, gRPC y HTTP2 para integrarse con sistemas existentes. El objetivo declarado es reemplazar el típico mosaico de cachés, colas y almacenes separados que usan las aplicaciones de IA actuales. Orkes afirma que ParticleDB es órdenes de magnitud más rápido que Postgres mientras conserva las mismas garantías de seguridad transaccional. Qué pasó Orkes contrató a Jepsen para auditar las versiones refactorizadas de ParticleDB antes de un lanzamiento más amplio. El resultado fueron 96 problemas, desde los pedestres (caídas del servidor, deadlocks, pérdida de datos) hasta los más curiosos: filas parcialmente invisibles que no se podían eliminar, consultas WHERE 4 = 5 que igual devolvían filas, tablas borradas que reaparecían con forma distinta, columnas confundidas entre sí y strings que se convertían en enteros. La mayoría de esos 96 casos no necesitó un clúster bajo estrés: bastó un solo servidor, un solo cliente y ninguna falla de red para reproducirlos. Para la versión 1.1.18.11.rc-22, Orkes había resuelto todos los problemas menos diez. Pero entre esos diez quedaban los más graves: ParticleDB seguía perdiendo escrituras confirmadas durante operación normal con occ, su modo de transacciones por defecto, y permitía anomalías internas y Lost Update incluso con serializable, el ajuste más fuerte disponible. Orkes ya respondió. Según el propio reporte de Jepsen, la empresa lanzó la versión 1.1.19-rc.13 y asegura que ahí quedan resueltos todos los problemas que seguían abiertos. Jepsen reprodujo la mayoría de los 96 fallos con un solo servidor, un solo cliente y cero fallas de red. Contexto e historia Orkes no construyó ParticleDB como un producto aislado. La empresa opera Conductor, una plataforma de orquestación de flujos de trabajo que originalmente guardaba su estado en Redis y Postgres. Para mejorar el rendimiento, Orkes decidió escribir su propia base de datos en lugar de seguir combinando dos motores distintos. La versión 1.0.0 de ParticleDB tenía un set de funciones limitado, pensado específicamente para las consultas de Conductor, y se desplegó como backend de algunos clústeres de producción. Varios clientes de Orkes siguieron el mismo camino. Antes de un lanzamiento más amplio al público, Orkes hizo un refactor mayor: agregó un protocolo de cable propio, más tipos de datos y consultas OLAP adicionales, y lanzó esas versiones de forma silenciosa en particledb.ai. Jepsen empezó a probar justamente esas versiones refactorizadas, a partir de la 1.1.4. Un detalle que el reporte deja claro: ParticleDB también ofrece un clúster replicado usando el protocolo de consenso Raft, pero esa función solo está disponible en despliegues de nube empresarial. Las builds que Orkes entregó a Jepsen no soportaban replicación, así que todo lo analizado corresponde únicamente a comportamiento de un solo nodo. El mecanismo detrás de los fallos La página de inicio de ParticleDB aseguraba estar "formalmente probada" con una especificación en TLA+. Durante la colaboración con Jepsen, sin embargo, no existía ninguna especificación TLA+ disponible, así que no queda claro qué propiedades se habrían demostrado ni si describían al ParticleDB real. La mayoría de los motores SQL controlan el nivel de aislamiento de una transacción con la instrucción SET TRANSACTION ISOLATION LEVEL. ParticleDB la ignora o la rechaza. A veces le dice al usuario que use READ COMMITTED, un nivel relativamente débil; otras veces acepta el pedido de otro nivel y lo ignora en silencio, sin avisar. En cambio, el aislamiento real depende de un flag de arranque, --txn-mode, con cuatro modos documentados: ModoComportamientoCuándo usarlofastSin locks; commit al escribirCargas masivas, benchmarks de un solo escritorocc (default)Overlay más validación OCC al hacer commitOLTP general, pensado para ser seguro bajo concurrenciazero-copy-occOCC anclado a partición; transacciones de una sola partición evitan coordinación cruzadaOLTP de alto rendimiento sobre cargas afines a particióntable-2plLocks exclusivos de tabla al primer toqueSerialización más estricta, semántica simple El problema es que la documentación nunca explicó qué garantizaba cada modo en términos formales. ¿fast equivalía a Read Atomic, a Read Uncommitted o a otra cosa? Jepsen tampoco pudo confirmarlo del todo con occ, el modo que usa cualquier instalación nueva por defecto. La confusión se repite con la durabilidad del Write-Ahead Log. ParticleDB ofrece tres tácticas: sync hace fsync en cada commit, groupsync agrupa varias transacciones en un solo fsync, y nosync no hace fsync en absoluto. La documentación del motor de transacciones dice que sync ofrece "mayor durabilidad" que groupsync. $ particledb start --help --txn-mode groupsync # "COMMIT waits for flush" Pero la ayuda de línea de comandos asegura que groupsync garantiza que "COMMIT espera al flush", y la guía interna de operación de Orkes coincide: según ese documento, groupsync ofrece "la misma durabilidad que sync". Tres fuentes oficiales, tres versiones distintas de la misma garantía. > ⚠️ Ojo: la portada de ParticleDB decía literalmente "Full ACID. 159K TPS." y, justo debajo, el texto describía "snapshot isolation", un nivel de aislamiento que por definición no es completamente serializable. Impacto y análisis Para equipos que evalúan la base de datos ParticleDB en producción, el reporte es una señal de alerta clara. No es que ParticleDB no sirva: la mayoría de los 96 fallos ya estaban resueltos para la última versión probada. El problema es que los que quedaban afectaban justo el modo por defecto (occ) y el modo que, en teoría, ofrece más garantías (serializable). Jepsen aclara que financió el estudio la propia Orkes, siguiendo la política de ética de Jepsen, que exige publicar los resultados sin filtrar hallazgos negativos. Eso le da credibilidad al informe, aunque también explica por qué Orkes respondió tan rápido con una nueva versión. La limitación más importante del estudio es que solo cubre comportamiento de un solo nodo. ParticleDB vende replicación vía Raft para sus clientes de nube empresarial, pero esa parte del sistema nunca se probó porque las builds entregadas a Jepsen no la soportaban. Cualquier equipo que piense correr un clúster replicado en producción debería pedir una auditoría separada antes de confiar en esa capa. Qué sigue Orkes ya lanzó la versión 1.1.19-rc.13 y sostiene que resuelve todos los fallos que seguían abiertos al cierre del reporte. Jepsen no confirma de forma independiente esa afirmación en el documento publicado, así que por ahora es la palabra de Orkes contra sus propios resultados anteriores. Para cualquier equipo que ya use o esté evaluando ParticleDB, el paso práctico es elegir --txn-mode con el mismo cuidado que recomienda la propia documentación del proyecto: la mejor práctica publicada advierte que el modo "cambia la corrección y el rendimiento más que el texto SQL", así que no conviene dejarlo en el valor por defecto sin entender qué cubre. 📖 Resumen en Telegram: Ver resumen Probalo vos: antes de poner la base de datos ParticleDB en producción, corré tu propio set de pruebas de concurrencia con el --txn-mode que pensás usar y compará el resultado contra el reporte de Jepsen en jepsen.io. El modo serializable, el más estricto que ofrece ParticleDB, también mostró Lost Update según Jepsen. Preguntas frecuentes ¿ParticleDB cumple de verdad con ACID? No de forma completa en las versiones auditadas. Jepsen encontró pérdida de escrituras confirmadas en el modo por defecto y anomalías incluso en serializable, el nivel más estricto que ofrece ParticleDB. ¿Qué modo de transacción de ParticleDB es más seguro? Según la documentación, table-2pl ofrece la serialización más estricta con locks exclusivos de tabla, aunque el reporte de Jepsen no confirma que ningún modo esté completamente libre de fallos en todas las versiones probadas. ¿ParticleDB soporta replicación? Sí, mediante el protocolo Raft, pero solo en despliegues de nube empresarial. Jepsen no evaluó esa capa porque las builds que usó no la incluían. ¿Qué es Orkes y por qué construyó ParticleDB? Orkes es la empresa detrás de Conductor, una plataforma de orquestación de flujos de trabajo. Construyó ParticleDB para reemplazar la combinación de Redis y Postgres que usaba Conductor y mejorar el rendimiento. ¿Ya se resolvieron los fallos que halló Jepsen en ParticleDB? Orkes dice que la versión 1.1.19-rc.13 resuelve todos los problemas que seguían abiertos al cierre del reporte, aunque Jepsen no lo confirma de forma independiente en el documento publicado. Referencias - Jepsen: ParticleDB 1.1.4: el reporte original de Kyle Kingsbury con los 96 fallos encontrados.- Jepsen en GitHub: el framework y la metodología que usa Jepsen para auditar bases de datos distribuidas.- Raft Consensus Algorithm: el protocolo de replicación que ParticleDB ofrece en su nube empresarial.- Conductor OSS: la plataforma de orquestación de Orkes que originó el desarrollo de ParticleDB.- ACID (Wikipedia): definición de las propiedades de atomicidad, consistencia, aislamiento y durabilidad. 📱 ¿Te gusta este contenido? Únete a nuestro canal de Telegram @programacion donde publicamos a diario lo más relevante de tecnología, IA y desarrollo. Resúmenes rápidos, contenido fresco todos los días.
Inordinately proud of this performance fix: github.com/jepsen-io/elle/commit/0964656dcf1ac1ed…
I wanted Jepsen's Elle in CI without a JVM, so I wrote adya Your database documentation says REPEATABLE READ. Your code assumes it. Have you ever checked? I spent the last stretch building adya, a black-box checker for transactional isolation. You give it a log of transactions a database ran, and it tells you which isolation guarantees held. For each one that didn't, it prints the transactions involved and the chain of reads and writes that proves the violation. It's an independent Rust implementation of the approach from Elle (Kingsbury and Alvaro, VLDB 2020), the checker behind Jepsen's database analyses. If you know Elle, adya reads the same history formats and takes the same flags. If you don't, keep reading. The problem with "it passed the tests" Isolation bugs don't show up in unit tests. They need two or more transactions to interleave in one particular way, and when they happen nothing crashes. You just get a balance that's off, or two people booked into the same seat. The classic example is write skew. Two doctors are on call, and the rule says at least one must stay on call. Each doctor's transaction checks "is the other one still on call?", sees yes, and takes themselves off. Both commit. Now nobody is on call. Snapshot isolation allows this. Postgres's REPEATABLE READ is snapshot isolation, so it allows this. If you thought REPEATABLE READ meant "safe enough", the bug ships. Elle's insight was that you can catch this from the outside. Record what every client asked for and what it got back, infer the dependencies between transactions, and look for cycles that a given isolation level forbids. Atul Adya's 1999 thesis catalogued those cycles (G0, G1c, G-single, G2 and so on), which is where the name comes from. Why another implementation Elle is excellent. It's also a Clojure library on the JVM. People who want it outside a Jepsen test end up shelling out to elle-cli from a Go or Python harness. I found two open-source projects doing exactly that in CI (barn, bytecaskdb), and the bytecaskdb PR lists what went wrong: a blocked Clojars mirror, a crash when graphviz was missing, log lines corrupting the JSON output. On my Windows machine, elle-cli 0.1.11 hung with no output on every anomalous history I gave it. On Linux it worked, but in my CI comparison it needed more than five minutes on five of forty random 300-transaction histories, and ran out of a 6 GB heap on one more. adya checked all forty in 0.2 seconds. Elle also leaves the workload to you. You have to write the client that generates transactions, runs them against your database and records the history. That's the part most people never get to. So adya ships both halves in one binary: cargo install adya # Run a workload against Postgres at REPEATABLE READ, # then check the history against serializability. adya run postgres --url postgres://localhost/test -i repeatable-read -c serializable What it looks like You don't need a database to try it. adya has a built-in simulated database that implements isolation levels the textbook way: $ adya run sim -i snapshot-isolation -c serializable -n 300 -p 4 --seed 1 history.jsonl false G2-item #0 Let: T234 = {"index":234,"process":1,"type":"ok","value":[["r",10,[4]],["append",3,6],["r",3,[4,5,6]]]} T237 = {"index":237,"process":3,"type":"ok","value":[["append",9,13],["append",10,5],["r",3,[4,5]],["r",10,[4,5]]]} Then: - T234 < T237, because T234 did not observe T237's append of 5 to 10. - However, T237 < T234, because T237 did not observe T234's append of 6 to 3: a contradiction! That's the doctors problem with list keys. T234 read key 10 before T237 appended to it, so T234 has to come first. T237 read key 3 before T234 appended to it, so T237 has to come first. Both can't be true, so no serial order exists. Snapshot isolation allows that cycle. Serializability doesn't. How it works The trick that makes this tractable is the workload. adya's default, borrowed from Elle, is list-append: every key holds a list, transactions append unique numbers to lists and read whole lists back. Each read then tells you the order of every append before it. If one client read [1, 2] and another read [1, 2, 5], you know 5 came after 2, without asking the database anything. From those orders adya builds a dependency graph over transactions, using Adya's three edge types: - ww: T2 appended right after T1's append to the same key. - wr: T2 read a list ending in T1's append. - rw: T1 read a list that T2 later appended to, so T1 didn't see T2's write. If you're checking a model with real-time guarantees (strict serializability), it adds edges for "T1 finished before T2 started", using a transitive reduction so the graph stays roughly linear in the history. Then it looks for cycles, one strongly connected component at a time. Each anomaly class is a cycle with constraints. G-single has exactly one rw edge. G2-item has at least two, with two of them adjacent. G1c has no rw edges and at least one wr. Instead of enumerating cycles and classifying them afterwards, adya runs a breadth-first search over pairs of (transaction, path state). The path state is seven bits: how many rw edges so far (0, 1, or 2+), whether the last edge was rw, whether the first one was, whether two were adjacent, whether it has seen a wr, and whether it has used a real-time edge. One BFS then returns the shortest cycle of exactly the shape you asked for. Before searching, it runs cheaper existence checks. For each model it asks whether the subgraph that model forbids cycles in has a nontrivial SCC at all. If the ww/wr-plus-one-rw subgraph is acyclic, snapshot isolation holds as far as cycles go, and adya skips every search that could only find SI violations. It also searches the most severe anomalies first and skips anything they imply. A 100,000-transaction history (30 MB of JSON) checks in about 1.6 seconds on my laptop. Is it right? A checker that's wrong is worse than none, so this took most of the work. 1. Elle's own expected results. elle-cli's test suite ships 56 list-append and rw-register histories along with the JSON verdicts Elle produced for them. adya matches all 56: same verdict, same anomaly types, and the same weakest models ruled out. Getting there taught me two Elle details I would never have guessed. It labels an edge that carries several relations by a fixed priority (ww before wr before rw before real-time), but tests whether a cycle exists using any relation the edge carries. And it counts a composed edge that loops back to the same transaction as a cycle. 2. Elle itself, live. On every push, CI generates random histories with adya's simulator and checks each with both tools. In the first full run Elle finished 34 of 40, and adya agreed on all 34. 3. Databases with known answers. The simulator implements serializable, snapshot isolation, read committed (with write locks), read uncommitted, and a deliberately broken "snapshot" that writes back stale state. Tests assert that correct runs produce zero anomalies at their own level and the expected ones above it. That test caught a bug in my simulator, not in the checker: my first read committed took no write locks, which is weaker than any real database. What Postgres and MySQL did CI runs 4,000 transactions from 10 clients over 6 hot keys against Postgres 17 and MySQL 8.4, at each isolation level, and checks every history against a ladder of models. List-append results: | database, level | serializable | snapshot isolation | read committed | |---|---|---|---| | Postgres READ COMMITTED | G-single, G2-item, internal, lost update | G-single, internal, lost update | valid | | Postgres REPEATABLE READ | G2-item | valid | valid | | Postgres SERIALIZABLE | valid | valid | valid | | MySQL REPEATABLE READ | G-single, G2-item, internal, lost update | G-single, internal, lost update | valid | | MySQL SERIALIZABLE | valid | valid | valid | Postgres did what its docs say. REPEATABLE READ is snapshot isolation, write skew included, and SERIALIZABLE came out strict serializable. I also restarted Postgres every few seconds during a 6,000-transaction SERIALIZABLE run (--fault "docker restart -t 0 pg"). Nine restarts, 2,957 commits, 3,043 failures, and the history still checked clean. MySQL's REPEATABLE READ is weaker than its name. It lets a transaction lose another's update and see part of another transaction's writes, so it isn't snapshot isolation. Jepsen reported the same thing about MySQL 8.0.34 in 2023. adya reproduces it from a cold start in a few seconds. Testing your own database The built-in drivers cover SQLite, Postgres and MySQL. For anything else there's adya run exec: adya starts your client once per process, writes one JSON line per transaction to its stdin, and reads back one line saying what happened. {"value":[["append",3,7],["r",4,null]]} {"type":"ok","value":[["append",3,7],["r",4,[1,7]]]} The repo has a 59-line Python client for SQLite as a template. Swap the SQL and you're testing your own database. Limits adya covers list-append and read-write register workloads. It doesn't do predicate reads, or Elle's bank, set and counter checkers. It prints text proofs instead of Graphviz plots. A clean result means it found no anomaly in that history. It doesn't prove your database is correct, so run it longer, with fewer keys for more contention, and with faults. If you run it against something interesting, I'd like to hear what it found. Issues and PRs are open. - GitHub: zaydmulani09/adya - crates.io: crates.io