본문 바로가기
WIKI 기술 지식 베이스

자동 페일오버

원문 보기 위키 갱신

자동 페일오버 (Automated failover)

프라이머리에서 .spec.failoverDelay(기본값 0초)보다 오래 예기치 않은 오류가 지속되면, 클러스터는 페일오버 모드로 들어가요. 이런 일이 발생할 수 있는 예는:

  • 프라이머리 pod의 디스크 장애
  • 프라이머리 pod 삭제
  • 프라이머리의 postgres 컨테이너에서 어떤 종류든 지속적인 실패

페일오버 시나리오에서 프라이머리가 제대로 동작하고 있다고 가정할 수 없어요.

위와 같은 경우가 발생한 뒤, 프라이머리 pod의 준비성 프로브(readiness probe)가 실패하기 시작해요. 이는 컨트롤러의 리컨실레이션 루프에서 감지돼요. 컨트롤러는 두 단계로 페일오버 과정을 시작해요:

  1. 먼저 TargetPrimary를 pending으로 표시해요. 이 상태 변경은 프라이머리 pod를 종료시켜 레플리카의 WAL 수신기가 멈추도록 강제해요. 클러스터는 페일오버 단계("Failing over")로 표시돼요.
  2. 모든 WAL 수신기가 멈추면 리더 선출이 일어나고 새 프라이머리가 이름이 정해져요. 선택된 인스턴스가 프라이머리로의 승격을 시작하고, 이것이 완료되면 클러스터는 정상 운영을 재개해요. 그동안 이전 프라이머리 pod는 재시작되어 더 이상 프라이머리가 아님을 감지하고 레플리카 노드가 돼요.

:::info[Important] 두 단계 절차는 WAL 수신기가 질서 있게 멈출 수 있도록 하고, 실패한 프라이머리가 재시작 시 WAL을 다시 스트리밍하지 않도록 보장해요. 이러한 안전장치는 새 프라이머리와 레플리카 사이의 타임라인 불일치를 방지해요. :::

실패한 프라이머리가 종료되는 동안:

  1. 먼저 .spec.switchoverDelay 초를 타임아웃으로 PostgreSQL의 fast shutdown을 시도해요. 이 정상 종료는 대기 중인 WAL을 아카이빙하려 시도해요.
  2. fast shutdown이 실패하거나 타임아웃을 초과하면 PostgreSQL의 immediate shutdown이 시작돼요.

위 순서는 PostgreSQL에 접근 가능하다고 가정해요. 접근할 수 없으면(pg_isready에 응답하지 않으면), 인스턴스 매니저는 fast shutdown 없이 곧바로 immediate shutdown을 발행하므로, 대기 중인 WAL을 아카이빙하려는 시도도 없어요. 남겨진 .ready WAL 세그먼트들은 인스턴스가 레플리카로 클러스터에 다시 합류하기 전에 다시 시작될 때 아카이빙돼요.

.spec.switchoverDelay는 이 경로에는 적용되지 않아요: 이 값은 생략되는 fast shutdown의 타임아웃이기 때문이에요.

:::info "Fast" 모드는 PostgreSQL 클라이언트의 연결 해제를 기다리지 않고 진행 중인 온라인 백업을 종료해요. 모든 활성 트랜잭션은 롤백되고 클라이언트는 강제로 연결 해제된 다음 서버가 종료돼요. "Immediate" 모드는 정상 종료 없이 모든 PostgreSQL 서버 프로세스를 즉시 중단해요. :::

출처: 문서

본문

안전한 프라이머리 선출 (Safe primary election)

어떤 순간에도 최대 하나의 인스턴스만 프라이머리로 승격되도록 보장하기 위해, CloudNativePG는 위에서 설명한 선출을 Cluster의 이름을 따서 그 네임스페이스에 존재하는 Kubernetes Lease 객체로 뒷받침해요. 인스턴스는 프라이머리로 승격되기 전에 이 리스(lease)를 획득하고 보유해야 해요. 정상 종료 시 이전 프라이머리는 리스를 해제하므로, 적격한 레플리카가 리스 만료를 기다리지 않고 인계받을 수 있어요.

프라이머리 리스가 보호하는 것 (What the primary lease protects against)

