본문으로 건너뛰기

3.3 2노드 failover 전이

primary와 secondary 두 대로만 구성된 group에서 primary를 잃으면 후보는 하나뿐입니다. 따라서 선출 단계 없이 곧바로 승격 절차를 시작합니다. 이 절차는 draining, prepare_promotion, stop_replication, demote_timeout, wait_primary 순서로 이어집니다. 옛 primary는 나중에 demoted, catchingup, secondary로 돌아옵니다. switchover와 장애 모두 같은 순서로 진행되지만 걸리는 시간은 크게 다릅니다. 이 장에서는 두 경우의 이벤트를 나란히 살펴봅니다.

전이 순서​

상태노드keeper가 하는 일 (v2.2 fsm.c)
draining옛 primaryPostgreSQL 중지. 중지하는 동안 남은 WAL을 동기 standby로 flush
prepare_promotion후보v2.2에서는 별도로 기다리지 않고 바로 완료 보고
stop_replication후보default_transaction_read_only = on 설정 후 승격
demote_timeout옛 primaryPostgreSQL 중지 상태 유지
wait_primary새 primarydefault_transaction_read_only 해제, 쓰기 허용
demoted옛 primaryPostgreSQL 중지 상태 유지
catchingup옛 primary새 primary를 기준으로 pg_rewind, 실패하면 pg_basebackup
노트

v2.2 fault-tolerance 문서에서는 prepare_promotion에서 standby가 WAL을 따라잡는다고 설명합니다. 실패하면 PREPARE_PROMOTION_CATCHUP_TIMEOUT 후 진행한다고도 설명합니다. 그러나 v2.2 소스의 fsm_prepare_standby_for_promotion()은 기다리지 않고 true를 반환합니다. 실측에서도 prepare_promotion 보고와 stop_replication 지정 시각이 같았습니다.

switchover: 5초​

run2에서 pg_autoctl perform switchover를 실행했을 때의 진행 표입니다.

05:27:10 | node1 | 1 | node1:5432 | primary | draining
05:27:10 | node2 | 2 | node2:5432 | secondary | prepare_promotion
05:27:10 | node2 | 2 | node2:5432 | prepare_promotion | stop_replication
05:27:10 | node1 | 1 | node1:5432 | primary | demote_timeout
05:27:10 | node1 | 1 | node1:5432 | draining | demote_timeout
05:27:10 | node1 | 1 | node1:5432 | demote_timeout | demote_timeout
05:27:11 | node2 | 2 | node2:5432 | stop_replication | stop_replication
05:27:11 | node2 | 2 | node2:5432 | stop_replication | wait_primary
05:27:11 | node1 | 1 | node1:5432 | demote_timeout | demoted
05:27:12 | node1 | 1 | node1:5432 | demoted | catchingup
05:27:15 | node1 | 1 | node1:5432 | catchingup | secondary
05:27:15 | node2 | 2 | node2:5432 | wait_primary | primary

옛 primary가 살아 있으므로 demote_timeout에 도달한 사실을 곧바로 보고했습니다. monitor는 이 보고를 받자마자 node2를 wait_primary로 전환했습니다. 쓰기 공백은 1.35초였습니다.

장애: 51초​

같은 구성에서 primary(node2)를 docker kill로 멈췄습니다.

kill at 05:27:40.263 UTC
05:27:48.528|node2|primary|primary|Node node 2 "node2" (node2:5432) is marked as unhealthy by the monitor
05:28:00.461|node1|secondary|prepare_promotion|Setting goal state of node 2 "node2" (node2:5432) to draining and node 1 "node1" (node1:5432) to prepare_promotion
05:28:00.461|node2|primary|draining|Setting goal state of node 2 "node2" (node2:5432) to draining and node 1 "node1" (node1:5432) to prepare_promotion
05:28:00.586|node1|prepare_promotion|prepare_promotion|New state is reported by node 1 "node1" (node1:5432): "prepare_promotion"
05:28:00.586|node1|prepare_promotion|stop_replication|Setting goal state of node 2 "node2" (node2:5432) to demote_timeout and node 1 "node1" (node1:5432) to stop_replic
05:28:00.586|node2|primary|demote_timeout|Setting goal state of node 2 "node2" (node2:5432) to demote_timeout and node 1 "node1" (node1:5432) to stop_replic
05:28:01.757|node1|stop_replication|stop_replication|New state is reported by node 1 "node1" (node1:5432): "stop_replication"
05:28:31.529|node1|stop_replication|wait_primary|Setting goal state of node 1 "node1" (node1:5432) to wait_primary and node 2 "node2" (node2:5432) to demoted after
05:28:31.691|node1|wait_primary|wait_primary|New state is reported by node 1 "node1" (node1:5432): "wait_primary"
구간경과 (kill 기준)원인
health BAD 표시+8.3초health check 3회 실패
failover 시작+20.2초보고가 20초 넘게 끊김(node_considered_unhealthy_timeout)
승격 완료(read-only)+21.5초stop_replication 전이
쓰기 허용+51.3초demote_timeout 지정 후 30초(primary_demote_timeout)

