본문으로 건너뛰기

6.2 자동 failover

primary가 죽으면 누군가 새 primary를 세워야 한다. 전통적인 PostgreSQL 운영에서는 이 판단과 승격을 사람이나 별도 도구(Patroni, repmgr 등)가 맡았다. CloudNativePG에서는 Operator가 그 역할을 직접 한다. 장애를 감지하고, 가장 앞선 replica를 골라 primary로 승격하고, 옛 primary를 replica로 되돌리는 일련의 과정을 자동으로 처리한다.

이 절에서는 그 흐름을 단계별로 따라가고, RPO(데이터 유실 허용량)와 RTO(복구까지 걸리는 시간)를 조절하는 파라미터를 짚는다.

장애 감지

Operator는 primary pod의 readiness probe로 상태를 살핀다. probe가 실패해 primary가 unhealthy로 판정되면 reconciliation loop가 돌면서 failover를 시작한다.

노드 자체가 통째로 죽는 경우는 감지 경로가 다르다. 노드가 응답을 멈추면 Kubernetes의 node lifecycle controller가 그 노드를 Unknown으로 표시하는데, 이 판정은 대체로 노드가 끊긴 뒤 40~55초쯤 지나서야 내려진다. 이 시간은 Kubernetes 쪽 기본 동작이라 CloudNativePG가 줄일 수 있는 값이 아니다.

승격 흐름

failover는 두 단계로 진행된다. 핵심은 “옛 primary를 확실히 멈춘 뒤에” 새 primary를 세운다는 점이다. 두 primary가 동시에 존재하는 split-brain을 막기 위해서다.

    sequenceDiagram
  participant OP as Operator
  participant P as 옛 primary
  participant R as replica
  OP->>P: TargetPrimary Pending 표시
  P->>P: 강제 shutdown
  Note over R: WAL receiver 정지
  OP->>OP: Lease 획득 후 leader 선출
  OP->>R: 가장 앞선 replica 승격
  R->>R: 새 primary 기동
  P-->>R: 재기동 후 replica로 합류
  
  1. TargetPrimary를 Pending으로 표시 — Operator가 실패한 primary를 pending으로 표시해 primary pod를 강제로 내린다. primary가 내려가야 replica들의 WAL receiver도 멈춰, 이후 승격 시점의 데이터 기준선이 고정된다.
  2. leader 선출과 승격 — WAL receiver가 멈추면 leader 선출이 일어나고, 선택된 인스턴스가 primary로 승격해 정상 운영을 재개한다. 옛 primary는 재기동하면서 자신이 더는 primary가 아님을 깨닫고 replica로 합류한다.

split-brain을 막는 Lease

CloudNativePG는 Cluster 이름을 딴 Kubernetes Lease 오브젝트로 “어느 순간에도 최대 하나의 인스턴스만 스스로 primary가 된다"를 보장한다. 승격하려는 인스턴스는 먼저 이 lease를 잡아야 한다. API server가 잠깐 불안정해도 오작동하지 않도록 .spec.primaryLease로 여유를 줄 수 있다.

필드기본값의미
leaseDurationSeconds15다른 인스턴스가 lease를 가져가기까지의 유효 기간
renewDeadlineSeconds10primary가 갱신을 포기하기까지 재시도하는 시간
retryPeriodSeconds2비보유 인스턴스의 획득·갱신 시도 주기
releasedLeaseDurationSeconds1clean shutdown 시 lease를 놓아줄 때의 TTL
spec:
  primaryLease:
    leaseDurationSeconds: 60
    renewDeadlineSeconds: 40
    retryPeriodSeconds: 15
admission webhook이 leaseDurationSeconds > renewDeadlineSecondsrenewDeadlineSeconds > retryPeriodSeconds × 1.2를 강제한다. 값을 어긋나게 넣으면 매니페스트 적용 단계에서 거부된다.

타이밍 조절: failoverDelay와 switchoverDelay

failover를 얼마나 빨리, 어떤 방식으로 시작할지는 두 파라미터로 조절한다.

  • .spec.failoverDelay (기본 0초): primary가 unhealthy로 감지된 뒤 failover를 시작하기까지 기다리는 시간. 순간적인 네트워크 흔들림이나 노드 불안정 때문에 성급하게 failover가 도는 것을 막는다. 값을 키우면 그만큼 오탐을 줄인다.
  • .spec.switchoverDelay: 옛 primary를 내릴 때 쓰는 fast shutdown의 타임아웃. 값을 크게 두면 밀린 WAL을 archive할 여유가 생겨 RPO에 유리하지만, 정상 복귀(RTO)가 그만큼 늦어진다.

옛 primary를 내릴 때 Operator는 두 방식을 순서대로 시도한다.

    flowchart TD
  START["primary shutdown 시작"]
  FAST["fast shutdown 시도"]
  CHK{"제한 시간 내<br/>완료?"}
  OK["정상 종료<br/>데이터 유실 없음"]
  IMM["immediate shutdown"]
  RISK["즉시 중단<br/>유실 가능"]

  START --> FAST --> CHK
  CHK -->|"예"| OK
  CHK -->|"아니오"| IMM --> RISK
  

fast shutdown은 클라이언트 연결이 끊기기를 기다리지 않고 밀린 WAL의 archive를 시도한다. switchoverDelay 안에 끝나지 못하면 immediate shutdown으로 넘어가 서버 프로세스를 곧바로 중단하는데, 이때는 WAL archive가 완결되지 못해 데이터 유실이 생길 수 있다.

failover vs switchover

두 용어는 자주 함께 등장하지만 성격이 다르다.

구분성격계기
failover계획에 없던 사건primary가 예기치 않게 죽음
switchover계획된 전환유지보수·업데이트를 위해 사람이 의도적으로 primary 교체

switchover는 6.3 rolling update에서 무중단 업데이트의 마지막 단계로 다시 등장한다.

failover quorum

synchronous replication과 결합해 데이터 안전을 한층 끌어올리는 옵션이다. .spec.postgresql.synchronous.failoverQuorum: true로 켠다.

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: cluster-example
spec:
  instances: 3
  postgresql:
    synchronous:
      method: any
      number: 1
      failoverQuorum: true
  storage:
    size: 1Gi

Dynamo의 정합성 모델 R + W > N을 적용한다. R은 승격 가능한 replica 수, W는 COMMIT 전에 쓰기를 확인한 replica 수, N은 잠재적 synchronous replica 총수다. R + W > N을 만족할 때만 failover가 진행되므로, 승격될 replica가 synchronous로 커밋된 데이터를 빠짐없이 갖고 있음을 보장한다. 클러스터의 quorum 상태는 FailoverQuorum CRD가 추적한다.

정리

  • Operator는 readiness probe로 primary 장애를 감지한다. 노드 통째 장애는 Kubernetes 판정을 따르므로 40~55초가 걸린다
  • failover는 “옛 primary 강제 종료 → leader 선출 → 승격” 두 단계로 진행되고, Kubernetes Lease가 split-brain을 막는다
  • failoverDelay로 오탐을, switchoverDelay로 RPO와 RTO의 균형을 조절한다
  • failoverQuorum은 synchronous 커밋 데이터를 가진 replica만 승격되도록 보장한다

다음 절(6.3)에서는 장애가 아니라 계획된 변경 상황, 즉 서비스를 멈추지 않고 버전과 설정을 바꾸는 rolling update를 본다.