레플리카가 이전 프라이머리에서 스트리밍하는 대신 아카이브에서 WAL 파일을 읽어 따라잡는 경우를 생각해보세요. 아카이브가 다음 기대 세그먼트에 대해 "file not found"를 반환하는 순간 PostgreSQL은 재생을 멈추고, 이를 WAL 스트림의 끝으로 취급해요. 이전 프라이머리가 아직 아카이빙을 끝내지 못한 WAL을 가지고 있는 동안 레플리카가 승격되면, 그 신호는 스트림의 진짜 끝보다 먼저 도착해요: 레플리카는 이전 프라이머리가 확인한 마지막 쓰기보다 더 이른 LSN에서 새 타임라인을 분기하고, 그 쓰기들은 손실돼요.

리스는 이전 프라이머리가 이를 해제할 때까지 승격을 보류해요. 정상 종료 시, PostgreSQL이 남은 WAL을 플러시하고 아카이빙한 뒤에 해제가 일어나므로, 인계받는 레플리카는 아카이브를 그 결정적인 끝에서 보게 돼요. 이전 프라이머리가 리스를 해제할 수 없으면(크래시, 노드 장애, 또는 인스턴스 매니저 자체가 도달 불가능), 리스는 leaseDurationSeconds가 경과한 후 만료되고 레플리카가 승격할 수 있어요. 그 경로에서는 아카이브가 따라잡히지 못했을 수 있고, 이전 프라이머리가 아카이빙을 끝내지 못한 모든 쓰기가 손실돼요.

프라이머리 격리 검사와의 관계 (Relationship with the primary isolation check)

리스는 Kubernetes API 서버로의 연결을 잃었지만 여전히 실행 중인 프라이머리를 펜싱하지 않아요. 그것은 프라이머리 격리(primary isolation) 검사의 역할이에요. 두 메커니즘은 상호 보완적이며 둘 다 기본으로 활성화돼 있어요:

  • 리스는 조기 승격을 방지해요: 이전 프라이머리가 여전히 리스를 보유하는 동안 레플리카는 승격할 수 없어요.
  • 격리 검사는 격리된 프라이머리를 비정상( unhealthy)으로 보고하고, kubelet이 정상 컨테이너 종료 경로로 이를 재시작해요. 그 경로는 스마트 종료(smart shutdown)예요: 새 연결을 거부하지만 이미 열려 있는 세션은 .spec.smartShutdownTimeout(기본 180초)이 경과할 때까지 계속 커밋하도록 허용하므로, 파티션 동안 쓰기 창을 완전히 닫기보다는 좁혀요.

둘 다 활성 상태로 유지하세요. 격리 검사를 비활성화하면 안전성을 오직 리스에만 맡기게 되고, 이전 프라이머리가 API 서버에 도달할 수 없지만 그 외에는 정상인 경우 리스 단독으로는 split-brain을 방지할 수 없어요. 그 쓰기 창이 워크로드에 중요하다면 .spec.smartShutdownTimeout: 0으로 설정해 전체 창을 기다리는 대신 재시작이 바로 fast shutdown으로 가게 하세요.

프라이머리 리스 검사 (Inspecting the primary lease)

리스는 클러스터의 이름을 공유하므로 직접 검사할 수 있어요. cluster-example 클러스터의 경우:

kubectl get lease cluster-example

HOLDER 열은 현재 리스를 보유하고 있는 pod, 즉 현재 프라이머리를 보고해요:

NAME              HOLDER              AGE
cluster-example   cluster-example-1   5m

효과 중인 리스 기간과 마지막 갱신 시각을 포함한 전체 그림을 보려면 다음을 사용하세요:

kubectl get lease cluster-example -o yaml

프라이머리 리스 튜닝 (Tuning the primary lease)

리스 타이밍은 .spec.primaryLease 아래에 노출되며 대부분의 클러스터에 적합한 값으로 기본 설정돼 있어요. 이 값들은 기반 Kubernetes 리더 선출 파라미터에 직접 매핑돼요.

필드 기본값 설명
leaseDurationSeconds 15 다른 인스턴스가 이를 획득하기 전에 리스가 유효한 기간
renewDeadlineSeconds 10 프라이머리가 리스 갱신을 포기하기 전에 계속 재시도하는 기간
retryPeriodSeconds 2 비보유자가 리스 획득·갱신을 재시도하는 빈도
releasedLeaseDurationSeconds 1 정상 종료 시 프라이머리가 리스를 해제할 때 기록되는 TTL

