본문으로 건너뛰기

7.4 네트워크 분리

프로세스가 죽는 장애와 달리 네트워크 분리에서는 primary가 살아 있습니다. monitor에서 failover가 진행되는 동안 고립된 primary가 쓰기를 계속 받으면 두 노드가 동시에 primary가 됩니다. pg_auto_failover는 두 가지 장치로 이를 막습니다. 하나는 고립된 primary의 keeper가 스스로 멈추는 장치입니다. 다른 하나는 monitor가 새 primary를 열기 전에 기다리는 장치입니다. 두 가지 방식으로 분리한 뒤 이 순서를 측정했습니다.

실험 A: primary 완전 고립​

3노드(node2 primary)에서 docker network disconnect로 node2를 네트워크에서 분리했습니다. monitor, standby, 클라이언트 모두 node2에 접속할 수 없는 상태입니다.

구간시각 차측정값
분리 → 고립 primary의 demote_timeout 전환05:36:14.284 → 05:37:04약 50초
분리 → monitor의 wait_primary 지정05:36:14.284 → 05:37:08.89054.6초
쓰기 공백 (writer.sh)05:36:14.223 → 05:37:09.13754.9초

고립된 node2의 keeper 로그는 두 구간으로 나뉩니다. 처음 약 28초(05:36:15 ~ 05:36:42) 동안은 standby가 아직 연결되어 있다고 판단해 서비스를 계속합니다.

05:36:15 11 WARN Checking for network partitions...
05:36:15 11 WARN We lost the monitor, but still have a standby: we're not in a network partition, continuing.

그 뒤에는 이 줄이 사라지고, 20초 후 스스로 내려옵니다.

05:37:04 11 INFO Network in not healthy, switching to state demote_timeout
05:37:04 11 INFO FSM transition from "primary" to "demote_timeout": A failover occurred, no longer primary

keeper는 pg_stat_replication에 복제 사용자의 연결이 보이면 standby가 있다고 판단합니다. 네트워크가 끊겨도 walsender는 즉시 사라지지 않습니다. PostgreSQL의 wal_sender_timeout(pg_auto_failover 설정값 30s)이 지나야 연결이 끊어집니다. 처음 28초는 이 대기 시간에 해당하는 것으로 보입니다. 로그만으로 walsender 종료 시각을 직접 확인하지는 않았습니다. 따라서 이 부분은 추론입니다. 이후 keeper의 network_partition_timeout(기본 20초)이 지나 05:37:04에 전환했습니다. 결과적으로 고립 primary는 약 50초 후 내려왔습니다. monitor는 54.6초 후 새 primary의 쓰기를 허용했습니다.

분리 후 40초 동안 node2 내부에서 로컬 소켓으로 pg_is_in_recovery()를 확인한 결과는 매번 f였습니다. 그동안 node2가 여전히 primary였다는 뜻입니다.

재연결하자 node2는 6.1초 만에 secondary로 돌아왔습니다. 분리 중 생긴 WAL 차이 때문에 pg_rewind가 실제로 되감았습니다.

05:37:58 11 INFO pg_rewind: servers diverged at WAL location 0/9026A30 on timeline 6
05:37:59 11 INFO pg_rewind: need to copy 180 MB (total source directory size is 206 MB)
05:38:01 11 INFO pg_rewind: Done!

실험 B: 비대칭 분리, 클라이언트는 옛 primary에 접속 가능​

실제 장애에서는 primary가 monitor와 standby에만 연결되지 않고 애플리케이션과는 통신할 수 있는 경우가 흔합니다. 2노드 구성(node2 primary, node1 secondary)에서 partition.sh로 primary 내부에 blackhole route를 추가했습니다. monitor와 standby IP만 차단했습니다. app 컨테이너는 계속 옛 primary에 접속할 수 있습니다.

./scripts/partition.sh on node2 172.28.0.10 172.28.0.11 # monitor, node1 차단
docker compose exec -T app bash /lab/scripts/dualcheck.sh 85 > dual.log

dualcheck.sh는 0.5초마다 두 노드에 직접 접속해 synchronous_commit = local INSERT를 시도합니다. 성공하면 W를 기록합니다.

