복구 절차의 정확성은 무엇을 성공의 증거로 읽고 어떤 순서로 실행하는지에 달려 있습니다. 이 두 조건은 정상 경로 테스트를 통과한 코드에서도 확인되지 않은 채 남을 수 있습니다.
분산 시스템의 복구 코드는 시스템이 이미 나빠진 상태에서 실행됩니다. 노드가 과부하를 받거나, 네트워크가 분할되거나, 두 저장소 사이의 쓰기가 중간에 끊긴 상태입니다. 정상 경로 테스트는 각 단계의 결과를 바로 확인할 수 있고 동작이 서로 겹치지 않는 조건에서 코드를 실행합니다.
복구 절차가 맞는지는 두 가지로 정해집니다. 첫째는 무엇을 성공의 증거로 삼는가입니다. 명령을 보낸 기록, 송신 측 체크포인트, 헬스체크 실패는 증거처럼 보이지만 판단하려는 동작의 결과를 직접 기록하지 않을 수 있습니다.
둘째는 실행 순서입니다. 복구 동작 여러 개가 겹치면 각 동작이 효력을 내는 시점의 차이가 결과를 바꿉니다. 클러스터 멤버십, 아웃박스 릴레이, 일관성 검사기를 다룬 연구와 운영 사례는 서로 다른 층에서 같은 결론을 가리키며, 이 글은 그 근거를 복구 절차 검증 체크리스트로 묶습니다.
겹친 복구 동작의 순서
Characterizing Metastable Faults and Failures(arXiv:2606.00942, 2026-05)는 상용 게임 플랫폼의 장애 사례를 분석합니다. 사용자가 수억 명인 이 플랫폼의 클러스터 매니저는 활성 워커를 W_min개 이상 유지합니다. 하트비트가 끊긴 워커에는 sleep을, 유휴 워커에는 wakeup을 보냅니다.
일부 워커가 크래시한 뒤 활성 워커 수는 W_min으로 돌아오지 않고 진동했습니다. 진동은 크래시한 워커가 복구된 뒤에도 계속됐고, 논문은 패킷 손실과 큐 지연이 원인이 아니었다고 밝힙니다. 워커와 클러스터 매니저의 구현에서는 원인을 짐작할 단서가 보이지 않았습니다.
원인은 두 가지 암묵적 성질의 조합입니다. 과부하를 받은 워커는 하트비트를 놓치고, wakeup은 sleep보다 효력이 늦게 납니다. 논문은 wakeup에는 가상 머신 프로비저닝이 들어가고 sleep은 상태 변경 하나라는 예를 듭니다.
flowchart TD
A["일부 워커 크래시"] --> B["남은 워커 과부하"]
B --> C["하트비트 누락"]
C --> D["sleep과 wakeup을 함께 발행"]
D --> E["sleep이 먼저 효력을 냄"]
E --> F["활성 워커 감소"]
F --> B
D --> G["wakeup이 늦게 완료"]
G --> H["새 워커도 과부하"]
H --> C
워커는 부하를 처리하려 하고 클러스터 매니저는 활성 워커 수를 채우려 하므로, 각 구성 요소는 따로 보면 안정화 방향으로 동작합니다. 장애는 sleep과 wakeup 사이의 시간 차이에서 생깁니다. 남은 워커가 과부하를 받지 않으면 하트비트 누락이 이어지지 않으므로 이 순환은 시작되지 않습니다.
제거를 잠정 동작으로 두는 규칙
논문은 sleep을 불안정화 동작, wakeup을 안정화 동작으로 분류합니다. 그리고 불안정화 동작은 잠정적이어야 하고(R1), 안정화 동작이 우선해야 한다(R2)는 두 원칙을 세웁니다. 제안된 클러스터 매니저는 기능을 바꾸지 않고 동작의 시점만 제어합니다.
이 클러스터 매니저는 타임아웃이 난 워커를 snoozing 상태로 두고, sleep을 인위적 지연 d'_sleep만큼 미룹니다. 대체 워커는 waking-up 상태가 되고 기동 확인을 받은 뒤에만 active가 되며, 확인을 받지 못하면 idle로 돌아갑니다. 유휴 워커가 없으면 기존 워커를 pending-timeout으로 기록하고, 하트비트가 오거나 유휴 워커를 찾을 때까지 재시도합니다.
이 상태 구분은 증거의 문제와도 닿습니다. wakeup을 보낸 기록은 기동의 증거가 되지 못하고, 기동 확인만 활성 워커 수에 반영됩니다. 논문은 상태를 idle과 active 두 가지로만 두면 겹친 동작 때문에 과부하 워커에 wakeup을 보낼 수 있다고 설명합니다.
지연 값도 겹친 동작을 기준으로 정합니다. d_wakeup < d'_sleep만 지키면 같은 타임아웃에서 나온 sleep과 wakeup 쌍의 순서만 보장됩니다. 다른 타임아웃에서 나온 wakeup보다 sleep이 먼저 효력을 낼 수 있으므로, 논문은 아래 조건을 요구합니다.
d_wakeup + T < d'_sleep
d_wakeup : wakeup이 효력을 내기까지 걸리는 시간
T : 하트비트 확인 주기이자 pending-timeout 재시도 주기
d'_sleep : sleep에 더하는 인위적 지연논문은 도메인 특화 언어(DSL)인 Nyx로 이 사례를 구현해 동역학을 살폈습니다. 고정 타임아웃을 쓰는 워커 40개 클러스터에서 100초를 모사하는 실행 1,000회로 벡터장을 만들었고, 생성에는 약 3분이 걸렸습니다. 이 결과는 진동 경향을 보여 주며, 운영 환경의 복구 시간을 측정한 값은 아닙니다.
성공의 증거를 읽는 위치
아웃박스와 변경 데이터 캡처(CDC) 릴레이의 복구에서는 증거를 어디서 읽는지가 정확성을 정합니다. 송신 측 상태만 읽는 복구의 정보 한계와 싱크 수락 기록으로 빠진 연산을 계산하는 방법은 2PC에서 커밋 로그로 옮겨간 정합성에서 다뤘습니다. 이 절은 어떤 기록이 증거로 인정되는지만 다룹니다.
Machine-Checked Dual-Write Recovery from a Commit Log(arXiv:2608.00501, 2026-08)는 증거의 자격을 좁게 정의합니다. 소스 아웃박스나 릴레이 커서를 조회해도 그 기록이 싱크의 수락 기록이 되지는 않습니다. 아웃박스 테이블이 수락 기록 역할을 하려면 싱크 소유의 수락 상태를 수락과 원자적으로 갱신해야 합니다.
수락 기록은 권위 있고 완전하며 결정 시점에 최신이어야 합니다. 논문은 오래된 복제본, 페이지로 나뉜 상태 조회 엔드포인트, 지연된 싱크 상태 프로젝션을 다른 관측으로 분류합니다. 이런 관측에는 별도 논증 없이 정리가 적용되지 않습니다.
| 복구가 읽는 기록 | 수락의 증거로 인정되는가 |
|---|---|
| 릴레이 커서, 송신 측 체크포인트 | 아니요. 전달과 기록 사이에 장애가 나면 두 상태를 구분하지 못함 |
| 소스 아웃박스 테이블 | 아니요. 싱크 소유 상태를 수락과 원자적으로 갱신할 때만 예외 |
| 오래된 복제본, 페이지 조회, 지연 프로젝션 | 정리의 적용 범위 밖. 별도 논증 필요 |
| 권위 있고 완전하며 최신인 싱크 수락 기록 | 예. 기존 중복 없음, 좌표의 엄격한 증가 등 네 조건(P1~P4)에서 빠진 연산을 계산 |
논문은 체크포인트를 더 신중하게 관리하거나 다르게 인코딩해도 이 공백이 닫히지 않는다고 설명합니다. 공백은 두 영속 쓰기 사이의 구간에 있고, 커서가 기록하는 방식에 있지 않기 때문입니다.
운영 사례도 같은 방향으로 읽을 수 있습니다. 토스페이먼츠는 결제 가용성을 우선하고 일부 외부 원천사 연동이 롤백을 지원하지 않아, Outbox 저장이 실패해도 결제 전체를 실패로 처리하지 않습니다. 2025-12 글에 따르면 장애 중 Outbox에 적재되지 않은 거래는 Elasticsearch(ES) 로그와 서버 로컬 디스크의 보조 로그로 식별했습니다.
토스페이먼츠는 장애 시간대 전체 거래의 이벤트를 재발행했고, 이벤트를 적재하는 서비스에서 승인과 취소가 중복으로 보였습니다. 이후 이벤트 헤더에 멱등키를 실어 소비자가 같은 건을 식별하도록 보완했습니다. 재발행 범위를 정할 때 소비자가 이미 받은 기록은 증거로 쓰이지 않았다고 읽히며, 이는 공개 본문에 근거한 추정입니다.
공개 본문에는 중복 제거 저장소, 보존 기간, 원자성 구현이 나오지 않습니다. 멱등키 저장과 상태 변경을 원자적으로 묶는 방법은 결제 시스템의 멱등성과 트랜잭션 무결성에서 다뤘습니다.
동작마다 맞는 증거
같은 신호도 어느 동작의 근거로 쓰는지에 따라 증거가 되기도 하고 되지 않기도 합니다. AWS의 2023-04 글은 Elastic Load Balancing(ELB) 헬스체크를 로컬 상태만 보는 shallow 검사와 외부 의존성까지 보는 deep 검사로 나눕니다. 그리고 각 검사를 Auto Scaling 그룹(ASG)의 교체 판단에 연결할 때의 결과를 비교합니다.
| 헬스체크 구성 | 처리하는 장애 | 복구 경로에서 남는 문제 |
|---|---|---|
| shallow 검사를 ASG에 연결 | 로컬 장애 인스턴스를 교체 | 의존성 장애와 단일 가용 영역(Availability Zone, AZ) 회색 장애를 우회하지 못함 |
| deep 검사를 ASG에 연결하지 않음 | 의존성 장애를 겪는 인스턴스에서 트래픽을 우회 | 로컬 장애 인스턴스가 교체되지 않을 수 있음 |
| deep 검사를 ASG에 연결 | 의존성 장애도 교체 판단에 반영 | 일시적 의존성 오류로 모든 대상을 동시에 교체할 수 있음 |
세 번째 구성에서 로드 밸런서는 fail-open으로 모든 대상에 트래픽을 보내지만, ASG는 모든 인스턴스를 비정상으로 보고 교체를 시작합니다. 서비스는 교체된 인스턴스가 부트스트랩을 마치고 헬스체크를 통과한 뒤에 돌아옵니다. 글은 이 시간이 일시적 문제가 끝나기를 기다리는 것보다 상당히 길 수 있다고 설명합니다.
AWS 글의 권고를 따르면 deep 검사 실패는 라우팅 판단의 증거로 쓰고 인스턴스 교체의 증거로는 쓰지 않습니다. 헬스체크를 통과하는 회색 장애를 탐지하는 방법은 적응적 라우팅의 회색 장애 탐지에서 다뤘습니다.
AWS 글은 deep 검사를 쓰면서도 로컬 장애를 교체하는 방법으로 heartbeat 표 패턴을 제시합니다. 인스턴스는 로컬 상태와 시각을 DynamoDB 표에 기록합니다. 1분마다 실행하는 watcher는 비정상 항목이나 일정 시간(예: 5분) 갱신이 없는 항목을 찾아 비정상으로 설정합니다.
watcher에는 교체 속도 제한도 둡니다. 글은 분당 1대만 교체하고 그 이상을 시도하면 사람에게 알리는 예를 듭니다. 1분, 5분, 분당 1대는 설명을 위한 예시이며 실험으로 정한 값이 아닙니다.
복구 상태를 관측하는 신호에도 같은 구분이 필요합니다. 우아한형제들의 2024-01 글은 Resilience4j의 fallback이 서킷브레이커 상태와 무관하게, 보호한 메서드가 실패하면 실행된다고 설명합니다. 그래서 fallback 실행 여부로는 서킷브레이커의 현재 상태를 알 수 없고, 글은 상태 전이 이벤트를 onStateTransition으로 따로 기록합니다.
검사기의 판정 기준
장애 주입 시험에서는 검사기의 판정 기준이 성공의 증거 역할을 합니다. Jepsen은 서버 장애, 패킷 지연, 네트워크 분할, 클럭 왜곡을 주입하고, 클라이언트가 관찰한 작업 이력을 일관성 검사기에 넘깁니다. 검사기가 통과로 판정한 이력은 그 기준 안에서만 정상입니다.
비트랜잭션 분산 스토리지의 일관성 검사를 일반화한 논문(arXiv:2608.17388, 2026-08)은 형식 검증을 거친 시스템이 보장을 어기는 이유를 세 가지로 듭니다. 그중 하나는 검증 과정이 특정 종류의 장애를 고려하지 않은 경우입니다.
이 논문은 검사기 기준의 한계도 지적합니다. 기존 비트랜잭션 검사기인 Porcupine과 Knossos는 선형성만 검사합니다. ZooKeeper는 선형성을 제공하지 않으므로, Knossos로 ZooKeeper 이력을 검사한 Jepsen 분석은 의미 있는 결과로 인정되지 않았습니다.
Wing-Gong 선형성 검사 알고리즘은 SingleOrder, RVal, RealTime 세 속성을 검사합니다. 논문의 도구 Pine은 SingleOrder를 유지하고 RVal을 Validity로, RealTime을 Ordering으로 일반화합니다.
Pine은 여기에 시스템이 응답에 담는 revision, zxid, 인스턴스 번호 같은 순서 정보를 작업 쌍 사이의 제약으로 넣습니다. 이렇게 하면 시스템이 실제로 약속하는 보장을 판정 기준으로 쓸 수 있습니다.
HoliPaxos 결과가 이 차이를 보여 줍니다. 네트워크 분할을 주입한 시험에서 선형성 검사는 통과했지만 인스턴스 번호 순서를 더한 HoliPaxos 기준은 위반으로 판정한 이력이 나왔습니다. 복제 로그를 조사하자 같은 작업이 인스턴스 451, 456, 461에서 세 번 커밋됐고, 클라이언트에는 마지막 결과만 돌아갔습니다.
응답의 ballot 번호가 258에서 770으로 바뀐 것은 분할 뒤 리더가 다시 선출됐음을 나타냅니다. 원인은 새 리더가 선출된 뒤 재시도된 작업을 멱등하지 않게 처리한 데 있었습니다. 리더 교체 직후 구간이 읽기 보장에 주는 영향은 Raft 리더 리스와 선형화 가능한 읽기에서 다뤘습니다.
저자들은 put을 increment로 바꾸자 선형성 검사에서도 위반이 드러나는 것을 확인했고, 작업마다 고유 ID를 붙여 결과를 저장하는 RIFL 방식으로 고쳤습니다. 같은 값을 덮어쓰는 put은 여러 번 적용돼도 읽기 값이 같으므로, 값만 보는 기준은 중복 적용을 놓칠 수 있습니다. 복구 경로의 버그가 보이는지는 장애 주입과 함께 판정 기준과 작업 부하의 형태에도 달려 있습니다.
논문은 8개 분산 스토리지 시스템을 시험해 4개 시스템에서 새 일관성 위반 버그 6건을 보고했고, 그중 5건은 기존 검사기로 찾을 수 없었습니다. 시스템별 기준으로 검사하면 기본 일관성 기준보다 최대 370배 빨랐고, 원문은 이 값이 어느 시스템에서 나왔는지 밝히지 않습니다. 두 결과는 아래처럼 다른 조건에서 나왔습니다.
| 측정 | 장애 주입 | 실행 조건 | 결과 |
|---|---|---|---|
| 버그 탐지 | Jepsen nemesis(Noop(주입 없음) 포함, 분할, 클럭 교란, hammer-time 등) | 복제본 5개, 클라이언트 5·25·125개, 실험마다 2분씩 3회 | 4개 시스템에서 새 버그 6건, 그중 5건은 기존 검사기로 탐지 불가 |
| 검사 성능 | 없음 | 복제본 5개, 클라이언트 스레드 8 | 시스템별 기준이 기본 기준보다 최대 370배 빠름 |
주장이 성립하지 않는 조건
모든 버그가 복구 경로에서 나오지는 않습니다. Pine이 EPaxos-revisited에서 찾은 버그 3건은 장애 주입 없이도 드러났습니다. 복구 경로를 따로 검증해도 정상 경로를 강한 기준으로 검사하는 일을 대신하지는 못합니다.
멤버십 연구의 수렴 증명은 좁은 전제 위에 있습니다. 과부하가 활성 워커 수에 따라 즉시 정해지고, W_noOverload = W_min이며, 충격은 일시적이라고 가정합니다. 제어 메시지 경로의 신뢰성은 저자들이 사례에서 추론한 전제이므로, 제어 경로까지 과부하를 받는 환경은 이 증명의 범위 밖입니다.
이중 쓰기 연구의 정리는 Isabelle/HOL 모델에서 P1~P4 조건 아래 증명됐습니다. 판정은 복구가 끝난 상태를 대상으로 하며, 이후 전달이 끝내 완료된다는 liveness는 주장하지 않습니다. 실제 싱크의 수락 기록이 권위성, 완전성, 최신성을 갖췄는지는 시스템마다 따로 확인해야 합니다.
세 연구 중 어느 것도 복구 경로를 따로 검증할 때 장애가 얼마나 줄어드는지 측정하지 않았습니다. 근거는 구현 검토나 약한 판정 기준으로 원인이 드러나지 않았던 사례들이며, 이를 일반 원칙으로 넓힌 것은 판단입니다. 장애 주입 환경과 시스템별 판정 기준을 만드는 비용이 이득보다 큰 시스템도 있을 수 있습니다.
복구 절차 검증 체크리스트
복구 동작마다 아래 질문에 답합니다. 앞의 네 질문은 증거, 다음 세 질문은 순서, 나머지 네 질문은 검증 방법에 관한 것입니다.
| 구분 | 확인할 질문 | 근거 사례 |
|---|---|---|
| 증거 | 명령을 보낸 기록과 효력을 확인한 기록을 다른 상태로 구분하는가 | waking-up과 active의 구분 |
| 증거 | 성공 여부를 결과를 소유한 쪽의 기록에서 읽는가 | 릴레이 커서는 싱크 수락 기록이 아님 |
| 증거 | 그 기록이 권위 있고 완전하며 결정 시점에 최신인가 | 오래된 복제본과 지연 프로젝션은 다른 관측 |
| 증거 | 신호의 범위가 그 신호로 결정하는 동작에 맞는가 | deep 헬스체크를 ASG 교체에 연결하지 않음 |
| 순서 | 용량을 줄이는 동작을 취소할 수 있고, 보충 동작이 효력을 낸 뒤에 실행되는가 | snoozing 상태와 R1·R2 |
| 순서 | 지연 값을 겹친 복구 동작 기준으로 정했는가 | d_wakeup + T < d'_sleep |
| 순서 | 자동 복구 동작의 속도를 제한하고 한도를 넘으면 사람에게 넘기는가 | watcher의 분당 교체 한도 |
| 검증 | 재시도와 재발행으로 생긴 중복을 수신 측이 식별할 수 있는가 | RIFL 방식 ID, 이벤트 헤더 멱등키 |
| 검증 | 판정 기준이 시스템이 약속하는 보장과 노출하는 순서 정보를 반영하는가 | Pine의 시스템별 기준 |
| 검증 | 시험 작업 부하가 중복 적용을 관측 가능한 형태로 만드는가 | put을 increment로 바꾼 확인 |
| 검증 | 복구 상태 전이를 부수 효과와 따로 기록하는가 | fallback 실행과 서킷브레이커 상태의 구분 |
이 질문들은 대부분 정상 경로 테스트로 답할 수 없습니다. 답하려면 장애 상태를 만들고, 그 상태에서 복구 동작이 읽은 증거와 실행 순서를 기록해야 합니다. 장애 판정 시각, 명령 발행 시각, 효력 확인 시각을 따로 남기면 순서 문제를 사후에 재구성할 수 있으며, 이는 위 사례에서 끌어낸 판단입니다.
장애 주입을 운영 연습으로 진행하는 Game Day 절차는 장애 대응과 인시던트 관리에 정리했습니다.
참고 자료
- Characterizing Metastable Faults and Failures, arXiv:2606.00942 (2026-05, v2 2026-06 기준)
- Machine-Checked Dual-Write Recovery from a Commit Log, arXiv:2608.00501 (2026-08, v5 2026-09 기준)
- Generalizing and accelerating consistency checking for non-transactional distributed storage systems, arXiv:2608.17388 (2026-08)
- Choosing the right health check with Elastic Load Balancing and EC2 Auto Scaling (AWS, 2023-04)
- 레거시 결제 원장을 확장 가능한 시스템으로 (토스페이먼츠, 2025-12)
- 개발자 의식의 흐름대로 적용해보는 서킷브레이커 (우아한형제들, 2024-01)
모든 수치는 2026-10 기준으로 각 원문에서 대조했습니다.
정리
복구 절차의 정확성은 무엇을 성공의 증거로 읽는지와 어떤 순서로 실행하는지에 달려 있습니다. 멤버십 사례에서는 sleep이 wakeup보다 먼저 효력을 내 활성 워커 수가 진동했고, 이중 쓰기 연구는 릴레이 커서나 지연된 프로젝션을 수락의 증거로 인정하지 않습니다. Pine의 HoliPaxos 사례에서는 리더 재선출 뒤 중복 커밋이 들어 있는 이력이 선형성 검사를 통과했습니다.
두 조건의 문제는 동작이 겹치지 않고 결과를 바로 확인할 수 있는 정상 경로에서는 드러나지 않는 경우가 많습니다. 그래서 복구 경로는 장애를 주입한 상태와 시스템이 실제로 약속하는 판정 기준으로 따로 검증해야 합니다. 다만 근거는 사례와 조건부 증명이며, 별도 검증이 장애를 얼마나 줄이는지는 측정되지 않았습니다.