예를 들어 클러스터가 느리거나 잠시 도달 불가능한 API 서버에 더 관대하게 만들려면:

spec:
  primaryLease:
    leaseDurationSeconds: 60
    renewDeadlineSeconds: 40
    retryPeriodSeconds: 15

:::warning 페일오버 타이밍에 미치는 영향을 이해할 때만 이 값을 튜닝하세요: 간격이 길수록 클러스터가 일시적인 API 서버 비가용성에 더 관대해지지만, 정당한 승격은 느려져요. admission webhook이 두 가지 불변 조건을 강제해요: leaseDurationSeconds는 renewDeadlineSeconds보다 커야 하고, renewDeadlineSeconds는 retryPeriodSeconds 곱하기 1.2보다 커야 해요. 둘 다 기반 Kubernetes 리더 선출의 요구사항을 반영해요. :::

:::note leaseDurationSeconds와 retryPeriodSeconds는 서로 다른 두 타이밍을 관장해요. 갑작스러운 프라이머리 손실(이전 프라이머리가 리스를 해제하지 않음) 후에는, 후보가 리스가 한 번도 변경되지 않은 채로 완전한 leaseDurationSeconds 동안 관찰해야 승격할 수 있어요: 이것이 이전 프라이머리가 아직 살아 있을 수 있는 동안 조기 승격을 보류하는 것과 같아요. 정상 스위치오버(이전 프라이머리가 리스를 해제함) 후에는 그런 대기는 없어요. 후보는 다음 폴에서 해제된 리스를 그냥 알아차리므로, 인계 지연은 retryPeriodSeconds로 제한돼요. retryPeriodSeconds를 낮추면 조기 승격을 막는 인계 대기 시간을 줄이지 않고 스위치오버를 빠르게 하지만, API 서버에 대한 리스 갱신이 더 빈번해지는 대가가 있어요. :::

:::note 프라이머리 인스턴스는 리스를 처음 획득할 때 이 타이밍을 포착해요. 따라서 실행 중인 클러스터에서 .spec.primaryLease를 변경하는 것은 영향받는 프라이머리 Pod가 재시작된 후에만 적용돼요. 그때까지 프라이머리는 시작 시 사용했던 값을 계속 사용해요. :::

RTO와 RPO 영향 (RTO and RPO impact)

페일오버는 서비스 영향(RTO) 및/또는 데이터 손실(RPO)로 이어질 수 있어요:

  1. 프라이머리가 실패하기 시작한 시점부터 컨트롤러가 페일오버 절차를 시작하기 전까지, 전송 중인 쿼리, WAL 쓰기, 체크포인트 및 유사한 작업이 실패할 수 있어요.
  2. fast shutdown 명령이 발행되면 클러스터는 더 이상 연결을 받지 않으므로 서비스는 영향받지만 데이터는 손실되지 않아요.
  3. fast shutdown이 실패하면 immediate shutdown이 WAL 쓰기를 포함한 모든 대기 중인 프로세스를 중지해요. 데이터가 손실될 수 있어요. 이전 프라이머리에 도달할 수 없을 때도 동일하게 적용돼요: fast shutdown 단계가 완전히 건너뛰고 immediate shutdown이 곧바로 발행돼요.
  4. 프라이머리가 종료되고 새 프라이머리가 아직 시작되지 않은 동안, 클러스터는 프라이머리 없이 운영되므로 손상되지만—데이터 손실은 없어요.

:::note fast shutdown을 제어하는 타임아웃은 스위치오버의 경우와 마찬가지로 .spec.switchoverDelay로 설정돼요. fast shutdown의 시간을 늘리는 것은 RPO 관점에서 더 안전하지만, 정상 운영으로의 복귀를 지연시킬 수 있어—RTO에 부정적인 영향을 줘요. :::

:::warning “Instance Manager” 섹션에서 스위치오버 과정을 설명할 때 이미 언급했듯이, .spec.switchoverDelay 옵션은 PostgreSQL 데이터베이스의 RPO와 RTO에 영향을 줘요. 낮은 값으로 설정하면 RTO를 선호할 수 있지만 클러스터 레벨 및/또는 백업 레벨에서 데이터 손실을 초래할 수 있어요. 반대로 높은 값으로 설정하면 데이터 손실 위험을 제거하면서 스위치오버 동안 클러스터가 활성 프라이머리 없이 더 오래 남을 수 있어요. :::