항목시각분리 기준
분리06:07:17.2000초
monitor의 node2 unhealthy 판정06:07:40.520+23.3초
monitor의 wait_primary 지정06:08:11.086+53.9초
옛 primary node2의 마지막 W06:08:08.757+51.6초
새 primary node1의 첫 W06:08:11.453+54.3초
두 노드가 같은 표본에서 모두 W없음0건

옛 primary는 51.6초까지 쓰기를 받았고, 새 primary는 54.3초부터 쓰기를 받았습니다. 두 구간은 겹치지 않았지만 여유는 2.7초였습니다. 옛 primary의 keeper는 이번에도 "50 seconds" 후 내려왔습니다.

06:08:08 11 INFO Failed to contact the monitor or standby in 50 seconds, at 20 seconds we shut down PostgreSQL to prevent split brain issues
06:08:08 11 INFO Network in not healthy, switching to state demote_timeout

실험 A보다 monitor의 unhealthy 판정이 늦었습니다(+10.5초 대 +23.3초). 컨테이너를 분리한 경우와 달리 blackhole은 연결 요청에 응답하지 않습니다. 따라서 health check가 health_check_timeout(5초)까지 기다렸습니다. 재시도까지 하면서 BAD 판정이 늦어진 것으로 보입니다.

경고

옛 primary가 분리 후 받은 비동기 쓰기 95건(dualcheck.sh의 W 표본)은 재합류할 때 pg_rewind로 사라졌습니다. 이 실험은 synchronous_commit = local로만 측정했습니다. 기본값이 on이었다면 해당 커밋들은 standby 확인을 기다리며 멈췄을 것입니다. 클라이언트는 성공 응답을 받지 못했을 것입니다. 이는 동기 복제 설계에 따른 설명입니다. 이 노트에서 별도로 측정하지는 않았습니다. synchronous_commit을 세션이나 role 단위로 낮춰 사용하는 애플리케이션에서는 분리 구간의 쓰기가 성공 응답 후에도 사라집니다.

무효로 처리한 시도​

실험 B 전에 두 번 실패했습니다. 첫 시도에서는 dualcheck 스크립트를 tmpfs인 컨테이너 /tmp에 docker cp로 넣었습니다. 그러나 파일이 없어 측정하지 못했습니다. 이때 사용한 별도 클라이언트 네트워크 방식에서는 node1이 unhealthy로 표시된 지 10초 만에 다시 healthy가 되었습니다. 따라서 failover가 일어나지 않았습니다. 네트워크 간 도달 경로가 불분명해 방식을 blackhole route로 바꿨습니다. 두 번째 시도에서는 partition.sh에 실행 권한이 없어 분리가 적용되지 않았습니다. 두 결과는 수치에서 제외했습니다.

순서가 안전을 만든다​

옛 primary의 강등(D)이 새 primary의 쓰기 허용(G)보다 먼저 일어나야 동시 쓰기가 발생하지 않습니다. 기본값에서는 두 실험 모두 이 순서를 지켰고 여유는 2.7~4.9초였습니다. v2.2 fault-tolerance.rst는 monitor가 새 primary 승격 전에 90초를 기다린다고 설명합니다. 그러나 실제 대기는 primary_demote_timeout 기본값인 30초였습니다. 이 순서를 깨는 설정은 7.6에서 확인합니다.

정리​

고립된 primary는 standby 연결이 사라진 뒤 network_partition_timeout 20초가 지나면 스스로 내려옵니다. 이 실습에서는 분리 후 약 50초가 걸렸습니다. monitor는 그보다 늦은 54초 무렵에 새 primary의 쓰기를 허용했습니다. 기본값에서 확인한 여유는 몇 초에 불과했습니다. 그사이 비동기로 커밋된 쓰기는 옛 primary와 함께 사라졌습니다. 이 순서는 두 실험에서 관찰된 결과일 뿐 v2.2가 보장하는 불변식은 아닙니다. monitor의 primary_demote_timeout과 keeper의 timeout 사이 관계를 검사하는 코드는 없습니다.