- Jepsen 테스트에서 Amazon RDS for PostgreSQL Multi-AZ 클러스터가 전체 노드 기준 최강 격리 수준인 Snapshot Isolation을 지키지 않는 사례가 확인됨
- 핵심 원인은 primary의 트랜잭션 표시 순서가 메모리 내 락으로 정해지는 반면, secondary는 WAL 순서를 따르면서 두 순서가 어긋날 수 있다는 점임
- 장애 주입이나 failover 없이
gp3스토리지와db.m6id.large인스턴스를 사용한 조건에서도 약 150 write TPS / 1600 read-only TPS에서 몇 분마다 G-nonadjacent cycle이 나타남 - 이상 현상은 Long Fork에 해당하며, AWS가 지원한 PostgreSQL 13.15부터 17.4까지 테스트한 모든 버전에서 나타났고 Short Fork/Write Skew는 관찰되지 않음
- 안전이 중요한 트랜잭션은 read-only secondary 사용 시 실행 순서를 다르게 볼 수 있어, writer endpoint만 쓰거나 최소 1개 write를 포함하는 방식의 검토가 필요함
Long Fork 원인 업데이트
- AWS의 Sergey Melnik과 HN 댓글 참여자인 matashii, Ants Aasma가 PostgreSQL 클러스터의 Long Fork 원인을 식별함
- PostgreSQL primary는 트랜잭션을 보이게 하는 순서를 메모리 내 락으로 결정함
- secondary는 트랜잭션을 Write-Ahead Log(WAL) 안의 순서에 따라 보이게 함
- 락 순서와 WAL 순서가 달라지면 primary와 secondary가 트랜잭션의 겉보기 순서를 다르게 볼 수 있음
- 이 동작은 2013년 PostgreSQL 메일링리스트 글에서 다뤄졌고, Melnik은 AWS 블로그에 PostgreSQL 클러스터와 read replica의 transaction visibility를 설명하는 글을 작성함
- Jepsen은 AWS와 PostgreSQL이 수정 작업과 함께 이 이슈를 문서화하기를 권장함
RDS for PostgreSQL의 격리 수준과 구조
- PostgreSQL은 범용 오픈소스 SQL 데이터베이스이며, MVCC로 세 가지 트랜잭션 격리 수준을 제공함
Read Uncommitted와Read Committed는 모두 Read Committed로 동작함Repeatable Read는 실제로 Repeatable Read가 아니라 Snapshot Isolation을 제공함Serializable은 Serializability를 제공함
- Amazon RDS for PostgreSQL은 관리형 PostgreSQL 클러스터를 제공하는 AWS 서비스임
- 프로비저닝, 스토리지 관리, 복제, 백업, 업그레이드 등을 자동화함
- Multi-AZ deployments는 데이터베이스 노드를 여러 가용 영역에 분산해 상관 장애 가능성을 줄임
- RDS는 primary와 최소 1개 secondary 인스턴스 모두에서 트랜잭션 내구성이 확보된 뒤 응답하도록 동기 복제를 사용함
- 사용자에게는 PostgreSQL wire protocol을 말하는 두 URL이 제공됨
- primary endpoint: read-write 트랜잭션용
- reader endpoint: read-only 트랜잭션용
- primary endpoint는 모든 PostgreSQL 격리 수준을 지원하지만, secondary는 Serializable을 지원하지 않음
- 전체 노드에서 쓸 수 있는 최강 격리 수준은 PostgreSQL이
Repeatable Read라고 부르는 Snapshot Isolation임
테스트 설계
- Jepsen은 PostgreSQL용 테스트 라이브러리를 Amazon RDS for PostgreSQL에 맞게 조정하고, 작은 wrapper program을 사용함
- 각 테스트 라운드마다 AWS의 CreateDBCluster API로 RDS 클러스터를 프로비저닝함
- 스토리지는
gp3 - 인스턴스는
db.m6id.large
- 스토리지는
- 테스트 실행용 EC2 노드 1개를 띄우고 RDS 클러스터의 main endpoint와 read-only endpoint를 제공함
- 장애 주입은 하지 않았고 failover도 트리거하지 않음
- 주 workload는 고유 정수 리스트를 다루는 트랜잭션으로 구성됨
- 각 리스트는 단일 row에 저장되고, 쉼표로 구분된 값을 담은
TEXT필드로 인코딩됨 - 트랜잭션은 primary key로 리스트를 읽거나
CONCAT으로 고유 정수를 리스트에 append함
- 각 리스트는 단일 row에 저장되고, 쉼표로 구분된 값을 담은
- 이 workload를 통해 Elle checker가 트랜잭션 간 데이터 흐름 의존성을 추론하고 그래프 cycle을 찾아 여러 격리 수준을 검증할 수 있음
G-nonadjacent cycle 관찰
- 정상 조건과 중간 수준의 동시성에서도 Amazon RDS for PostgreSQL 17.4는 몇 분마다 G-nonadjacent cycle을 보임
- 한 2분 테스트 실행은 약 150 write TPS와 1600 read-only TPS를 수행했고, 4개 트랜잭션 cycle을 포함함
- 예시 cycle은 네 트랜잭션
T1,T2,T3,T4로 구성됨T1은 row 89에9를 append해 리스트[4 9]를 만들었고,T2가 이를 관찰함T3는 row 90에11을 append해 리스트[11]을 만듦T4는 row 90에3을 append하고 결과 리스트[11, 3]을 읽어T3의 version을 덮어씀T2는 row 89에서T1의 append를 관찰했지만 row 90에서T3의 append는 보지 못함- 반대로
T4는 row 90에서T3의 append를 관찰했지만 row 89에서T1의 append는 놓침
- 이 cycle은 서로 인접하지 않은 read-write dependency를 포함하므로 Snapshot Isolation 위반인 G-nonadjacent cycle임
- 표준 PostgreSQL의
Repeatable Read에서는 이런 동작이 발생해서는 안 되며, Jepsen은 표준 PostgreSQL에서 이를 관찰하지 못함
Snapshot Isolation과 충돌하는 이유
- Snapshot Isolation에서는 모든 트랜잭션이 시작 timestamp
s시점의 데이터베이스 snapshot 위에서 동작하는 것처럼 보여야 함 - 트랜잭션의 효과는 이후 commit timestamp
c에 다른 트랜잭션에 보이게 됨 - 예시 cycle의 관찰 결과를 timestamp 관계로 쓰면 서로 모순됨
T2가T1의 append를 읽었으므로T2의 시작은T1의 commit 뒤여야 함:c1 < s2T2가T3의 append를 관찰하지 못했으므로s2 < c3T4가T3를 덮어쓰고 관찰했으므로c3 < s4T4가T1의 append를 관찰하지 못했으므로s4 < c1
- 이 관계들은 모두 동시에 성립할 수 없어 Snapshot Isolation의 timestamp 모델과 충돌함
Long Fork와 버전별 결과
- 해당 cycle은 Long Fork의 예시이기도 함
- 첫 번째와 두 번째 트랜잭션은 하나의 논리적 상태 fork를 구성함
- 세 번째와 네 번째 트랜잭션은 두 번째 fork를 구성함
- 두 fork는 서로 다른 row를 갱신하지만 서로의 효과를 관찰하지 못함
- Short Fork, 즉 Write Skew는 관찰되지 않음
- 이 결과는 Amazon RDS for PostgreSQL이 Snapshot Isolation보다 약간 약한 Parallel Snapshot Isolation을 제공할 가능성을 시사함
- G-nonadjacent 이상 현상은 write-read edge만으로 연결된 경우와 4개를 넘는 트랜잭션을 포함한 경우까지 다양하게 나타남
- AWS가 지원한 가장 오래된 버전인 PostgreSQL 13.15부터 최신 버전인 17.4까지 테스트한 모든 버전에서 같은 종류의 이상 현상이 발생함
사용자가 점검할 부분
- Long Fork와 다른 G-nonadjacent cycle이 존재하므로 Amazon RDS for PostgreSQL Multi-AZ 클러스터는 Snapshot Isolation을 보장하지 않음
- 이 점에서 RDS for PostgreSQL Multi-AZ 클러스터는 이전 Jepsen 테스트에서 Strong Snapshot Isolation을 제공하는 것으로 보였던 단일 노드 PostgreSQL보다 약한 안전성 의미론을 제공함
- 사용자는 트랜잭션 구조가 Long Fork에 취약한지 살펴보거나, 의도한 불변 조건이 유지되는지 실험으로 검증할 수 있음
- read 트랜잭션은 트랜잭션 실행 순서에 대해 다른 트랜잭션과 서로 다른 결과를 볼 수 있음
- 이상 현상은 read-only secondary에 대한 쿼리와 관련된 것으로 보이므로, 다음 방식으로 Snapshot Isolation을 회복할 가능성이 있음
-
writer endpoint만 사용
- 안전이 중요한 모든 트랜잭션에 최소 1개 write 포함
- Jepsen의 검증은 실험적 접근이며, 버그의 존재는 증명할 수 있지만 부재는 증명할 수 없음
- 이 보고서는 RDS for PostgreSQL 동작을 자세히 조사한 결과가 아니라 예비적 탐색의 산물임
-