지연 페일오버 (Delayed failover)

위에서 예고했듯이 .spec.failoverDelay 옵션은 프라이머리가 비정상으로 감지된 후 페일오버 절차의 시작을 몇 초 지연시킬 수 있게 해줘요. 기본적으로 이 설정은 0으로, 페일오버 절차를 즉시 트리거해요.

때로는 새 프라이머리로 페일오버하는 것이 프라이머리가 다시 온라인으로 돌아올 때까지 기다리는 것보다 더 파괴적일 수 있어요. 특히 여러 계층이 영향받는 네트워크 장애(예: 다운스트림 논리 구독자)나 페일오버 수행 시간이 예상 정전 기간보다 길 때 더 그래요.

페일오버를 지연하는 새 구성 옵션을 활성화하면 수명이 짧은 네트워크나 노드 불안정에 대해 조기 페일오버를 방지하는 메커니즘을 제공해요.

노드 레벨 장애 감지 (Detection of node-level failures)

프라이머리를 호스팅하는 노드가 도달 불가능해지면(예: kubelet 크래시 또는 노드와 Kubernetes API 서버 사이의 네트워크 파티션), 연산자는 pod의 Ready 컨디션에 의존해 프라이머리가 더 이상 서비스 불가능하다고 판단해요. 노드가 정상인 동안 kubelet은 준비성 프로브에서 이 컨디션을 최신 상태로 유지해요. 노드가 보고를 중단하면, Kubernetes 노드 수명 주기 컨트롤러가 노드를 Unknown으로 선언하는 즉시 컨디션을 False로 뒤집는 역할을 해요.

기본 kube-controller-manager 설정에서 전환은 --node-monitor-grace-period(Kubernetes 1.29-1.31에서 기본 40s, 1.32 이상에서 50s로 상향)가 관장해요: 그 창이 지나면 컨트롤러가 노드를 Unknown으로 표시하고, 같은 모니터링 패스에서 그 노드의 pod마다 Ready 컨디션을 뒤집는 패치를 발행해요. 실제로 연산자는 노드가 도달 불가능해진 후 약 40~55초 뒤에 프라이머리를 준비 안 됨(unready)으로 관찰해요 (grace period + 최대 한 번의 --node-monitor-period 폴, 기본 5s). 관리형 Kubernetes 배포(GKE, EKS, AKS)는 이 값을 튜닝할 수 있어요. 관찰된 타이밍이 일치하지 않으면 프로바이더 문서를 확인하세요. 그 후 페일오버 절차가 시작돼요(추가로 .spec.failoverDelay로 게이트).

Ready 컨디션 뒤집기는 부분적 존(partial-zonal) 또는 대규모 클러스터 장애 중 pod 축출(eviction) 을 조절하는 rate limiter(--node-eviction-rate, --secondary-node-eviction-rate, --unhealthy-zone-threshold)의 적용을 받지 않아요. 연산자는 존 또는 클러스터 전역 건강 상태와 무관하게, 컨트롤러가 패치를 발행하는 즉시 컨디션 뒤집기에 반응해요.

pod 축출(도달 불가능한 노드에서의 실제 삭제)은 node.kubernetes.io/unreachable NoExecute 테인트의 tolerationSeconds(기본 300s)가 주도하는 별도의 메커니즘이에요. 그 타이머는 연산자의 페일오버 결정을 보류하지 않아요. CloudNativePG는 Ready 컨디션이 뒤집히는 즉시 새 프라이머리를 승격해요. 그 시점에 격리된 노드의 kubelet은 이미 문제를 알아차렸어요: 기본 .spec.probes.liveness.isolationCheck.enabled: true로, 인스턴스 매니저는 API 서버도 나머지 클러스터에도 도달할 수 없게 되면 자신의 liveness 프로브를 실패시키고, kubelet은 약 3개의 프로브 주기(~30s) 안에 컨테이너를 재시작해요. 그 재시작은 정상 종료 경로, 즉 스마트 종료를 거쳐, 이미 격리된 프라이머리에서 열려 있던 세션이 그 ~30s 감지 창 위에 .spec.smartShutdownTimeout(기본 180초)이 경과할 때까지 계속 커밋하도록 허용해요. 완전한 고가용성(연산자가 정상 노드에서 이전 프라이머리를 재생성)은 여전히 테인트 기반 축출이 실제로 pod를 삭제하는 것에 게이트돼 있어요.

