원문 캡처 · jepsen.io
원문 캡처 · jepsen.io

Jepsen이 ParticleDB 1.1.4부터 1.1.18.11.rc-22까지 시험해 데이터 손실과 트랜잭션 오류를 포함한 96개 문제를 발견했다. 대부분은 장애를 주입하지 않은 단일 노드에서도 발생했으며, Jepsen은 ParticleDB 배포를 중단하고 다른 데이터베이스로 옮길 것을 권고했다.

시험에서는 커밋된 쓰기가 사라지거나 트랜잭션이 서로 일관되지 않은 데이터를 읽는 문제가 확인됐다. 제약 조건이 무시되고, 잘못된 행을 반환하거나 데이터를 손상시키는 사례도 있었다. WAL 파일이 손상되면 데이터 일부 또는 전체를 잃을 수 있었다. Jepsen은 복제 기능을 시험하지 않았으므로 클러스터 환경의 안전성은 평가하지 않았다.

Orkes는 시험 종료 뒤 공개한 1.1.19-rc.13에서 보고서의 문제를 해결했다고 밝혔지만, Jepsen은 이 버전을 검증하지 않았다. 보고서가 다룬 시험 버전 이후의 격리 수준이나 동시성 제어 기능도 평가하지 않았으며, 테스트로 버그의 존재를 확인할 수는 있어도 버그가 없음을 증명할 수는 없다고 설명했다.

Jepsen은 영향을 받은 버전을 사용한 경우 신뢰할 수 있는 다른 자료가 있다면 데이터 상태를 확인하거나 재구축할 것을 권고했다. 전체 시험 결과와 조건은 Jepsen의 ParticleDB 분석 보고서에서 확인할 수 있다.

부록: 원문 예시

