복제 (Replication/Standby)
복제 (Replication/Standby)
어느 날 갑자기 데이터베이스가 죽었는데 백업본도 깨져 있으면, 서비스가 통째로 멈추게 돼요. 이럴 때를 대비해서 PostgreSQL은 프라이머리(primary) 서버와 스탠바이(standby) 서버가 함께 일하는 고가용성 구성을 제공해요. 이 두 서버는 느슨하게(loosely) 묶여 있어요. 프라이머리 서버는 지속 아카이빙(continuous archiving) 모드로 동작하고, 각 스탠바이 서버는 지속 복구(continuous recovery) 모드로 동작하면서 프라이머리에서 생성된 WAL 파일을 읽어요.
이번 장에서는 이 복제가 어떻게 동작하는지, 각 구성 요소를 어떻게 설정하는지를 하나씩 살펴볼게요.
로그 쉬핑(Log Shipping)이란
한 데이터베이스 서버에서 다른 서버로 WAL 레코드를 직접 옮기는 일을 보통 log shipping(로그 쉬핑) 이라고 불러요. PostgreSQL은 파일 기반 로그 쉬핑을 구현하는데, WAL 레코드를 한 번에 하나의 파일(WAL 세그먼트)씩 전송해요.
- 레코드 기반 로그 쉬핑(record-based log shipping): WAL 변경 사항을 네트워크 연결 위로 더 잘게, 점진적으로 스트리밍해요. (26.2.5절 참고)
한 가지 꼭 기억해 둘 점이 있어요. 로그 쉬핑은 비동기(asynchronous) 방식이에요. 즉, WAL 레코드는 트랜잭션이 커밋된 이후에 전송돼요. 그래서 만약 프라이머리 서버에 치명적인 장애가 발생하면, 아직 전송되지 않은 트랜잭션은 유실될 수 있는 데이터 손실의 창(window) 이 존재해요.
복구 성능이 충분히 좋아서, 스탠바이가 활성화(activated)되면 보통 몇 순간 만에 완전히 사용 가능한 상태가 돼요. 그래서 이 구성을 warm standby(웜 스탠바이) 라고 부르며, 높은 가용성(high availability)을 제공해요.
계획(Planning)
스탠바이 서버를 운영하려면 먼저 몇 가지를 계획해야 해요.
- 아카이브 위치는 프라이머리가 죽어 있어도 스탠바이에서 접근 가능해야 해요. 즉, 프라이머리 서버가 아니라 스탠바이 서버 자체나 신뢰할 수 있는 다른 서버에 있어야 해요.
- 프라이머리 설정 파일에서
max_wal_senders를 충분히 크게 설정해야 해요. 복제 슬롯(replication slot) 을 사용할 예정이라면max_replication_slots도 충분히 높게 설정해 주세요.
프라이머리 준비(Preparing the Primary for Standby Servers)
고가용성을 목적으로 여러 대의 스탠바이 서버를 운영할 계획이라면, recovery_target_timeline을 latest(기본값)로 설정해서, 장애 조치(failover) 시 타임라인 변경을 잘 따라갈 수 있게 해 주세요.
스탠바이 서버 설정(Setting Up a Standby Server)
스트리밍 복제를 사용하려면 primary_conninfo에 libpq 연결 문자열을 채워 넣어요. 호스트 이름(또는 IP 주소)과 프라이머리에 연결하는 데 필요한 추가 정보를 포함해야 하고요, 프라이머리가 인증용 비밀번호를 요구한다면 그 비밀번호도 primary_conninfo에 지정해야 해요.
스탠바이 서버는 몇 대든 만들 수 있어요. 다만 스트리밍 복제를 쓴다면, 프라이머리의 max_wal_senders를 충분히 높여서 여러 스탠바이가 동시에 연결될 수 있게 해 주세요.
스트리밍 복제(Streaming Replication)
스트리밍 복제는 기본적으로 비동기예요(26.2.8절 참고). 이 경우 프라이머리에서 트랜잭션을 커밋한 뒤 변경 사항이 스탠바이에 보일 때까지 약간의 지연이 있어요.
- keepalive 소켓 옵션을 지원하는 시스템에서는
tcp_keepalives_idle,tcp_keepalives_interval,tcp_keepalives_count를 설정하면 프라이머리가 끊긴 연결을 재빨리 감지하는 데 도움돼요. - 스탠바이 서버로부터의 최대 동시 연결 수는
max_wal_senders로 설정해요.
인증(Authentication)
복제용 클라이언트 인증은 pg_hba.conf의 database 필드에 replication을 지정해서 제어해요.
프라이머리의 호스트 이름과 포트, 연결 사용자 이름, 비밀번호는 primary_conninfo에 지정돼요. 비밀번호는 스탠바이의 ~/.pgpass 파일에도 넣을 수 있어요(database 필드에 replication 지정).
예를 들어 프라이머리가 IP 192.168.1.50, 포트 5432에서 실행 중이고, 복제용 계정 이름이 foo, 비밀번호가 foopass라면, 스탠바이의 postgresql.conf에 다음과 같은 줄을 추가할 수 있어요:
primary_conninfo = 'host=192.168.1.50 port=5432 user=foo password=foopass'
모니터링(Monitoring)
프라이머리의 현재 WAL 위치는 pg_current_wal_lsn으로, 스탠바이의 마지막 WAL 수신 위치는 pg_last_wal_receive_lsn으로 확인할 수 있어요(자세한 내용은 Table 9.97, Table 9.98 참고). 스탠바이의 마지막 WAL 수신 위치는 ps 명령으로 확인할 수 있는 WAL 수신 프로세스(receiver)의 프로세스 상태에서도 표시돼요(27.1절 참고).
복제 슬롯(Replication Slots)
복제 슬롯은 프라이머리가 전송해야 할 WAL을 안전하게 보관하게 해 주는 기능이에요. 아래처럼 물리 복제 슬롯을 만들고 확인할 수 있어요:
postgres=# SELECT * FROM pg_create_physical_replication_slot('node_a_slot');
slot_name | lsn
-------------+-----
node_a_slot |
postgres=# SELECT slot_name, slot_type, active FROM pg_replication_slots;
slot_name | slot_type | active
-------------+-----------+--------
스탠바이가 이 슬롯을 사용하도록 하려면 스탠바이에서 primary_slot_name을 설정해요. 간단한 예시는 이렇습니다:
primary_conninfo = 'host=192.168.1.50 port=5432 user=foo password=foopass'
primary_slot_name = 'node_a_slot'
계단식 복제(Cascading Replication)
수신과 송신을 동시에 하는 스탠바이를 cascading standby(계단식 스탠바이) 라고 불러요. 프라이머리에 더 직접 연결된 스탠바이는 업스트림(upstream) 서버, 그보다 더 멀리 있는 스탠바이는 다운스트림(downstream) 서버라고 해요.
만약 업스트림 스탠바이가 승격(promote)되어 새로운 프라이머리가 되면, recovery_target_timeline이 'latest'(기본값)로 설정되어 있다면 다운스트림 서버들은 새 프라이머리에서 계속 스트리밍해요.
동기 복제(Synchronous Replication)
내장된 물리·논리 복제 시스템 외에도 pg_receivewal, pg_recvlogical 같은 특수 프로그램과 일부 서드파티 복제 시스템, 커스텀 프로그램이 있어요. 동기 복제 지원에 대한 자세한 내용은 각 문서를 확인해 주세요.
기본 구성(Basic Configuration)
synchronous_commit을remote_apply로 설정하면, 커밋 레코드가 재생(replayed) 되어 트랜잭션이 보이게 될 때 스탠바이가 응답 메시지를 보내요.synchronous_commit을remote_write로 설정하면, 각 커밋은 스탠바이가 커밋 레코드를 수신해서 운영체제에 썼다고 확인할 때까지 기다려요. 단, 스탠바이에서 데이터가 디스크에 플러시(flush) 될 때까지는 기다리지 않아요.
빠른 종료(fast shutdown)가 요청되면 사용자들은 대기를 멈추게 돼요. 하지만 비동기 복제를 사용할 때와 마찬가지로, 아직 전송되지 않은 모든 WAL 레코드가 현재 연결된 스탠바이 서버로 전송될 때까지 서버는 완전히 종료되지 않아요.
여러 동기 스탠바이(Multiple Synchronous Standbys)
우선순위(priority) 기반의 여러 동기 스탠바이 예시:
synchronous_standby_names = 'FIRST 2 (s1, s2, s3)'
쿼럼(quorum) 기반의 여러 동기 스탠바이 예시:
synchronous_standby_names = 'ANY 2 (s1, s2, s3)'
스탠바이 서버의 동기 상태는 pg_stat_replication 뷰로 확인할 수 있어요.
성능 계획(Planning for Performance)
동기 복제를 부주의하게 사용하면 응답 시간 증가와 높은 경합(contention) 때문에 데이터베이스 애플리케이션의 성능이 떨어져요. PostgreSQL은 애플리케이션 개발자가 복제를 통해 필요한 내구성(durability) 수준을 지정할 수 있게 해 줘요. 시스템 전체에 대해 지정할 수도 있고, 특정 사용자·연결, 또는 개별 트랜잭션에 대해서도 지정할 수 있어요.
예를 들어 애플리케이션 워크로드가 이렇게 구성되어 있을 수 있어요: 변경 사항의 10%는 중요한 고객 정보이고, 90%는 채팅 메시지처럼 유실되어도 비즈니스가 견딜 수 있는 덜 중요한 데이터인 경우. 이럴 때 전부 동기로 맞추면 불필요한 비용이 들어요.
고가용성 계획(Planning for High Availability)
synchronous_standby_names는 synchronous_commit이 on, remote_apply, 또는 remote_write로 설정되었을 때 트랜잭션 커밋이 응답을 기다릴 동기 스탠바이의 개수와 이름을 지정해요.
고가용성을 위한 가장 좋은 해결책은 요청한 만큼의 동기 스탠바이를 계속 유지하는 거예요. synchronous_standby_names로 여러 잠재적 동기 스탠바이를 지정하면 이걸 달성할 수 있어요. 우선순위 기반 동기 복제에서 목록의 앞쪽에 이름이 있는 스탠바이가 동기 스탠바이로 사용되고, 그 뒤에 나열된 스탠바이들은 현재 동기 스탠바이가 실패하면 그 역할을 이어받아요.
스탠바이는 streaming 상태에 도달해야 동기 스탠바이가 될 수 있어요. 이 상태는 pg_stat_replication 뷰로 확인할 수 있어요.
스탠바이에서 지속 아카이빙(Continuous Archiving in Standby)
공유 아카이브도 비슷하게 처리할 수 있는데, archive_command 또는 archive_library는 아카이빙할 파일이 이미 존재하는지, 존재한다면 내용이 같은지 검사해야 해요. 이렇게 하려면 archive_command 또는 archive_library를 더 신중하게 작성해야 해요. 내용이 다른 기존 파일을 덮어쓰지 않도록 하면서, 정확히 같은 파일이 두 번 아카이빙되면 성공을 반환해야 하니까요.
archive_mode가 on으로 설정되어 있어도, 복구 또는 스탠바이 모드 동안에는 아카이버가 활성화되지 않아요. 스탠바이 서버가 승격되면 승격 이후부터 아카이빙을 시작하지만, 스스로 생성하지 않은 WAL이나 타임라인 히스토리 파일은 아카이빙하지 않아요.