Jepsen: NATS 2.12.1

1 day ago 3

  • 분산 메시징 시스템 NATS JetStream의 내구성과 일관성을 Jepsen이 다양한 장애 환경에서 검증
  • 테스트 결과, 파일 손상(.blk, snapshot)전원 장애 시뮬레이션에서 데이터 손실과 split-brain 현상이 발생
  • JetStream은 기본적으로 fsync를 2분마다 수행해, 최근 승인된 메시지가 디스크에 기록되지 않은 상태로 남을 수 있음
  • 단일 노드의 OS 크래시만으로도 데이터 손실과 복제본 불일치가 유발될 수 있음
  • Jepsen은 NATS에 fsync=always 기본 설정 변경 또는 데이터 손실 위험 명시적 문서화를 권고

1. 배경

  • NATS는 메시지를 스트림으로 발행·구독하는 인기 있는 스트리밍 시스템
    • JetStream은 Raft 합의 알고리듬을 사용해 데이터를 복제하고, 최소 한 번(at-least-once) 전달을 보장
  • JetStream은 문서상 Linearizable 일관성항상 가용성을 주장하지만, CAP 정리에 따라 두 조건을 동시에 만족할 수 없음
  • NATS 문서에 따르면 3노드 스트림은 1대, 5노드 스트림은 2대의 서버 손실을 견딜 수 있음
  • 메시지는 서버가 publish 요청을 acknowledge한 시점에 “성공적으로 저장됨”으로 간주
  • 데이터 일관성을 위해 과반수(quorum) 노드가 필요하며, 5노드 클러스터에서는 최소 3대가 동작해야 새 메시지를 저장 가능

2. 테스트 설계

  • Jepsen은 JNATS 2.24.0 클라이언트와 Debian 12 LXC 컨테이너 환경에서 테스트 수행
    • 일부 테스트는 Antithesis 환경에서 공식 NATS Docker 이미지를 사용
  • 단일 JetStream 스트림(복제 5)을 구성하고, 프로세스 중단·충돌·네트워크 분할·패킷 손실·파일 손상 등을 주입
  • LazyFS 파일시스템을 사용해 fsync되지 않은 쓰기를 손실시키는 전원 장애 시뮬레이션 수행
  • 각 프로세스는 고유 메시지를 발행하고, 테스트 종료 후 모든 노드에서 acknowledged 메시지의 존재 여부를 검증
  • 메시지가 일부 노드에서만 존재할 경우 divergence(복제 불일치) 로 분류

3. 주요 결과

3.1 NATS 2.10.22의 전체 데이터 손실 (#6888)

  • 단순 프로세스 충돌만으로 JetStream 스트림 전체가 사라지는 현상 발견
  • "No matching streams for subject" 오류 발생 후 수 시간 동안 복구되지 않음
  • 원인은 리더 스냅샷 역전, Raft 상태 삭제 등으로, 2.10.23 버전에서 수정됨

3.2 .blk 파일 손상 시 데이터 손실 (#7549)

  • JetStream의 .blk 파일에 단일 비트 오류나 잘림(truncation) 발생 시 수십만 건의 승인된 쓰기 손실
    • 예: 1,367,069건 중 679,153건 손실
  • 일부 노드만 손상되어도 대규모 데이터 손실 및 split-brain 발생
    • 예: 노드 n1, n3, n5에서 최대 78% 메시지 손실
  • NATS는 해당 문제를 조사 중

3.3 스냅샷 파일 손상 시 전체 데이터 삭제 (#7556)

  • data/jetstream/$SYS/_js_/ 내 스냅샷 파일이 손상되면, 노드가 스트림을 고아(orphaned) 로 판단하고 데이터 전체 삭제
  • 소수 노드만 손상되어도 클러스터 과반수 불가 및 스트림 영구 불가용
  • 예: 노드 n3, n5 손상 → n3이 리더로 선출되어 jepsen-stream 전체 삭제
  • Jepsen은 리더 선출 시 손상된 노드가 리더가 되는 위험성을 지적

3.4 기본 fsync 설정으로 인한 데이터 손실 (#7564)

  • JetStream은 기본적으로 2분마다만 fsync 수행, 메시지는 즉시 승인
    • 결과적으로 최근 승인된 메시지가 디스크에 기록되지 않은 상태로 남음
  • 전원 장애나 커널 크래시 시 수십 초 분량의 승인된 메시지 손실 발생
    • 예: 930,005건 중 131,418건 손실
  • 단일 노드 장애가 연속 발생해도 전체 스트림 삭제 가능
  • 문서에는 이 동작이 거의 언급되지 않음
  • Jepsen은 fsync=always 기본값 변경 또는 데이터 손실 위험 명시적 경고를 권장

3.5 단일 OS 크래시로 인한 split-brain (#7567)

  • 단일 노드의 전원 장애 또는 커널 크래시만으로도 데이터 손실 및 복제 불일치 발생 가능
  • 리더-팔로워 구조에서 일부 노드가 메모리에만 커밋된 상태로 승인 후 장애 발생 시,
    다수 노드가 해당 쓰기를 잃고 새로운 상태로 진행
  • 테스트에서 단일 전원 장애 후 지속적 split-brain 발생
    • 노드별로 서로 다른 구간의 승인 메시지 손실 확인
  • Jepsen은 Kafka의 유사 사례를 인용하며, Raft 기반 시스템에서도 동일 위험 존재를 강조

4. 논의 및 결론

  • 2.10.22의 전체 데이터 손실 문제는 2.10.23에서 해결
  • 2.12.1에서는 파일 손상 및 OS 크래시로 인한 데이터 손실·split-brain이 여전히 발생
  • .blk 및 스냅샷 파일 손상 시 일부 노드에서 메시지 누락 또는 전체 스트림 삭제 발생
  • 기본 fsync 주기가 길어 복수 노드 동시 장애 시 승인된 데이터 손실 위험 존재
  • Jepsen은 fsync=always 설정 또는 문서 내 명확한 위험 고지를 제안
  • JetStream의 “항상 가용” 주장은 CAP 정리상 불가능, 문서 수정 필요
  • Jepsen은 버그 존재는 입증 가능하지만, 안전성의 부재는 증명 불가함을 명시

4.1 LazyFS의 역할

  • LazyFS를 이용해 fsync되지 않은 쓰기 손실을 시뮬레이션
  • 전원 장애 시 부분적 쓰기 손상(torn write) 등 다양한 스토리지 오류 재현 가능
  • 관련 연구 *When Amnesia Strikes (VLDB 2024)*에서 PostgreSQL, Redis, ZooKeeper 등에서도 유사 버그 보고

4.2 향후 과제

  • 단일 소비자 수준의 메시지 손실, 메시지 순서, Linearizable/Serializable 보장 검증은 미실시
  • 정확히 한 번(exactly-once) 전달 보장도 향후 연구 대상
  • 노드 추가·제거 시 문서 오류 및 필수 health check 단계 누락 발견 (#7545)
  • 안전한 클러스터 구성 변경 절차는 아직 불명확

Read Entire Article