본문으로 건너뛰기

6.1 Replication

CloudNativePG는 PostgreSQL이 원래 제공하는 physical streaming replication을 그대로 쓴다. 새로운 복제 엔진을 만든 것이 아니라, replica를 몇 개 둘지 선언하면 Operator가 그에 맞춰 인스턴스를 배치하고 복제 채널을 붙여 준다. Cluster의 instances 값이 곧 인스턴스 총 개수이며, 그중 하나가 primary, 나머지가 replica가 된다. 즉 replica 수 = instances - 1이다.

복제 자체는 primary가 남기는 WAL(Write-Ahead Log)을 replica가 네트워크로 받아 계속 재생하는 방식이다. Operator는 이를 위해 streaming_replica라는 전용 사용자를 만들고, 복제 채널을 TLS client certificate 인증으로 암호화한다. DBA가 pg_hba.conf나 인증서를 직접 손댈 일은 없다.

    flowchart TD
  APP["애플리케이션"]
  P["primary"]
  R1["replica 1"]
  R2["replica 2"]
  WAL["WAL stream"]

  APP -->|"쓰기"| P
  P --> WAL
  WAL -->|"streaming"| R1
  WAL -->|"streaming"| R2
  APP -.->|"읽기"| R1
  

기본 구성: 3-노드 Cluster

특별한 설정 없이 instances: 3만 선언해도 primary 1 + replica 2 구성이 완성된다. 복제는 기본이 asynchronous다.

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: cluster-example
spec:
  instances: 3
  storage:
    size: 1Gi
asynchronous replication에서는 primary가 replica의 응답을 기다리지 않고 커밋을 끝낸다. 응답 속도가 빠른 대신, primary가 갑자기 죽으면 아직 replica로 넘어가지 못한 최근 트랜잭션이 사라질 수 있다(RPO > 0).

Synchronous replication

데이터 유실을 허용할 수 없다면 synchronous replication을 켠다. 이때 primary는 지정한 수의 standby가 WAL을 받았다고 확인할 때까지 커밋을 멈춘다. 안전(RPO=0)을 얻는 대신 커밋 지연이 늘어난다.

CloudNativePG는 .spec.postgresql.synchronous 아래에서 두 값을 반드시 지정하게 한다.

  • method: any(quorum 기반) 또는 first(priority 기반)
  • number: 응답을 기다릴 synchronous standby 개수

quorum 기반 (method: any)

standby 목록 중 아무나 number개가 응답하면 커밋이 진행된다. 특정 standby에 얽매이지 않아 유연하다.

spec:
  instances: 3
  postgresql:
    synchronous:
      method: any
      number: 1

이 설정은 PostgreSQL의 synchronous_standby_names를 다음과 같이 자동 생성한다.

ANY 1 (cluster-example-2, cluster-example-3, cluster-example-1)

priority 기반 (method: first)

목록에서 앞쪽에 놓인 standby일수록 우선순위가 높다. 앞선 standby가 끊기면 즉시 다음 우선순위 인스턴스가 그 자리를 메운다.

synchronous_standby_names 내용 다듬기

Operator가 자동으로 채우는 이름 목록에 손을 대고 싶을 때 다음 옵션을 쓴다.

  • maxStandbyNamesFromCluster: Cluster 내부 pod 이름을 최대 몇 개까지 넣을지
  • standbyNamesPre: 로컬 pod 앞에 끼워 넣을 standby 이름
  • standbyNamesPost: 로컬 pod 뒤에 붙일 standby 이름
spec:
  postgresql:
    synchronous:
      method: first
      number: 2
      maxStandbyNamesFromCluster: 1
      standbyNamesPre:
        - angus
      standbyNamesPost:
        - malcolm

결과는 다음과 같다. 외부 standby(angus, malcolm)와 Cluster 내부 pod 하나가 섞인다.

FIRST 2 (angus, cluster-example-2, malcolm)

dataDurability: 안전과 가용성의 저울

synchronous를 켜면 새로운 고민이 생긴다. 응답할 standby가 부족하면 어떻게 할 것인가. dataDurability가 이 선택을 정한다.

required — 요구한 수의 synchronous standby가 살아 있지 않으면 쓰기를 멈춘다. 데이터 유실을 절대 허용하지 않는 모드다.

spec:
  postgresql:
    synchronous:
      method: any
      number: 1
      dataDurability: required

standby를 고를 때는 건강한 인스턴스 → 비정상 인스턴스 → primary 순으로 우선순위를 둔다.

Replication slot

Replication slot은 replica가 아직 받아 가지 못한 WAL을 primary가 함부로 지우지 않도록 잡아 두는 장치다. slot이 없으면 replica가 잠깐 뒤처진 사이 필요한 WAL이 삭제되어 복제가 깨질 수 있다.

HA replication slot

CloudNativePG는 고가용성을 위해 slot을 자동으로 관리한다. primary에만 두는 slot과, standby로 동기화되는 slot을 함께 유지해 failover 뒤에도 복제가 끊기지 않게 한다. 기본으로 켜져 있다.

spec:
  replicationSlots:
    highAvailability:
      enabled: true          # 기본값
      slotPrefix: "_cnpg_"   # 기본값
    updateInterval: 30       # standby 동기화 주기(초)

사용자 정의 slot 동기화

논리 복제 등으로 직접 만든 slot도 primary와 standby 사이에서 동기화할 수 있다. 특정 slot은 정규식으로 제외한다.

spec:
  replicationSlots:
    synchronizeReplicas:
      enabled: true          # 기본값
      excludePatterns:
        - "^foo"

slot이 잡는 WAL 상한

slot이 WAL을 붙들다가 디스크를 가득 채우는 사고를 막으려면, PostgreSQL 파라미터로 상한을 둔다(PostgreSQL 13+).

spec:
  postgresql:
    parameters:
      max_slot_wal_keep_size: "10GB"
slot은 뒤처진 replica가 다시 따라올 여지를 주지만, 그 replica가 오래 끊겨 있으면 primary의 pg_wal이 계속 부풀어 결국 디스크가 찬다. max_slot_wal_keep_size로 안전판을 두고, 죽은 replica를 방치하지 않는다.

정리

  • CloudNativePG는 PostgreSQL의 physical streaming replication을 그대로 쓰고, instances 값에 맞춰 primary·replica를 자동 배치한다
  • 기본은 asynchronous. RPO=0이 필요하면 .spec.postgresql.synchronousmethod(any/first)와 number를 지정한다
  • dataDurability가 안전(required)과 가용성(preferred)의 저울을 정한다
  • HA replication slot이 failover 뒤에도 복제를 지켜 주되, WAL이 무한정 쌓이지 않게 상한을 둔다

다음 절(6.2)에서는 primary가 실제로 죽었을 때 Operator가 어떻게 감지하고 새 primary를 세우는지, 자동 failover의 흐름과 타이밍을 본다.