「붙어 있다」 와 「따라오고 있다」 는 다르다
한 줄 요약
복제 지연은 받기와 적용 사이에서 생기는 일이 많고, 그 사이는 pg_stat_replication 의 시간 숫자보다 LSN 의 바이트 차이가 정직하게 보여 준다. 그리고 WAL 을 붙잡는 장치(슬롯·피드백)는 하나같이 주 서버에 비용을 떠넘긴다.
왜 이게 필요했나
대기 서버가 "streaming" 으로 떠 있으면 다들 복제가 정상이라고 말한다. 그런데 보고서를 대기 서버에서 뽑는 팀은 어느 날 "새벽 이후 데이터가 없다" 고 한다. 연결은 멀쩡하고, 주 서버가 보낸 WAL 도 대기 서버 디스크에 다 내려와 있다 — 적용만 안 되고 있다. 반대쪽 사고도 있다. 대기 서버를 지우면서 슬롯을 남겨 두면 주 서버는 그 대기 서버가 언젠가 돌아올 줄 알고 WAL 을 지우지 않는다. 몇 주 뒤 주 서버 디스크가 가득 차 쓰기가 멈춘다.
어떻게 동작하나
대기 서버 안에는 일꾼이 둘이다. WAL 수신기(walreceiver)가 주 서버에서 받아 디스크에 쓰고(pg_last_wal_receive_lsn()), startup 프로세스가 그것을 한 줄로 적용한다(pg_last_wal_replay_lsn()). 둘은 따로 멈출 수 있다.
| 적용이 멈추는 이유 | 어떻게 보이나 |
|---|---|
누가 pg_wal_replay_pause() 를 불렀다 |
pg_get_wal_replay_pause_state() 가 paused, 로그에 recovery has paused |
복구 목표(recovery_target_*)에 닿았다 |
같은 paused. 로그는 recovery stopping at restore point. 여기서 resume 하면 복구가 끝나 승격한다 |
recovery_min_apply_delay |
멈춤이 아니다. 커밋 기록을 그 시간만큼 묵혔다가 적용한다(재해 복구용 「지연 대기 서버」 설정) |
| 대기 서버 조회와의 충돌 | 멈춤이 아니다. 주 서버의 VACUUM 이 지운 행을 대기 서버의 조회가 아직 보고 있으면, 적용은 max_standby_streaming_delay 만큼 기다렸다가 조회를 취소한다. -1 이면 끝없이 기다린다 |
바이트로 재라. pg_wal_lsn_diff(flush_lsn, replay_lsn) 은 받아 놓고 적용 못 한 양이다. replay_lag 같은 시간 숫자는 적용이 어떤 지점을 지날 때 계산되므로, 적용이 아예 서 있으면 마지막으로 계산된 작은 값이 그대로 남아 있기도 하다.
충돌을 피하는 두 길에는 각자 값이 있다. 기다리는 시간을 늘리면 대기 서버가 그만큼 뒤처지고, hot_standby_feedback = on 으로 "내 조회가 아직 이 행을 본다" 고 주 서버에 알리면 충돌은 사라지는 대신 주 서버의 VACUUM 이 그 행들을 못 지운다(슬롯의 xmin 으로 남는다). 공짜인 설정은 없다.
슬롯은 약속이다. 슬롯의 restart_lsn 이후 WAL 은 지워지지 않는다. 쓰는 대기 서버가 사라져도 약속은 남는다. max_slot_wal_keep_size 를 걸면 그 이상 붙잡은 슬롯은 체크포인트 때 끊기고(wal_status = lost) 주 서버는 산다 — 끊긴 대기 서버는 베이스백업부터 다시 떠야 한다.
재는 순서를 정해 두면 덜 헤맨다. 첫째, 주 서버에서 대기 서버마다 sent_lsn·flush_lsn·replay_lsn 을 나란히 놓고 어디서 벌어지는지 본다 — 보내기와 받기 사이면 네트워크, 받기와 확정 사이면 대기 서버 디스크, 확정과 적용 사이면 적용이다. 둘째, 적용 쪽이면 대기 서버에서 멈춤 상태를 묻고, 멈춤이 아니면 startup 프로세스가 무엇을 기다리는지(pg_stat_activity 의 wait_event)와 적용을 늦추는 설정을 본다. 셋째, 대기 서버 로그를 읽는다 — 멈춤·복구 목표·충돌 대기는 모두 로그에 한 줄씩 흔적을 남기고, 그 줄이 무엇을 하라고(또는 하지 말라고) 하는지까지 적혀 있다.
현장에서 만나는 모습
보고서용 대기 서버에 "절대 조회를 취소하지 마라" 며 max_standby_streaming_delay = -1 을 걸어 두었다가, 밤새 도는 분석 조회 하나 때문에 대기 서버가 여섯 시간 뒤처진 일이 흔하다. 모니터링은 연결 상태만 보고 있어 초록불이었다. 또 PITR 연습 때 쓴 recovery_target_name 이 설정 관리 도구를 타고 대기 서버로 퍼져, 어느 날 그 이름의 복원 지점을 만나자 대기 서버들이 일제히 멈춘 일도 있다. 누군가 "멈췄으니 resume" 을 눌렀고, 그 대기 서버는 조용히 승격해 쓰기를 받기 시작했다. 슬롯 쪽 사고는 더 조용하다. 읽기 부하용 대기 서버를 줄이면서 서버만 지우고 슬롯을 남겼더니, 주 서버의 pg_wal 이 몇 주에 걸쳐 수백 GB 로 불어 어느 새벽 디스크가 가득 찼다. 그동안 아무 경보도 울리지 않았다 — pg_replication_slots 에서 active 가 f 인 슬롯과 그 restart_lsn 이 얼마나 뒤에 있는지를 재는 경보가 없었기 때문이다.
다음 실습에서 할 것
대기 서버 하나를 세우고 적용만 멈춰 받기와 적용의 차이를 바이트로 잰다. 대기 서버 조회가 적용과 충돌해 취소되는 것, 피드백을 켜면 그 대가가 주 서버의 청소로 넘어가는 것을 일으킨다. 아무도 안 쓰는 슬롯이 WAL 을 100MB 넘게 붙잡게 한 뒤 상한으로 끊는다. 마지막은 사건이다 — 연결은 멀쩡한데 데이터가 오래된 대기 서버 셋을, 원인을 가려 재시작 없이(꼭 필요한 곳만 빼고) 고친다.