페일오버 쿼럼 (Failover Quorum, 쿼럼 기반 페일오버)

페일오버 쿼럼은 CloudNativePG 관리 PostgreSQL 클러스터에서 페일오버 이벤트 중 데이터 내구성과 안전성을 강화하는 메커니즘이에요.

쿼럼 기반 페일오버는 컨트롤러가 레플리카 쿼럼의 상태에 따라 레플리카를 프라이머리로 승격할지 결정할 수 있게 해줘요. 이는 동기 복제와 기본 자동 페일오버 절차가 제공하는 것보다 더 강한 데이터 내구성이 필요할 때 유용해요.

동기 복제가 활성화되지 않으면, 레플리카가 승격될 때 프라이머리보다 뒤처질 수 있으므로 페일오버 중 일부 데이터 손실이 예상되고 수용돼요.

동기 복제가 활성화되면, 모든 필수 동기 스탠바이가 WAL 데이터를 안전하게 받았다고 알려질 때까지 애플리케이션이 트랜잭션 커밋의 명시적 확인을 받지 못한다는 보장이 돼요. 이는 연산자가 가장 진보된 레플리카를 승격할 수 있음을 보장하기에 충분하지 않아요.

예를 들어, 동기 복제가 ANY 1 (...)로 설정된 3노드 클러스터에서 데이터는 커밋이 확인되기 전에 프라이머리와 하나의 스탠바이에 기록돼요. 프라이머리와 정렬된 스탠바이 모두가 사용 불가능해지면(예: 네트워크 파티션 중), 남은 레플리카는 최신 데이터를 갖지 못할 수 있어요. 이를 승격하면 애플리케이션이 커밋된 것으로 간주한 일부 데이터가 손실될 수 있어요.

쿼럼 기반 페일오버는 승격할 인스턴스에 모든 동기 커밋 데이터가 존재함을 연산자가 확인할 수 있을 때만 페일오버가 발생하도록 보장하고, 그렇지 않으면 발생하지 않도록 함으로써 이 위험을 해결해요.

이 기능은 사용자가 데이터 내구성과 데이터 가용성 사이에서 선호하는 절충을 선택할 수 있게 해줘요.

페일오버 쿼럼은 .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

하위 호환성을 위해 레거시 어노테이션 alpha.cnpg.io/failoverQuorum이 admission webhook에서 여전히 지원되며 Cluster 스펙 옵션보다 우선해요:

  • 어노테이션이 "true"로 평가되고 동기 복제 스탠자가 있으면, webhook이 자동으로 .spec.postgresql.synchronous.failoverQuorum을 true로 설정해요.
  • 어노테이션이 "false"로 평가되면 기능이 항상 비활성화돼요.

:::info[Important] 어노테이션이 스펙을 덮어쓰기 때문에, 이 실험적 기능 사용자들은 네이티브 .spec.postgresql.synchronous.failoverQuorum 옵션으로 마이그레이션하고 매니페스트에서 어노테이션을 제거할 것을 권장해요. 어노테이션은 deprecated이며 향후 릴리스에서 제거될 예정이에요. :::

어떻게 동작하나요 (How it works)

레플리카를 프라이머리로 승격하기 전에 연산자는 Dynamo R + W > N 일관성 모델[^1]의 원리를 따르는 쿼럼 검사를 수행해요.

쿼럼 페일오버에서 이 값들은 다음 의미를 가져요:

  • R은 승격 가능한 레플리카의 수(읽기 쿼럼)
  • W는 COMMIT이 클라이언트에 반환되기 전에 쓰기를 확인해야 하는 레플리카의 수(쓰기 쿼럼)
  • N은 잠재적으로 동기인 레플리카의 총 수

승격 가능한 레플리카(promotable replicas) 는 다음 속성을 가진 레플리카를 말해요:

  • 클러스터의 일부이고
  • 연산자에게 자신의 상태를 보고할 수 있고
  • 잠재적으로 동기이며

