PostgreSQL 고가용성: 서버 세 대로 하는 active-standby
역할을 바꾸는 두 가지 길: 전환과 되감기
한 줄 요약
계획된 전환은 옛 주 서버의 마지막 WAL 까지 대기 서버가 받은 것을 확인한 뒤에 승격하는 일이고, 그렇게 하면 옛 주 서버는 되감기 없이 새 주 서버를 따라갑니다. 확인 없이 승격하면 두 서버의 역사가 갈라지고, 그때 되붙이는 도구가 pg_rewind 입니다.
왜 이게 필요했나
운영 중인 주 서버도 커널 패치, 하드웨어 교체, 메이저 버전 업그레이드 때문에 내려야 합니다. 그때 대기 서버를 승격시켜 서비스를 넘기는 것이 전환(switchover)입니다. 장애 조치와 달리 시간을 고를 수 있으니 아무것도 잃지 않아야 합니다. 그런데 절차를 하나 빠뜨리면 옛 주 서버를 다시 붙이려는 순간 이런 로그를 만납니다.
new timeline 2 forked off current database system timeline 1 before current recovery point 0/3047DA8
옛 주 서버가 새 주 서버의 갈림 지점보다 앞서 나가 있다는 뜻입니다. 이 상태에서는 따라갈 수 없고, 예전에는 베이스백업을 처음부터 다시 받는 것이 유일한 답이었습니다. 데이터가 수백 GB 면 몇 시간이 걸립니다.
어떻게 동작하나
타임라인. 대기 서버를 승격하면 새 타임라인 번호를 받고(1 에서 2), 그 갈림 지점을 00000002.history 파일에 적습니다. 그 뒤의 WAL 은 새 번호로 이어집니다. 다른 대기 서버는 recovery_target_timeline = latest(기본값) 덕분에 history 파일을 보고 새 타임라인으로 옮겨 탑니다. 단, 자기가 갈림 지점보다 뒤에 있지 않을 때만 그렇습니다.
깨끗한 종료가 전환의 열쇠입니다. pg_ctl stop -m fast(서비스 중지의 기본값)는 연결을 끊고, 대기 서버에 남은 WAL 을 다 보낸 뒤 종료 체크포인트를 쓰고 내려갑니다. 그 체크포인트 레코드까지 대기 서버가 받았다면, 옛 주 서버의 역사 전체가 대기 서버 안에 있습니다. 확인하는 방법은 두 숫자를 비교하는 것입니다.
옛 주 서버: pg_controldata 의 Latest checkpoint location
대기 서버: select pg_last_wal_replay_lsn() ← 이것이 같거나 커야 한다
이 조건이 맞을 때 대기 서버를 승격하면 갈림 지점이 옛 주 서버의 끝보다 뒤에 놓입니다. 옛 주 서버는 standby.signal 과 primary_conninfo 만 두고 띄우면 새 주 서버를 따라갑니다. -m immediate 로 내리면 종료 체크포인트가 없어 이 보장이 사라집니다.
되감기(pg_rewind). 역사가 갈라진 서버를 다시 붙이려면 갈림 지점 뒤에 바뀐 것을 버려야 합니다. pg_rewind 는 대상 서버의 WAL 을 갈림 지점부터 읽어 그 뒤에 바뀐 블록이 무엇인지 찾고, 그 블록만 원본 서버에서 가져와 덮습니다. 데이터 디렉터리 전체를 복사하는 베이스백업보다 훨씬 빠릅니다. 조건이 셋 있습니다.
| 조건 | 이유 |
|---|---|
wal_log_hints = on 또는 데이터 체크섬 |
힌트 비트만 바뀐 블록은 WAL 에 안 남는다. 켜 두어야 빠짐없이 찾는다 |
| 대상이 깨끗이 멈춰 있을 것 | 크래시 상태면 먼저 복구를 끝내야 한다 |
| 대상의 WAL 이 마지막 공통 체크포인트부터 남아 있을 것 | 갈라진 뒤 바뀐 블록을 대상의 WAL 에서 읽는다. wal_keep_size 나 WAL 보관소로 지킨다 |
| 원본에 접속할 권한 | 슈퍼유저, 또는 파일을 읽는 함수 네 개를 받은 계정 |
wal_log_hints 는 재시작해야 바뀌므로 사고가 난 뒤에 켜면 늦습니다. 클러스터를 만들 때부터 켜 두는 설정입니다.
되감기는 데이터를 버립니다. 갈림 지점 뒤에 옛 주 서버가 받은 커밋은 되감기와 함께 사라집니다. 그래서 되감기 전에 그 사이에 무엇이 들어왔는지 확인하는 절차가 따라야 합니다(다음 모듈의 주제입니다).
전환 순서를 한 장으로
- 앱의 쓰기를 멈추거나 연결 풀을 비운다
- 옛 주 서버를 fast 로 내린다
- 옛 주 서버의 종료 체크포인트 위치와 대기 서버의 재생 위치를 비교한다
- 대기 서버를 승격한다
- 옛 주 서버에 standby.signal 과 primary_conninfo 를 두고 띄운다
- 앱이 새 주 서버로 가는지 확인하고 쓰기를 다시 연다
3번이 맞지 않으면 4번으로 가지 않습니다. 대기 서버가 아직 받는 중이면 잠시 기다리고, 끝내 모자라면 옛 주 서버를 다시 띄워 전환을 미룹니다. 전환은 미룰 수 있는 작업이라는 점이 장애 조치와 가장 크게 다릅니다.
현장에서 만나는 모습
전환 절차서의 가운데에는 늘 "대기 서버가 종료 체크포인트까지 받았는가" 라는 확인이 있습니다. 이 한 줄을 건너뛴 전환은 대개 잘 되지만, 쓰기가 몰리는 시간에 하면 마지막 몇 초가 대기 서버로 가지 않고, 옛 주 서버는 되감기를 거쳐야 돌아옵니다. 또 하나 흔한 사고는 누군가 대기 서버를 "잠깐 시험해 보려고" 승격하는 것입니다. 승격은 되돌릴 수 없는 명령이라, 그 서버는 새 타임라인으로 갈라지고 되감기로만 돌아옵니다.
다음 실습에서 할 것
두 서버에 wal_log_hints 를 켜고, 전환 뒤 db1 이 쓸 슬롯을 db2 에 미리 만듭니다. db1 을 깨끗이 내리고 두 LSN 을 비교해 기록한 뒤 db2 를 승격하고, db1 을 되감기 없이 붙입니다. 그다음 누군가 db1 을 실수로 승격한 상황을 만들고, pg_rewind 로 되감아 다시 붙입니다.