전이 순서는 같지만 switchover와 40초 넘게 차이가 납니다. 그 이유는 demote_timeout 대기입니다. 옛 primary가 상태를 보고할 수 없기 때문입니다. monitor는 옛 primary가 멈췄다고 판단할 수 있을 때까지 기다립니다. 이 30초의 근거는 2.5에 있습니다.

stop_replication 상태로 기다리는 30초 동안 새 primary는 이미 승격된 상태입니다. 하지만 default_transaction_read_only = on으로 설정되어 있습니다. target_session_attrs=read-write를 쓰는 클라이언트는 이 노드에 연결하지 않습니다. default_transaction_read_only는 세션에서 바꿀 수 있는 설정입니다. 따라서 연결 문자열에 이 조건이 없고 세션에서 값을 직접 바꾸는 클라이언트까지 막지는 못합니다.

옛 primary의 재합류​

옛 primary를 다시 시작하면 keeper는 monitor에서 demoted를 받습니다. 이어서 catchingup을 받습니다. demoted에서 catchingup으로 전이하는 함수는 fsm_rewind_or_init입니다. 먼저 pg_rewind를 시도합니다. 실패하면 pg_basebackup으로 전체 데이터를 다시 받습니다.

05:10:42 10 INFO FSM transition from "demoted" to "catchingup": A new primary is available. First, try to rewind. If that fails, do a pg_basebackup.
05:10:42 10 INFO Postgres needs to enter crash recovery before pg_rewind.
05:10:43 10 INFO pg_rewind: error: connection to server at "node1" (172.18.0.3), port 5432 failed: FATAL: no pg_hba.conf entry for host "172.18.0.4", user "pgautofailover_replicator", database "postgres", SSL encryption
05:10:43 10 WARN Failed to rewind demoted primary to standby, trying pg_basebackup instead

이 실습의 노드는 --dbname appdb로 만들었습니다. pg_autoctl이 추가하는 HBA 규칙은 appdb와 replication만 허용합니다. 하지만 v2.2의 pg_rewind는 dbname=postgres로 접속합니다. 이 때문에 pg_rewind 접속이 거부되어 전체 basebackup으로 넘어갔습니다. postgres DB에 대한 규칙을 추가한 뒤에는 pg_rewind가 바로 성공했습니다. 재합류에는 1.1초가 걸렸습니다.

05:30:24 10 INFO pg_rewind: connected to server
05:30:24 10 INFO pg_rewind: servers diverged at WAL location 0/7004D40 on timeline 3
05:30:24 10 INFO pg_rewind: no rewind required
경고

데이터가 작은 실습에서는 basebackup 대체가 몇 초 만에 끝났습니다. 수 TB 데이터에서는 같은 대체 작업이 수 시간 걸리는 전체 복사가 됩니다. 그동안 group에는 standby가 없습니다. --dbname을 postgres 이외의 값으로 쓴다면 postgres DB에 대한 pgautofailover_replicator 규칙을 미리 추가합니다. 그런 다음 switchover로 pg_rewind 경로를 확인해 둡니다(6.5).

정리​

2노드 failover는 선출 없이 draining, prepare_promotion, stop_replication, wait_primary 순서로 진행합니다. 옛 primary의 응답 여부에 따라 demote_timeout 대기 시간이 정해집니다. 이 차이로 switchover는 1초대, 장애는 50초대가 걸립니다. 옛 primary는 기본적으로 pg_rewind로 복귀합니다. 하지만 HBA 규칙 하나가 빠지면 별도 알림 없이 전체 복사로 바뀝니다.