text
root=> CREATE TABLE t (a real); root=> INSERT INTO t (a) VALUES (3); root=> BEGIN; root=*> SELECT * FROM t WHERE 1 < (a + 2);  a --- (0 rows)
text
root=> SELECT * FROM t WHERE 1 < (5.0 % 3); ERROR:  internal error root=> BEGIN; root=*> SELECT * FROM t WHERE 1 < (5.0 % 3);  a --- (0 rows)
text
root=> CREATE TABLE t        (a int primary key, b int); root=> INSERT INTO t VALUES (4, 5), (5, 6); root=> SELECT * FROM t WHERE b = 4;  a | b ---+---  4 | 5
text
root=> CREATE TABLE t (a INT UNIQUE); root=> INSERT INTO t VALUES (1), (2); root=> BEGIN; root=*> SELECT * FROM t;  a ---  1  2 (2 rows)
text
root=*> SELECT * FROM t WHERE a = 1;  a ---  2 (1 row) root=*> SELECT * FROM t WHERE a = 3;  a ---  2 (1 row) root=*> SELECT * FROM t WHERE a IN (1, 2);  a ---  2 (1 row) root=*> SELECT * FROM t WHERE FALSE;  a ---  2 (1 row)
text
root=*> SELECT * FROM t WHERE TRUE;  a --- (0 rows) root=*> SELECT * FROM t WHERE 3 = 3;  a --- (0 rows)
text
root=> CREATE TABLE t (a BOOLEAN NOT NULL); root=> INSERT INTO t (a) VALUES (NULL); INSERT 0 1
text
root=> SELECT * FROM t;  a --- (0 rows) root=> SELECT COUNT(*) FROM t;  COUNT -------      1 (1 row) root=> SELECT COUNT(*) FROM t WHERE a IS NULL;  COUNT -------      0 (1 row)
text
root=> CREATE TABLE t (a boolean UNIQUE); root=> INSERT INTO t (a) VALUES (TRUE); INSERT 0 1 root=> INSERT INTO t (a) VALUES (TRUE); INSERT 0 1 root=> SELECT * FROM t;  a ---  t  t (2 rows)
text
root=> CREATE TABLE t (id text PRIMARY KEY,                        desc text); root=> INSERT INTO t VALUES ('a', 'a'); root=> INSERT INTO t VALUES ('b', 'b'); root=> UPDATE t SET id = 'c'; UPDATE 2
text
root=> SELECT * FROM t WHERE id = 'c';  id | desc ----+------  c  | b (1 row) root=> SELECT * FROM t WHERE id LIKE 'c';  id | desc ----+------  c  | a  c  | b
text
root=> CREATE TABLE t (a int PRIMARY KEY); root=> INSERT INTO t (a) VALUES (3), (4); root=> UPDATE t SET a = NULL; root=> SELECT * FROM t;  a --- (2 rows)
text
root=> CREATE TABLE t (a real); root=> CREATE TABLE u (a int); root=> INSERT INTO t (a) VALUES (2.0); root=> INSERT INTO u (a) VALUES (2); root=> SELECT t.a FROM t JOIN u ON u.a = t.a; ERROR:  join key must be integer type
text
root=> CREATE TABLE t (a int); root=> CREATE TABLE u (a int); root=> INSERT INTO t (a) VALUES (1), (2); root=> SELECT t.a AS t, u.a AS u        FROM t LEFT JOIN u ON u.a = t.a;  t | u ---+--- (0 rows)
text
root=> CREATE TABLE t (a text); root=> CREATE TABLE u (a text); root=> INSERT INTO t VALUES ('this'), ('that'); root=> INSERT INTO u VALUES        ('is not the right column'); root=> SELECT t.a AS ta, u.a AS ua FROM t        CROSS JOIN u WHERE (u.a = 'this');   ta  |           ua ------+-------------------------  this | is not the right column (1 row)
text
root=> CREATE TABLE t (a boolean); root=> INSERT INTO t (a) VALUES (TRUE); root=> BEGIN; root=*> DELETE FROM t; DELETE 0 root=*> COMMIT; root=> SELECT * FROM t;  a ---  t (1 row)
text
root=> CREATE TABLE t (a boolean); root=> INSERT INTO t (a) VALUES (TRUE); root=> BEGIN; root=*> DELETE FROM t; DELETE 1 root=*> SELECT COUNT(*) FROM t WHERE a = TRUE;  COUNT -------      1 (1 row) root=*> SELECT COUNT(*) FROM t WHERE a = FALSE;  COUNT -------      0 (1 row)
text
root=> CREATE TABLE t (i int); root=> INSERT INTO t VALUES (0); root=> UPDATE t SET i = 1.5; root=> DELETE FROM t; DELETE 0 root=> SELECT * FROM t;  i ---  1 (1 row)
text
root=> CREATE TABLE t (a character(3)); root=> INSERT INTO t (a) VALUES ('hi'); root=> DELETE FROM t; DELETE 0 root=> UPDATE t SET a = 'sup'; UPDATE 0 root=> SELECT * FROM t;   a -----  hi (1 row)
text
root=> CREATE TABLE t (a text, b real); root=> BEGIN; root=*> INSERT INTO t VALUES ('boo', 18.83); root=*> COMMIT;
text
root=> DROP TABLE t; root=> SELECT * FROM t; ERROR:  table not found
text
root=> CREATE TABLE t (a text, b int); root=> BEGIN; root=*> SELECT * FROM t;   a  | b -----+----  boo | 19 (1 row)
text
root=> CREATE TABLE t (a real); root=> INSERT INTO t (a) VALUES (2.5); root=> INSERT INTO t (a) VALUES ('NaN'); root=> SELECT * FROM t;   a -----  2.5    0 (2 rows)
text
root=> CREATE TABLE t (a INT, b BOOLEAN); root=> INSERT INTO t VALUES (2, FALSE); root=> SELECT * FROM t WHERE a = b; ERROR:  arrow compute: Invalid argument error: Invalid comparison operation: Int64 == Boolean root=> UPDATE t SET a = b; root=> SELECT * FROM t;  a | b ---+---  0 | f
text
ERROR: column "a" is of type text but expression is of type int
text
root=> CREATE TABLE t        (a TEXT NOT NULL PRIMARY KEY,         b boolean); root=> INSERT INTO t (a, b) VALUES ('x', TRUE); root=> INSERT INTO t (b, a) VALUES (FALSE, 'y'); root=> SELECT COUNT(*) FROM t;  COUNT -------      2 (1 row)
text
root=> SELECT * FROM t WHERE a = 'x';  a | b ---+---  x | t (1 row) root=> SELECT * FROM t WHERE a = 'y';  b | a ---+---  f | y (1 row)
text
root=> CREATE TABLE t (a INT, b REAL); root=> INSERT INTO t (a, b)        VALUES (1, 1.23E32); root=> INSERT INTO t (b, a)        VALUES (2.34E32, 2); root=> SELECT * FROM t;           a          |    b ---------------------+---------                    1 | 1.23e32  9223372036854775807 |       2 (2 rows)
text
root=> DELETE FROM t; DELETE 1 root=> SELECT * FROM t;  a |    b ---+---------  2 | 2.34e32 (1 row)
text
root=> CREATE TABLE t        (id int not null primary key,         val integer); root=> BEGIN; root=*> UPDATE t SET val = 10 WHERE id = 0; UPDATE 1 root=*> COMMIT;
text
root=> SELECT * FROM t;  id | val ----+----- (0 rows) root=> SELECT * FROM t WHERE id = 0;  id | val ----+----- (0 rows) root=> SELECT * FROM t WHERE val = 10;  id | val ----+----- (0 rows)
text
root=> INSERT INTO t (id, val) VALUES (0, 100); INSERT 0 1 root=> INSERT INTO t (id, val) VALUES (10, 110); ERROR:  duplicate key value violates unique         constraint (key=10)
text
root=> CREATE TABLE t        (id int not null primary key,         val integer); root=> BEGIN; root=*> UPDATE t SET val = 50 WHERE id = 5; UPDATE 1 root=*> SELECT * FROM t WHERE id = 5;  val -----   50 (1 row) root=*> COMMIT;
text
root=> INSERT INTO t VALUES (6, 60); root=> SELECT * FROM t WHERE id IN (6);  id | val ----+-----   6 |  60 (1 row) root=> SELECT * FROM t WHERE id IN (6, 7);  id | val ----+----- (0 rows)
text
root=> CREATE TABLE t (a INT UNIQUE); root=> INSERT INTO t VALUES (1); root=> BEGIN; root=*> UPDATE t SET a = 3 WHERE a = 2; UPDATE 1 root=*> COMMIT; root=> SELECT * FROM t;  a ---  1  3 (2 rows)
text
root=> CREATE TABLE t (a int); root=> BEGIN; root=*> INSERT INTO t VALUES (3); root=*> BEGIN; root=*> SELECT * FROM t;  a --- (0 rows) root=*> COMMIT;
text
root=> BEGIN; root=> INSERT INTO t (a) VALUES (3); root=> THIS IS NOT VALID SQL; ERROR:  sql parser error: Expected: an SQL statement, found: THIS at Line: 1, Column: 1 root=> COMMIT; ROLLBACK
text
root=> CREATE TABLE t (a int); root=> INSERT INTO t (a) VALUES (2); root=> BEGIN; root=*> INSERT INTO t (a) VALUES (3); root=*> SELECT COUNT(*) FROM t;  COUNT  -------      1 (1 row)
text
root=> CREATE TABLE t (a INT); root=> INSERT INTO t (a) VALUES (0); root=> BEGIN; root=*> UPDATE t SET a = 1; root=*> INSERT INTO t (a) VALUES (0); root=*> UPDATE t SET a = 2; UPDATE 2 root=*> SELECT t.a FROM t;  a ---  2  0 (2 rows)
text
root=> CREATE TABLE t (a TEXT PRIMARY KEY); root=> INSERT INTO t (a) VALUES ('mood'); root=> BEGIN; root=*> SELECT * FROM t;  a --- (0 rows)
text
root=> CREATE TABLE t_876 (a boolean); root=> INSERT INTO t_876 (a) VALUES (NULL); root=> INSERT INTO t_876 (a) VALUES (NULL); root=> SELECT * FROM t_876 WHERE TRUE;   a ------  NULL (1 row)
text
root=> CREATE TABLE t_40 (a boolean); root=> INSERT INTO t_40 (a) VALUES (FALSE); root=> UPDATE t_40 SET a = (a <= a) WHERE NULL; UPDATE 1
text
root=> CREATE TABLE t_101 (   a text COLLATE unicode PRIMARY KEY ); root=> INSERT INTO t_101 (a) VALUES (' '); root=> UPDATE t_101 SET a = NULL; ERROR: could not serialize access due to        concurrent update on table "t_101"        key " "
text
root=> CREATE TABLE t_27763 (   a boolean,   b boolean NOT NULL UNIQUE PRIMARY KEY ); root=> INSERT INTO t_27763 (a, b)        VALUES (NULL, TRUE); root=> DELETE FROM t_27763; root=> SELECT t_27763.a AS j FROM t_27763        WHERE t_27763.b ORDER BY t_27763.b;   j ------  NULL (1 row)
text
2026-07-22T14:26:07.985223Z  WARN ThreadId(01) recover_streaming{dir=/opt/particledb/data/wal}: spanner_wal::segment: checksum mismatch in segment tail, stopping read segment_id=0 2026-07-22T14:26:07.985235Z  WARN ThreadId(01) recover_streaming{dir=/opt/particledb/data/wal}: spanner_wal::recovery: replaying valid prefix of already-sealed corrupt WAL segment (.corrupt marker present) and continuing with later segments of this stripe stripe_id=0 segment_id=0 corruption_offset=25184 valid_size=25184 recovered_entries=242 detail="checksum mismatch: stored 0xf3b8aa37, computed 0x4a774da2" 
text
2026-07-30T14:53:58.715589Z  INFO ThreadId(01) spanner_exec::engine::context: No data found in storage — starting fresh
text
root=> CREATE TABLE t (a boolean); root=> SELECT COUNT(*) FROM t        WHERE (a >= a) ORDER BY a LIMIT 0;  COUNT ------- (0 rows)
text
root=> CREATE TABLE t (a text, b smallint); root=> DELETE FROM t WHERE (a = b); DELETE 0
text
ERROR: operator does not exist: text = smallint
text
root=> CREATE TABLE t (a double precision); root=> INSERT INTO t (a) VALUES (1.0); root=> SELECT * FROM t WHERE        ((0 / 1.2002480805601245E26) >= t.a); ERROR: arrow compute: Invalid argument error:        Invalid comparison operation:        Decimal256(76, 44) >= Float64
text
root=> CREATE TABLE t (a boolean); root=> INSERT INTO t (a) VALUES (TRUE); root=> SELECT * FROM t WHERE NULL; ERROR:  filter predicate must produce boolean result
text
root=> CREATE TABLE t (a int); root=> INSERT INTO t (a) VALUES (2), (NULL); root=> UPDATE t SET a = 5 WHERE a = NULL; ERROR:  arrow compute: Invalid argument error: Invalid comparison operation: Int64 == Null
text
root=> SELECT TRUE AND 5;      # => 5 root=> SELECT 5 AND TRUE;      # => 5 root=> SELECT 5 AND FALSE;     # => FALSE root=> SELECT 5 OR TRUE;       # => TRUE root=> SELECT 'cat' OR FALSE;  # => 'cat' root=> SELECT 'cat' OR 'dog'; ERROR:  OR requires boolean operands root=> SELECT 1 AND 2; ERROR:  AND conjunct must produce boolean
text
root=> CREATE TABLE t (a int); root=> UPDATE t SET a = NOT a; UPDATE 0
text
root=> CREATE TABLE t (a boolean); root=> UPDATE t SET a = ((a + a) IS NULL)        WHERE a; UPDATE 0
text
root=> CREATE TABLE t (a boolean, b boolean); root=> INSERT INTO t (a, b)        VALUES (FALSE, FALSE); root=> UPDATE t SET a = FALSE, a = TRUE; UPDATE 1
text
root=> CREATE TABLE t (a INT); root=> SELECT * FROM t WHERE b = 1;  a --- (0 rows)
text
root=> SELECT 200 ^ 200; ERROR:  value out of range: overflow root=> SELECT -200 ^ -200;  -0 ----   0 (1 row)
text
root=> CREATE TABLE t (a int); root=> INSERT INTO t (a) VALUES (NULL); root=> SELECT * FROM t WHERE        (t.a + 2.0000000000000000E-23) IS NULL; ERROR:  internal error: called 
text
root=> CREATE TABLE t (a real); CREATE TABLE root=> INSERT INTO t (a) VALUES (1.2); INSERT 0 1 root=> UPDATE t SET a = a + 1; ERROR:  internal error
text
root=> CREATE TABLE t (a smallint); root=> INSERT INTO t (a) VALUES (32768); root=> INSERT INTO t (a)        VALUES (9223372036854775807); root=> INSERT INTO t (a)        VALUES (9223372036854775808); root=> INSERT INTO t (a) VALUES (1.234E40); root=> SELECT * FROM t;           a ---------------------                32768  9223372036854775807                    0  9223372036854775807 (5 rows)
text
root=> CREATE TABLE t (a bigint); root=> INSERT INTO t (a) VALUES        (58950791971326900); root=> UPDATE t SET a = (a + 1E-1); root=> SELECT a FROM t;          a -------------------  58950791971326896
text
root=> CREATE TABLE t (a character(3)); root=> INSERT INTO t        VALUES ('large language model'); root=> SELECT * FROM t;           a ----------------------  large language model (1 row)
text
root=> CREATE TABLE t (b text COLLATE unicode); root=> INSERT INTO t (b)        VALUES ('a'), ('b'), ('B'); root=> SELECT * FROM t ORDER BY b;  b ---  B  a  b (3 rows)
text
root=> CREATE TABLE t (a text COLLATE unicode); root=> INSERT INTO t VALUES        ('_aa'), ('a'), ('aa'), ('aaa'); root=> SELECT * FROM t ORDER BY a ASC;   a -----  a  _aa  aa  aaa (4 rows)