7.6 failover 시간 조정
7.1의 51초 가운데 20초는 장애 확인에 걸린 시간이었습니다. 나머지 30초는 옛 primary의 강등을 기다린 시간이었습니다. 두 값은 모두 monitor GUC이며 조정할 수 있습니다. 이 장에서는 두 값을 10초와 15초로 줄인 뒤 primary kill과 비대칭 분리를 다시 측정했습니다. 쓰기 공백은 절반으로 줄었지만, 분리 실험에서는 두 노드가 동시에 쓰기를 받았습니다.
관련 설정
| 위치 | 설정 | 기본값 | 역할 |
|---|---|---|---|
| monitor | pgautofailover.health_check_period | 5000ms | health check 주기 |
| monitor | pgautofailover.health_check_timeout | 5000ms | health check 연결 timeout |
| monitor | pgautofailover.health_check_max_retries | 2 | 실패 판정 전 재시도 횟수 |
| monitor | pgautofailover.health_check_retry_delay | 2000ms | 재시도 간격 |
| monitor | pgautofailover.node_considered_unhealthy_timeout | 20000ms | 마지막 keeper 보고 후 이 시간이 지나야 unhealthy |
| monitor | pgautofailover.primary_demote_timeout | 30000ms | 새 primary를 열기 전 옛 primary의 강등을 기다리는 시간 |
| monitor | pgautofailover.startup_grace_period | 10000ms | monitor 기동 직후 failover 유예 |
| keeper | timeout.network_partition_timeout | 20 (초) | 고립된 primary가 스스로 내려오기까지 |
| PostgreSQL | wal_sender_timeout | 30s (pg_auto_failover 설정) | 끊긴 standby 연결을 walsender가 정리하는 시간 |
이 실습에서는 monitor 설정을 ALTER SYSTEM과 reload로 변경했습니다. v2.2 설정 문서는 postgresql.conf나 ALTER DATABASE pg_auto_failover SET ...을 사용한 뒤 reload하는 방법을 안내합니다. keeper 설정은 노드마다 pg_autoctl config set으로 변경합니다.
# monitor
docker compose exec -T monitor psql -d pg_auto_failover \
-c "alter system set pgautofailover.node_considered_unhealthy_timeout = '10s'" \
-c "alter system set pgautofailover.primary_demote_timeout = '15s'" \
-c "select pg_reload_conf()"
# 각 데이터 노드 (keeper)
pg_autoctl config set timeout.network_partition_timeout 20
pg_autoctl reload
실험 1: 줄인 설정으로 primary kill
2노드(node1 primary) 구성에서 node1을 kill했습니다.
pgautofailover.node_considered_unhealthy_timeout=10000ms
pgautofailover.primary_demote_timeout=15000ms
kill at 05:42:59.958 UTC
node2 reached wait_primary in 26.0s
writer: 100 ok, 106 FAIL, longest gap between ok starts 26.42s (05:43:00.019 -> 05:43:26.442)
| 구간 | 시각 차 | 기본값(run2) | 조정 후 |
|---|---|---|---|
| kill → 승격 시작 | 05:42:59.958 → 05:43:10.391 | 20.2초 | 10.4초 |
| 승격 시작 → wait_primary | 05:43:10.391 → 05:43:26.153 | 31.1초 | 15.8초 |
| 쓰기 공백 | 05:43:00.019 → 05:43:26.442 | 51.2초 | 26.4초 |
각 구간은 설정한 값만큼 정확히 줄었습니다. 이 실험에서는 unhealthy 표시와 승격 시작이 거의 같은 시각(05:43:10.366, 05:43:10.391)에 나타났습니다. health check는 이미 BAD 상태였습니다. 마지막으로 보고 중단 조건인 10초가 충족되었습니다.
실험 2: 같은 설정으로 비대칭 분리
7.4의 실험 B를 조정한 설정으로 반복했습니다. primary node1에서 monitor와 node2로 가는 트래픽만 blackhole로 차단했습니다. app은 계속 node1에 접속하게 했습니다.
partition on at 06:09:04.016 UTC
node1(old) last W: 06:09:54.227 | node2(new) first W: 06:09:42.817 | samples with both W: 22 06:09:42.817 ~ 06:09:54.227
06:09:54 11 INFO Network in not healthy, switching to state demote_timeout
| 항목 | 분리 기준 |
|---|---|
| 새 primary node2 첫 쓰기 | +38.8초 |
| 옛 primary node1 마지막 쓰기 | +50.2초 |
| 두 노드가 같은 표본에서 모두 쓰기 성공 | 22표본, 06:09:42.817 ~ 06:09:54.227 (11.4초) |
| 분리 후 node1이 받은 쓰기 표본 | 93건 |
| 재합류 후 새 primary에 남은 그 쓰기 | 0건 |
monitor는 새 primary를 더 빨리 열었습니다. 하지만 옛 primary가 스스로 강등된 시각은 이전과 마찬가지로 약 50초였습니다. keeper의 판단은 monitor GUC와 무관하기 때문입니다. 그 결과 두 노드는 11.4초 동안 모두 primary로 쓰기를 받았습니다. 재연결 뒤 node1은 pg_rewind로 85 MB를 되감았습니다(need to copy 85 MB (total source directory size is 110 MB)). 옛 primary가 받은 93건은 새 primary 어디에도 남지 않았습니다.
이 실험의 쓰기는 모두 synchronous_commit = local입니다. 기본값이 on이었다면 고립된 node1의 커밋은 standby 확인을 받지 못해 멈췄을 것입니다. 그렇다고 안전한 것은 아닙니다. 옛 primary에서는 읽기가 계속 성공합니다. 세션 단위로 동기 커밋을 끈 쓰기는 응답을 받고도 사라집니다.
필요 조건: 강등이 먼저
동시 쓰기를 막으려면 옛 primary가 내려온 뒤 새 primary가 열려야 합니다. 이 실습에서 관찰한 두 시각은 다음 요소로 구성됩니다.
- 새 primary 쓰기 허용: unhealthy 판정(보고 끊김
node_considered_unhealthy_timeout과 health check BAD 중 늦은 쪽) +primary_demote_timeout - 옛 primary 자기 강등: standby 연결이
pg_stat_replication에서 사라지는 시간(약wal_sender_timeout) +network_partition_timeout
기본값에서는 약 54초와 약 50초로 순서가 맞았습니다. 하지만 여유는 2.7초(실험 B)와 4.9초(실험 A)에 불과했습니다. 10초와 15초로 줄이자 약 39초와 약 50초로 순서가 뒤집혔습니다. primary_demote_timeout을 줄이려면 keeper의 network_partition_timeout이나 PostgreSQL의 wal_sender_timeout도 함께 줄여야 합니다. 그래야 옛 primary가 더 일찍 내려옵니다. 이 노트에서는 이 조합을 측정하지 않았습니다. 설정을 변경한 뒤에는 이 장의 비대칭 분리 실험을 반복해 볼 필요가 있습니다. 다만 동시 쓰기 표본이 0건이어도 해당 실험 조건에서 겹치지 않았다는 뜻일 뿐입니다. TCP 종료 시점이나 keeper polling이 다른 조건에서도 split brain이 없다는 증거는 아닙니다. v2.2에는 세 timeout 사이의 제약 검사나 fencing 확인이 없습니다. 따라서 timeout을 줄일 때는 최악의 조건을 고려해 충분한 여유를 두어야 합니다. 외부 fencing(전원 차단, 네트워크 차단)도 함께 설계해야 합니다.
wal_sender_timeout을 줄이면 느린 네트워크에서 정상인 standby 연결까지 끊길 수 있습니다. network_partition_timeout을 줄이면 monitor가 잠시 재시작하는 동안 primary가 불필요하게 내려올 위험이 있습니다. 이때 standby 연결까지 불안정하면 위험이 더 커집니다. 대기 시간 하나를 줄일 때마다 반대쪽의 오판 가능성이 커집니다.
정리
monitor GUC 두 개를 10초와 15초로 줄이자 primary kill 후 쓰기 공백이 51초에서 26초로 줄었습니다. 같은 설정에서 비대칭 분리를 만들자 두 노드가 11.4초 동안 동시에 쓰기를 받았습니다. 옛 primary의 쓰기 93건은 사라졌습니다. 기본값 51초는 단순히 느린 것이 아닙니다. 옛 primary가 먼저 내려오게 하는 시간 설정입니다. 줄이더라도 이 순서를 유지하는 범위 안에서만 줄입니다.