본문으로 건너뛰기

6.4 synchronous mode

asynchronous replication 클러스터에서 failover는 언제나 트랜잭션 유실 가능성을 안고 있다. primary가 커밋을 반환한 뒤 아직 replica로 전송되지 않은 WAL은 primary와 함께 사라진다. synchronous mode는 이 구멍을 막기 위한 기능으로, Patroni가 PostgreSQL의 synchronous replication을 직접 관리하면서 failover 안전성과 연동한다. synchronous_mode는 세 값을 가진다.

동작
off (기본)synchronous replication 미사용
on지정된 sync standby가 커밋을 확인 (FIRST n 형식)
quorum후보 집합 중 아무나 n개가 확인 (ANY n 형식)

quorum 모드는 6.5 quorum commit에서 별도로 다루고, 이 절은 on 모드를 중심으로 본다.

Patroni가 직접 관리하는 synchronous_standby_names

synchronous_mode: on에서 DBA는 postgresql.confsynchronous_standby_names를 직접 수정하지 않는다. Patroni가 primary에 synchronous_commit: on을 유지하면서 synchronous_standby_names를 스스로 설정하고 갱신한다. 형식은 FIRST n 목록이다.

synchronous_standby_names = 'FIRST 2 (node1,node2)'

sync standby 수는 synchronous_node_count(기본 1)로 정한다. Patroni는 매 HA 루프마다 sync standby 선정을 재평가한다. 복제 연결이 유지되고 있고 제거 요청이 없는 노드는 그대로 두고, 빈자리가 생기면 가장 앞서 있는 replica를 새로 선정한다. maximum_lag_on_syncnode(기본 -1, 비활성)를 양수로 설정하면 그 바이트 수보다 lag이 큰 sync standby를 unhealthy로 보고 건강한 asynchronous replica와 교체한다.

# patronictl edit-config (dynamic configuration)
synchronous_mode: on
synchronous_node_count: 1
maximum_lag_on_syncnode: 16777216   # 16MiB, 기본은 -1(비활성)

/sync key와 세 가지 불변식

Patroni는 동기화 상태를 DCS의 /sync key에 기록한다. 최신 primary와 현재 synchronous standby 목록이 들어 있으며, 이 key와 PostgreSQL 설정 사이에 세 가지 불변식을 유지한다. 원문 표현 자체가 규칙의 정의라 그대로 옮긴다.

  1. “A node must be marked as the latest leader whenever it can accept write transactions.”
  2. “A node must be set as the synchronous standby in PostgreSQL as long as it is published as the synchronous standby in the /sync key in DCS.”
  3. “A node that is not the leader or current synchronous standby is not allowed to promote itself automatically.”

세 번째 불변식이 failover 안전성의 근거다. synchronous mode에서는 leader race 참여 자격 자체가 “마지막으로 알려진 leader이거나 /sync key에 오른 synchronous replica"로 좁혀지므로, 커밋을 확인하지 않은 노드가 승격되는 경로가 차단된다.

보장과 한계

핵심 보장은 다음 한 문장이다. synchronous mode가 켜져 있으면 Patroni는 “클라이언트에 성공 커밋 상태를 반환했을 수 있는 모든 트랜잭션을 보유했다고 확신할 수 없는 standby는 promote하지 않는다”. 승격 후보의 자격을 데이터 보유 여부로 판정한다는 뜻이다.

다만 문서가 명시하는 한계가 두 가지 있다.

첫째, backend cancel에 의한 유실이다. PostgreSQL synchronous replication의 구현 특성상, replication 확인을 기다리던 backend가 취소되면 그 변경은 로컬에 커밋된 채 다른 세션에 보이지만 아직 복제되지 않은 상태가 된다. 이 직후 failover가 겹치면 synchronous_mode_strict를 쓰더라도 트랜잭션을 잃을 수 있다.

둘째, primary와 현재 sync replica가 동시에 죽는 경우다. 남은 노드에는 모든 확정 트랜잭션이 없을 수 있으므로 이 시나리오에서 zero data loss는 보장되지 않는다.

sync standby가 사라졌을 때

sync standby가 crash하면 primary의 커밋은 다음 Patroni HA 루프가 돌아 primary를 standalone 모드로 전환할 때까지 블로킹된다. 문서 기준 최악 ttl초, 평균 loop_wait/2초 수준이다. 반면 정상 종료나 재시작은 커밋을 멈추지 않는다. standby가 PostgreSQL 종료 전에 primary에 신호를 보내 synchronous standby 의무에서 먼저 빠지기 때문이다.

standalone 전환 덕분에 synchronous mode는 2노드 클러스터에서도 쓸 만하다. sync standby가 사라지면 primary가 홀로 쓰기를 계속하되, 그 구간의 커밋은 미복제 상태다. 이때 primary까지 장애가 나고 적합한 standby가 없으면 자동 승격은 일어나지 않으며, 이전 primary가 복구되면 스스로 다시 primary가 된다.

standalone 전환이 싫다면 synchronous_mode_strict가 있다. sync standby 후보가 하나도 없을 때 synchronous replication을 끄지 않고, sync replica가 복귀할 때까지 모든 쓰기 요청을 블로킹한다. 무손실을 앞세우는 대신 가용성을 희생하는 선택이므로, 2노드 클러스터에서는 standby 하나의 장애가 곧 쓰기 중단이 된다는 점을 감안해야 한다.

sync 선정을 통제하는 tags

노드별 tag로 sync replica 선정에 개입한다.

  • nosync: true는 그 노드가 synchronous replica로 선정되는 것을 막는다. nostream: true도 같은 효과를 낸다.
  • sync_priority(Patroni 4.1.0 도입)는 synchronous_mode: on에서 sync replica 선정 우선순위를 정한다. 값이 높을수록 우선하고, 0 이하는 선정에서 배제되어 nosync: true와 같다.
Patroni의 sync_priority tag는 pg_stat_replication 뷰의 sync_priority 컬럼과 의미가 반대다. 뷰에서는 숫자가 작을수록 우선순위가 높지만, tag는 클수록 우선한다. 모니터링 쿼리와 tag 값을 나란히 볼 때 혼동하기 쉬운 지점이다.

정리

  • synchronous_mode: on이면 Patroni가 synchronous_standby_names(FIRST n 형식)를 직접 관리하고, /sync key의 불변식으로 “커밋 확인 없는 노드의 자동 승격"을 차단한다.
  • 보장에는 한계가 있다. backend cancel 직후의 failover, primary와 sync replica 동시 장애에서는 유실이 가능하다.
  • sync standby crash 시 커밋은 최악 ttl초 블로킹된 뒤 standalone으로 풀린다. 이 전환까지 막으려면 synchronous_mode_strict, 대가는 가용성이다.
  • 선정 통제는 nosync, sync_priority tag로 한다. sync_prioritypg_stat_replication과 부호 방향이 반대다.