R + W > N이면, 승격 가능한 레플리카 중 적어도 하나가 모든 동기 커밋을 확인했음을 확신할 수 있고, 안전하게 프라이머리로 승격할 수 있어요. 그렇지 않으면 컨트롤러는 어떤 레플리카도 프라이머리로 승격하지 않고 상황이 바뀌길 기다려요.

사용자는 쿼럼 검사가 실패해도 kubectl cnpg promote 명령으로 레플리카의 프라이머리 승격을 강제할 수 있어요.

:::warning 수동 승격은 최후의 수단으로만 사용해야 해요. 진행하기 전에 데이터 손실 위험을 완전히 이해하고, 애플리케이션의 쓰기 워크로드 재개를 우선하는 것의 결과를 신중히 고려하세요. :::

추가 CRD가 클러스터의 쿼럼 상태를 추적하는 데 사용돼요. 쿼럼 페일오버가 활성화된 Cluster는 Cluster 리소스와 같은 이름의 FailoverQuorum 리소스를 가져요. FailoverQuorum CR은 쿼럼 페일오버가 활성화될 때 컨트롤러가 생성하고, 프라이머리 인스턴스가 리컨실레이션 루프 중에 갱신하며, 연산자가 쿼럼 검사 중에 읽어요. 동기 복제의 최신 알려진 구성을 추적하는 데 사용돼요.

:::info[Important] 사용자는 FailoverQuorum 리소스를 직접 수정해서는 안 돼요. PostgreSQL 구성 변경 중에 구성을 결정할 수 없을 때 FailoverQuorum 리소스는 재설정되어, 새 구성이 적용될 때까지 페일오버를 방지해요. :::

FailoverQuorum 리소스는 PostgreSQL 동기 복제와 함께 동작해요.

:::warning 클라이언트에 반환됐지만 동기적으로 수행되지 않은 COMMIT 연산(예: SET synchronous_commit TO local로 동기 복제를 명시적으로 비활성화해 만든 것)이 승격된 레플리카에 존재한다는 보장은 없어요. :::

쿼럼 페일오버 예시 시나리오 (Quorum Failover Example Scenarios)

다음 시나리오에서 R은 승격 가능한 레플리카 수, W는 커밋 전 쓰기를 확인해야 하는 레플리카 수, N은 잠재적으로 동기인 레플리카의 총 수예요. "Failover" 열은 쿼럼 페일오버 규칙 하에 페일오버가 허용되는지 나타내요.

시나리오 1: 3노드 클러스터, pod 장애

instances: 3, synchronous.number=1, dataDurability=required인 클러스터.

  • 프라이머리만 실패하면 승격 가능한 레플리카 2개가 남아요 (R=2). R + W > N(2 + 1 > 2)이므로 페일오버가 허용되고 안전해요.
  • 프라이머리와 레플리카 하나가 모두 실패하면 승격 가능한 레플리카 1개만 남아요 (R=1). R + W = N(1 + 1 = 2)이므로, 가능한 데이터 손실을 방지하기 위해 페일오버가 허용되지 않아요.
R W N Failover
2 1 2 ✅
1 1 2 ❌
시나리오 2: 3노드 클러스터, 네트워크 파티션

instances: 3, synchronous.number: 1, dataDurability: required인 클러스터가 네트워크 파티션을 겪어요.

  • 연산자가 프라이머리와 통신할 수 있으면 페일오버가 발생하지 않아요. 프라이머리가 어떤 스탠바이에도 도달할 수 없으면, 동기 복제 요구사항 때문에 트랜잭션을 커밋하지 않으므로 클러스터가 영향받을 수 있어요.
  • 연산자가 프라이머리에는 도달할 수 없지만 두 레플리카에는 도달할 수 있으면 (R=2) 페일오버가 허용돼요. 연산자가 레플리카 하나에만 도달할 수 있으면 (R=1), 동기인 것이 다른 쪽일 수 있으므로 페일오버가 허용되지 않아요.
R W N Failover
2 1 2 ✅
1 1 2 ❌
시나리오 3: 5노드 클러스터, 네트워크 파티션

