# Jepsen: NATS 2.12.1

> Clean Markdown view of GeekNews topic #24933. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=24933](https://news.hada.io/topic?id=24933)
- GeekNews Markdown: [https://news.hada.io/topic/24933.md](https://news.hada.io/topic/24933.md)
- Type: GN+
- Author: [neo](https://news.hada.io/@neo)
- Published: 2025-12-09T11:33:20+09:00
- Updated: 2025-12-09T11:33:20+09:00
- Original source: [jepsen.io](https://jepsen.io/analyses/nats-2.12.1)
- Points: 1
- Comments: 1

## Topic Body

- 분산 메시징 시스템 **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)  
- 안전한 클러스터 구성 변경 절차는 아직 불명확  

---

## Comments



### Comment 47438

- Author: neo
- Created: 2025-12-09T11:33:20+09:00
- Points: 1

###### [Hacker News 의견](https://news.ycombinator.com/item?id=46196105) 
- 누군가 **복잡한 이론**을 건너뛰고 이런 시스템을 만들 때마다 aphyr가 그걸 무너뜨리는 걸 봄  
  이제는 AI가 프로젝트 문서를 읽고 **데이터 손실 가능성**을 마케팅 문구만으로 예측할 수 있을지도 궁금해짐  
  - 긴 수염을 쓰다듬으며 고개를 끄덕이는 기분임  
    사람들은 늘 “이론은 과대평가됐다”거나 “학교 교육보다 해킹이 낫다”고 말하지만, 결국 **문서화된 문제 공간**에서 스스로 발목을 잡는 꼴이 됨  
  - 나도 LLM에게 비슷한 일을 시켜봤는데, 결과가 꽤 **유용했음**
- NATS가 **CAP 이론을 무시**하는 듯한 느낌임  
  - 과소평가된 발언 같음
- 나는 NATS를 **in-memory pub/sub** 용도로 써왔는데, 그 부분에서는 훌륭했음  
  미묘한 **스케일링 디테일**도 잘 처리했음  
  하지만 persistence는 써본 적 없고, 이렇게까지 취약할 줄은 몰랐음  
  단일 비트 파일 손상에도 취약하다니 당황스러움
- 관련된 자료로, Jepsen과 Antithesis가 최근에 **분산 시스템 용어집**을 공개했음  
  참고하기에 아주 좋은 자료임 → [Jepsen Glossary](https://jepsen.io/blog/2025-10-20-distsys-glossary)
- aphyr.com/tags/jepsen과 jepsen.io/analyses의 **콘텐츠 차이**가 궁금했음  
  최근 aphyr.com을 발견했는데, 통찰이 많을 것 같아 기대 중임  
  - Jepsen은 원래 **개인 블로그 시리즈**로 시작했음  
    이후 jepsen.io는 **전문 프로젝트**로 발전했고, 약 10년 전부터 본격적으로 운영하기 시작했음
- “Lazy fsync by Default” 설정이 왜 존재하는지 의문임  
  벤치마크 성능을 높이려는 이유인가? 작은 클러스터에서는 이런 설정이 **문제의 원인**이 되곤 함  
  - 단순히 지연 시간뿐 아니라 **처리량(throughput)** 향상도 있음  
    많은 애플리케이션은 완전한 내구성을 요구하지 않기 때문에 lazy fsync가 유용할 수 있음  
    다만 기본값으로 두는 건 논란의 여지가 있음  
  - fsync를 왜 반드시 지연시켜야 하는지 늘 궁금했음  
    TCP corking처럼 **묶음 처리(batch)** 로 해결할 수 있을 것 같음  
  - 분산 시스템이라 가능한 일 중 하나임  
    lazy fsync로 인한 실패는 대부분의 노드에서 동시에 일어나지 않기 때문임  
  - 성능 향상을 위한 선택이 맞음  
  - **복제와 분산**을 통한 내구성과, lazy fsync로 확보한 처리량을 함께 노림
- JetStream의 **서버리스 대안**으로 [s2.dev](https://s2.dev)를 추천함  
  장점: 객체 스토리지 수준의 내구성을 가진 **무제한 스트림** 지원  
  단점: 아직 **consumer group** 기능이 없음  
  - Jepsen 테스트를 돌려본 적 있는지 궁금함
- NATS가 기본적으로 **2분마다만 fsync**를 수행하고, 즉시 ack를 반환한다는 점이 문제임  
  여러 노드가 동시에 장애를 겪으면 **커밋된 데이터 손실**이 발생할 수 있음  
  MongoDB 초창기 시절의 “웹 스케일” 마케팅이 떠오름  
  기본값은 항상 **가장 안전한 옵션**이어야 한다고 생각함  
  - NATS는 **클러스터 가용성만 보장**한다고 명확히 밝힘  
    그 점이 오히려 좋았고, 그 위에 시스템을 설계할 수 있었음  
    2018년에 사용했을 때는 성능도 좋고 관리도 쉬웠음  
  - 대부분의 현대 DB도 기본값이 완전 안전하지 않음  
    예를 들어 PostgreSQL의 기본 트랜잭션 격리 수준은 **read committed**임  
    Redis도 기본적으로 1초마다 fsync함  
  - Redis 클러스터는 다수 노드에 복제된 후에만 ack를 반환함  
    standalone Redis에서도 fsync 후 ack 설정이 가능하지만, OS 버퍼링 때문에 완전 보장은 어려움  
    결국 **ack의 의미**를 정확히 이해하는 게 중요함  
  - 대부분의 시스템은 **속도와 내구성의 절충**을 택함  
    안전한 기본값만 고집하면 성능이 크게 떨어지고, 사용자가 직접 튜닝해야 하는 부담이 커짐  
    예를 들어 Postgres의 기본 격리 수준도 약해서 **race condition**이 발생할 수 있음  
    참고: [Hermitage 테스트 글](https://martin.kleppmann.com/2014/11/25/hermitage-testing-the-i-in-acid.html)  
  - fsync는 **극단적인 선택지**밖에 없는 게 문제임  
    SSD 시대에는 group-commit 같은 중간 단계가 사라졌고, 이제는 **syscall 전환 비용**이 병목임  
    2분은 너무 긴 주기임 (fdatasync vs fsync 차이도 고려해야 함)
- 솔직히 예상은 했지만, 이렇게까지 심각할 줄은 몰랐음  
  그냥 **Redpanda**를 쓰는 게 나을 듯함
- NATS의 fsync 성능 경고를 개선할 수 있을지 궁금했음  
  일정 주기로 **배치 플러시(batch flush)** 를 하면 지연은 늘겠지만 처리량은 유지할 수 있지 않을까 생각함  
  - 고정된 주기가 아니라, **fsync가 진행 중일 때 쓰기 요청을 큐잉**하고 다음 배치에 함께 처리하면 됨  
    이는 **Paxos 라운드**를 묶는 방식과 유사함  
    한 라운드가 끝나면 바로 다음 배치를 시작하는 식으로 처리해야 함
