6.1 장애 감지와 leader race
Patroni에는 primary의 생사를 판정하는 별도 모니터링 컴포넌트가 없다. DCS(Distributed Configuration Store)에 있는 leader key의 TTL 만료가 곧 장애 판정이다. leader가 제때 lease를 갱신하면 살아 있는 것이고, 갱신이 끊겨 key가 만료되면 죽은 것으로 간주한다. 이 단순한 규칙 위에서 자동 failover의 전 과정, 즉 leader race가 시작된다.
장애 판정은 leader key TTL 만료
leader key는 현재 leader인 멤버 이름을 담고 있으며 TTL이 걸려 있다. 정상 상태에서는 leader가 매 HA 루프마다 이 key의 lease를 갱신한다. primary 노드가 죽거나, Patroni 프로세스가 멈추거나, 네트워크가 끊겨 갱신이 계속 실패하면 key는 결국 DCS에서 만료되어 사라진다.
거꾸로 primary는 살아 있는데 DCS에만 닿지 못하는 경우도 있다. 단일 노드 관점에서는 DCS 자체 장애와 network partition을 구분하지 못하므로, Patroni는 최악의 경우인 network partition으로 간주하고 leader key가 만료되기 전에 스스로 demote한다. 이 자가 강등이 split brain을 막는 1차 방어선이며, 상세한 흐름은 Part II의 split brain 방지에서 다룬다.
판정 속도를 좌우하는 타이밍 파라미터는 세 개다. 모두 DCS에 저장되는 dynamic configuration이다.
| 파라미터 | 기본값 | 최소값 | 역할 |
|---|---|---|---|
ttl | 30초 | 20초 | leader key의 수명. 자동 failover 개시까지의 시간 |
loop_wait | 10초 | 1초 | HA 루프 1회 후 sleep 시간 |
retry_timeout | 10초 | 3초 | DCS와 PostgreSQL 연산 재시도 한도 |
세 값 사이에는 loop_wait + 2 * retry_timeout <= ttl 제약식이 있다. retry_timeout보다 짧은 DCS 순단이나 네트워크 순단은 leader의 demote를 유발하지 않는다.
ttl만 줄이면 제약식이 깨진다. loop_wait와 retry_timeout을 함께 줄여야 하는데, 그만큼 짧은 순단에도 demote가 일어나기 쉬워진다. 감지 속도와 오탐 내성은 맞바꾸는 관계라는 점을 고려해 세 값을 한 세트로 조정한다.leader race의 진행
leader key가 만료되면 모든 노드가 “자신이 최적 후보인지"를 검사하기 시작한다. 이때 각 노드는 다른 모든 Patroni 멤버의 GET /patroni를 호출해 서로의 WAL position과 상태를 조회하고, 자신보다 앞선 노드가 있는지 확인한다. 자격 검사의 세부 순서는 6.2 후보 선정에서 다룬다.
자신을 최적 후보로 판단한 노드는 모두 leader key 생성을 시도한다. 이 경쟁은 DCS의 원자적 key 생성 연산으로 판가름 나며, 여러 노드가 동시에 시도해도 성공하는 쪽은 하나다. 먼저 leader key를 취득한 멤버가 스스로 promote해 read/write 노드(standby cluster라면 standby leader)가 되고, 나머지 노드는 새 leader를 follow하도록 재구성된다.
flowchart TD
A["primary 장애 또는 고립"] --> B["leader key 갱신 중단"]
B --> C["TTL 만료, key 소멸"]
C --> D["leader race 시작"]
D --> E["각 노드 자격 자체 검사"]
E --> F["GET /patroni 상호 조회"]
F --> G{"자신이 최적 후보인가"}
G -->|아니오| H["race 불참"]
G -->|예| I["leader key 생성 시도"]
I --> J{"원자적 create 성공"}
J -->|성공| K["promote, 새 primary"]
J -->|실패| L["새 leader follow"]
H --> L
failover에 걸리는 시간
primary가 있는 노드 자체는 살아 있고 PostgreSQL 프로세스만 crash한 경우, Patroni는 곧바로 failover하지 않는다. primary_start_timeout(기본 300초) 동안 primary가 제자리에서 복구되기를 기다린 뒤에야 failover를 트리거한다. 0으로 설정하면 crash 감지 즉시(가능한 경우) failover한다.
공식 문서는 primary 장애 시 최악의 failover 시간을 다음 공식으로 명시한다.
loop_wait + primary_start_timeout + loop_wait기본값 기준 10 + 300 + 10 = 320초다. primary_start_timeout이 0이면 최악의 경우도 loop_wait 하나로 줄어든다. 다만 asynchronous replication 환경에서는 failover가 트랜잭션 유실로 이어질 수 있다. 유실 없이 빠른 전환까지 원한다면 6.4 synchronous mode를 함께 검토해야 한다.
타이밍 관련 설정을 한곳에 모으면 다음과 같다. 초기 클러스터라면 bootstrap.dcs에, 운영 중이라면 patronictl edit-config로 반영한다.
# bootstrap.dcs (또는 patronictl edit-config)
ttl: 30
loop_wait: 10
retry_timeout: 10
primary_start_timeout: 300
maximum_lag_on_failover: 1048576primary_start_timeout: 0은 노드는 멀쩡하고 PostgreSQL만 crash한 상황에서도 즉시 failover한다는 뜻이다. 재기동 한 번이면 끝날 장애에 timeline 분기와 구 primary 재합류 비용(6.6 pg_rewind와 timeline)을 치르게 되므로, 전환 속도가 정말 중요한 클러스터에만 적용할 만하다.정리
- 장애 감지는 leader key TTL 만료 그 자체다. 별도의 health check 데몬이 없고,
ttl,loop_wait,retry_timeout세 값이 감지 속도와 오탐 내성을 결정한다. - leader race에서는 각 노드가
GET /patroni로 상호 조회해 최적 후보를 가리고, DCS의 원자적 create에 성공한 하나만 promote한다. - PostgreSQL 프로세스 crash에는
primary_start_timeout(기본 300초)만큼 제자리 복구를 먼저 기다린다. 최악 failover 시간은loop_wait + primary_start_timeout + loop_wait다.