instances: 5, synchronous.number=2, dataDurability=required인 클러스터가 네트워크 파티션을 겪어요.

  • 연산자가 프라이머리와 통신할 수 있으면 페일오버가 발생하지 않아요. 프라이머리가 최소 두 스탠바이에 도달할 수 없으면, 동기 복제 요구사항 때문에 트랜잭션을 커밋하지 않으므로 클러스터가 영향받을 수 있어요.
  • 연산자가 프라이머리에는 도달할 수 없지만 최소 세 레플리카에는 도달할 수 있으면 (R=3) 페일오버가 허용돼요. 연산자가 두 레플리카에만 도달할 수 있으면 (R=2), 동기인 것이 다른 쪽일 수 있으므로 페일오버가 허용되지 않아요.
R W N Failover
3 2 4 ✅
2 2 4 ❌
시나리오 4: 원격 동기 레플리카가 있는 3노드 클러스터

instances: 3이고 standbyNamesPre 또는 standbyNamesPost에 원격 동기 레플리카가 정의된 클러스터. 프라이머리가 실패하고 있다고 가정해요.

이 시나리오에는 중요한 고려 사항이 필요해요. standbyNamesPre 또는 standbyNamesPost에 나열된 레플리카는 R에 포함되지 않지만(승격될 수 없으므로), N에는 포함돼요(동기 쓰기를 받았을 수 있으므로). 따라서 synchronous.number <= len(standbyNamesPre) + len(standbyNamesPost)이면, 어떤 로컬 레플리카도 요구되는 데이터를 가졌다고 보장할 수 없으므로 페일오버가 불가능해요. 연산자는 검증 중에 이런 구성을 방지하지만, 명확성을 위해 일부 잘못된 구성이 아래에 표시돼요.

예시 구성:

구성 #1 (유효):

instances: 3
postgresql:
  synchronous:
    method: any
    number: 2
    standbyNamesPre:
      - angus

이 구성에서 프라이머리가 실패하면 R = 2(로컬 레플리카), W = 2, N = 3(로컬 레플리카 2 + 원격 1)으로 페일오버가 허용돼요. 추가 레플리카가 실패하면(R = 1) 페일오버가 허용되지 않아요.

R W N Failover
3 2 4 ✅
2 2 4 ❌

구성 #2 (잘못됨):

instances: 3
postgresql:
  synchronous:
    method: any
    number: 1
    maxStandbyNamesFromCluster: 1
    standbyNamesPre:
      - angus

이 구성에서 R = 2(로컬 레플리카), W = 1, N = 3(로컬 레플리카 2 + 원격 1)이에요. 이 설정에서는 페일오버가 불가능하므로, 이 구성으로 쿼럼 페일오버를 활성화할 수 없어요.

R W N Failover
1 1 2 ❌

구성 #3 (잘못됨):

instances: 3
postgresql:
  synchronous:
    method: any
    number: 1
    maxStandbyNamesFromCluster: 0
    standbyNamesPre:
      - angus
      - malcolm

이 구성에서 R = 0(로컬 레플리카), W = 1, N = 2(로컬 레플리카 0 + 원격 2)이에요. 이 설정에서는 페일오버가 불가능하므로, 이 구성으로 쿼럼 페일오버를 활성화할 수 없어요.

R W N Failover
0 1 2 ❌
시나리오 5: 3노드 클러스터, 선호 데이터 내구성(preferred), 네트워크 파티션

instances: 3, synchronous.number=1, dataDurability=preferred인 클러스터가 네트워크 파티션을 겪는 경우를 생각해보세요.

  • 연산자가 프라이머리와 API 서버 모두와 통신할 수 있으면, 프라이머리는 도달 불가능한 스탠바이를 synchronous_standby_names 집합에서 제거하며 계속 운영돼요.
  • 프라이머리가 연산자나 API 서버에 도달할 수 없으면 쿼럼 검사가 수행돼요. 프라이머리가 새 구성을 받을 수 없으므로 FailoverQuorum 상태가 변경될 수 없어요. 연산자가 두 레플리카에 도달할 수 있으면 페일오버가 허용돼요(R=2). 레플리카 하나에만 도달할 수 있으면(R=1) 페일오버가 허용되지 않아요.
R W N Failover
2 1 2 ✅
1 1 2 ❌

[^1]: Dynamo: Amazon's highly available key-value store

더 알아보기 (Learn more)