
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 분석 보고서에서 확인할 수 있다.
부록: 원문 예시
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)
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)
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
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)
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)
root=*> SELECT * FROM t WHERE TRUE; a --- (0 rows) root=*> SELECT * FROM t WHERE 3 = 3; a --- (0 rows)
root=> CREATE TABLE t (a BOOLEAN NOT NULL); root=> INSERT INTO t (a) VALUES (NULL); INSERT 0 1
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)
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)
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
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
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)
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
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)
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)
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)
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)
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)
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)
root=> CREATE TABLE t (a text, b real); root=> BEGIN; root=*> INSERT INTO t VALUES ('boo', 18.83); root=*> COMMIT;
root=> DROP TABLE t; root=> SELECT * FROM t; ERROR: table not found
root=> CREATE TABLE t (a text, b int); root=> BEGIN; root=*> SELECT * FROM t; a | b -----+---- boo | 19 (1 row)
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)
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
ERROR: column "a" is of type text but expression is of type int
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)
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)
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)
root=> DELETE FROM t; DELETE 1 root=> SELECT * FROM t; a | b ---+--------- 2 | 2.34e32 (1 row)
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;
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)
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)
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;
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)
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)
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;
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
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)
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)
root=> CREATE TABLE t (a TEXT PRIMARY KEY); root=> INSERT INTO t (a) VALUES ('mood'); root=> BEGIN; root=*> SELECT * FROM t; a --- (0 rows)
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)
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
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 " "
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)
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"
2026-07-30T14:53:58.715589Z INFO ThreadId(01) spanner_exec::engine::context: No data found in storage — starting fresh
root=> CREATE TABLE t (a boolean); root=> SELECT COUNT(*) FROM t WHERE (a >= a) ORDER BY a LIMIT 0; COUNT ------- (0 rows)
root=> CREATE TABLE t (a text, b smallint); root=> DELETE FROM t WHERE (a = b); DELETE 0
ERROR: operator does not exist: text = smallint
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
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
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
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
root=> CREATE TABLE t (a int); root=> UPDATE t SET a = NOT a; UPDATE 0
root=> CREATE TABLE t (a boolean); root=> UPDATE t SET a = ((a + a) IS NULL) WHERE a; UPDATE 0
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
root=> CREATE TABLE t (a INT); root=> SELECT * FROM t WHERE b = 1; a --- (0 rows)
root=> SELECT 200 ^ 200; ERROR: value out of range: overflow root=> SELECT -200 ^ -200; -0 ---- 0 (1 row)
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
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
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)
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
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)
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)
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)
댓글 (0)
로그인하면 이 기사에 내 생각을 남길 수 있어요