PostgreSQL 고가용성을 위한 동기 복제 설정
동기 스트리밍 복제를 사용하여 데이터 손실 제로(RPO=0) PostgreSQL 고가용성을 구성하는 방법을 알아봅니다. 이 단계별 튜토리얼에서는 `wal_level`, 복제 슬롯, `pg_basebackup`, 그리고 프라이머리 및 스탠바이 서버에서 `synchronous_commit` 매개변수를 올바르게 설정하여 중요 환경에서 트랜잭션 내구성을 보장하는 필수 구성을 다룹니다.
PostgreSQL 고가용성을 위한 동기 복제 설정
PostgreSQL 고가용성(HA)을 구성할 때는 일반적으로 어려운 질문으로 시작합니다: 프라이머리 서버가 커밋 직후에 사라지면 얼마나 많은 데이터를 손실할 수 있습니까? 일반적인 비동기 스트리밍 복제를 사용하는 경우 답은 "일부 손실 가능"입니다. 프라이머리는 스탠바이가 WAL 레코드를 수신하거나 재생하기 전에 트랜잭션이 커밋되었다고 애플리케이션에 알릴 수 있습니다. 그 짧은 시간 동안 프라이머리가 실패하면 승격된 스탠바이에 마지막 몇 개의 커밋된 트랜잭션이 포함되지 않을 수 있습니다.
동기 스트리밍 복제는 이러한 트레이드오프를 변경합니다. PostgreSQL은 커밋 성공을 보고하기 전에 하나 이상의 지정된 스탠바이를 기다립니다. synchronous_commit 수준에 따라 스탠바이는 WAL을 운영 체제에 쓰기만 하거나, 영구 저장소에 플러시하거나, 스탠바이에서 쿼리가 볼 수 있도록 재생하기만 하면 됩니다. 이를 통해 커밋된 트랜잭션에 대해 RPO를 0으로 만들 수 있지만, 쓰기 경로가 이제 네트워크와 스탠바이의 상태에 의존하게 됨을 의미합니다.
이러한 트레이드오프는 중요합니다. 동기 복제는 승인된 트랜잭션 하나라도 손실하는 것이 용납되지 않는 소규모 데이터 세트(결제, 계정 잔액, 재고 예약, 주문 상태, 감사 추적)에 적합합니다. 대용량 이벤트 로그, 클릭스트림 데이터, 메트릭 또는 노드 간 완벽한 내구성보다 가용성과 지연 시간이 더 중요한 워크로드에는 적합하지 않은 경우가 많습니다. 전역적으로 활성화하기 전에 워크로드 중 실제로 필요한 부분을 결정하십시오.
사전 요구 사항
시작하기 전에 동일한 주요 PostgreSQL 버전을 실행하는 두 대의 PostgreSQL 서버(프라이머리 및 스탠바이)가 설정되어 있는지 확인하십시오. 두 서버 모두 네트워크 연결이 가능해야 합니다. 이 가이드에서는 다음을 가정합니다:
- 프라이머리 호스트명/IP:
pg_primary - 스탠바이 호스트명/IP:
pg_standby - 복제 사용자:
repl_user - 데이터베이스 이름:
mydb
또한 초기 기본 백업을 위한 작업 백업 및 유지 관리 시간이 필요합니다. 예제는 PostgreSQL 12 이상을 가정하며, 스탠바이 모드는 standby.signal로 제어되고 연결 설정은 일반적으로 pg_basebackup -R에 의해 작성됩니다.
1단계: 프라이머리 서버 구성
프라이머리 서버는 스트리밍 복제를 활성화하고 동기 커밋에 필요한 Write-Ahead Log(WAL)를 관리하기 위한 특정 설정이 필요합니다.
A. 프라이머리에서 postgresql.conf 조정
프라이머리 서버의 postgresql.conf 파일을 편집합니다. 다음 매개변수는 스트리밍 복제에 필수적입니다:
# --- 복제에 필요 ---
listen_addresses = '*' # 스탠바이의 연결 허용
wal_level = replica # 'replica' 이상이어야 함 (예: 'logical')
max_wal_senders = 10 # 스탠바이의 최대 동시 연결 수
max_replication_slots = 10 # 지속적인 복제 스트림에 필요한 슬롯
# --- 동기 커밋에 필수 ---
synchronous_standby_names = 'FIRST 1 (standby1)' # application_name으로 필요한 스탠바이 지정
# --- 선택 사항이지만 권장 ---
wal_log_hints = on # 더 안전한 복제를 위해 권장되지만 WAL 볼륨 증가
shared_preload_libraries = 'pg_stat_statements' # 모니터링 사용 시
주요 매개변수 설명:
wal_level = replica: 스탠바이 서버가 데이터베이스 상태를 재구성할 수 있도록 WAL에 충분한 정보가 기록되도록 합니다. 동기 커밋의 경우 이 수준이 최소 요구 사항입니다.synchronous_standby_names: 어떤 스탠바이가 쓰기를 승인해야 하는지 정의하는 핵심 설정입니다. 여기서 이름은 복제 슬롯 이름이 아닌 복제 연결application_name값입니다.FIRST 1 (standby1)은 PostgreSQL이 해당 목록에서 첫 번째로 사용 가능한 동기 스탠바이를 기다린다는 의미입니다.ANY 1 (standby1, standby2)는 나열된 스탠바이 중 하나라도 커밋을 충족할 수 있음을 의미합니다.
B. 호스트 기반 인증 구성 (pg_hba.conf)
프라이머리 서버는 복제 목적으로 스탠바이 서버의 복제 사용자가 연결할 수 있도록 허용해야 합니다.
프라이머리의 pg_hba.conf에 항목을 추가합니다:
# TYPE DATABASE USER ADDRESS METHOD
host replication repl_user pg_standby/32 scram-sha-256
pg_standby/32를 스탠바이 서버의 실제 IP 주소 또는 서브넷으로 바꿉니다.
C. 복제 슬롯 및 사용자 생성
프라이머리 서버의 PostgreSQL에 연결하여 필요한 사용자와 복제 슬롯을 생성합니다.
1. 복제 사용자 생성:
CREATE ROLE repl_user WITH REPLICATION LOGIN PASSWORD 'a_strong_password';
2. 복제 슬롯 생성:
이 슬롯은 스탠바이가 수신을 확인할 때까지 WAL 세그먼트가 유지되도록 하여 스탠바이가 너무 뒤쳐져서 새 기본 백업이 필요해지는 것을 방지합니다. 슬롯은 유용하지만 스탠바이가 오랫동안 다운되면 디스크를 채울 수 있으므로 유지된 WAL을 모니터링하십시오.
SELECT pg_create_physical_replication_slot('standby1_slot');
슬롯 이름이 synchronous_standby_names와 일치할 필요는 없습니다. 이 예에서 standby1은 동기 스탠바이 선택에 사용되는 application_name이고, standby1_slot은 WAL 보존에 사용되는 물리적 복제 슬롯입니다.
D. 프라이머리 재시작
프라이머리 서버에서 PostgreSQL 서비스를 다시 시작하여 모든 구성 변경 사항을 적용합니다.
sudo systemctl restart postgresql
2단계: 스탠바이 서버 구성
스탠바이 서버는 복구 구성을 사용하여 프라이머리에서 WAL 레코드를 스트리밍하도록 구성됩니다.
A. 기본 백업
스트리밍을 시작하기 전에 스탠바이는 프라이머리의 데이터 디렉터리 전체 복사본이 필요합니다. 먼저 스탠바이에서 PostgreSQL을 중지합니다.
sudo systemctl stop postgresql
pg_basebackup을 사용하여 기본 백업을 수행합니다. 필요에 따라 경로 및 연결 세부 정보를 바꿉니다:
# pg_basebackup 유틸리티 사용 예
pg_basebackup -h pg_primary -D /var/lib/postgresql/15/main/ -U repl_user -P -Xs -R -W
-D: 스탠바이의 대상 데이터 디렉터리.-U: 복제 사용자.-P: 진행률 표시.-Xs: 기본 백업 중 필요한 WAL 파일 포함.-R:standby.signal파일을 자동으로 생성하고postgresql.auto.conf(또는 복구 구성)에 필요한 연결 설정을 생성합니다.
B. 스탠바이에서 postgresql.conf 구성
스탠바이에서 PostgreSQL이 프라이머리에 다시 연결하는 방법을 알고 있는지 확인합니다. 동기 복제의 핵심 세부 사항은 application_name입니다. synchronous_standby_names에 나열된 이름과 일치해야 합니다.
# --- 스탠바이에 필요 ---
primary_conninfo = 'host=pg_primary port=5432 user=repl_user password=a_strong_password application_name=standby1'
primary_slot_name = 'standby1_slot'
hot_standby = on # 복구/스탠바이 모드에서 읽기 쿼리 허용
C. 스탠바이 시작
스탠바이 서버에서 PostgreSQL 서비스를 시작합니다.
sudo systemctl start postgresql
3단계: 동기 커밋 확인 및 테스트
두 서버가 모두 실행 중이면 연결을 확인한 다음 동기 동작을 테스트합니다.
A. 복제 상태 확인
프라이머리 데이터베이스에 연결하고 pg_stat_replication 뷰를 확인합니다:
SELECT client_addr, application_name, state, sync_state FROM pg_stat_replication;
standby1에 대한 항목이 sync_state가 sync로 표시되어야 합니다. potential로 표시되면 스탠바이가 연결되어 있지만 현재 동기 커밋을 충족하는 스탠바이가 아닙니다. async로 표시되면 PostgreSQL이 이를 동기 스탠바이로 처리하지 않는 것입니다. application_name과 synchronous_standby_names의 철자를 확인하십시오.
B. 동기 커밋 테스트
PostgreSQL이 얼마나 기다려야 하는지를 결정하는 전역 매개변수는 synchronous_commit입니다. RPO=0을 위해서는 동기화를 강제하는 값을 사용해야 합니다.
1. 전역 동작 설정
1단계에서와 같이 프라이머리에서 synchronous_standby_names를 구성한 경우 기본 synchronous_commit = on은 동기 스탠바이가 WAL 레코드를 영구 저장소에 플러시할 때까지 기다립니다. remote_write는 스탠바이가 WAL 레코드를 운영 체제에 쓸 때까지 기다리며, 일반적으로 더 빠르지만 스탠바이 호스트가 플러시 전에 충돌하면 강력하지 않습니다. remote_apply는 스탠바이가 트랜잭션을 재생할 때까지 기다리며, 애플리케이션이 프라이머리에 쓴 직후 스탠바이에서 읽을 때 유용합니다.
대부분의 데이터 손실 제로 HA 설정에서 on은 실용적인 시작점입니다. 추가 지연 시간을 감수할 만큼 스탠바이에서 읽기 후 쓰기 동작이 중요한 경우에만 remote_apply를 사용하십시오.
# 프라이머리의 postgresql.conf에서
synchronous_commit = on
경고: 동기 커밋은 비동기 모드(
off또는local)에 비해 쓰기 지연 시간을 눈에 띄게 증가시킬 수 있습니다. 추가 지연 시간은 네트워크 왕복 시간, 스탠바이 WAL 쓰기 속도, 그리고remote_apply의 경우 재생 속도에서 발생합니다.
2. 트랜잭션 내에서 테스트
트랜잭션 방식으로 테스트하려면(전역 구성 변경 없이) 세션 또는 트랜잭션별로 설정할 수 있습니다:
-- 프라이머리에 연결
BEGIN;
SET LOCAL synchronous_commit = on;
INSERT INTO sales (item, amount) VALUES ('Widget A', 100);
-- 이 INSERT는 동기 스탠바이가 승인해야 하는 WAL을 준비합니다.
COMMIT;
-- COMMIT는 스탠바이가 WAL 쓰기를 승인한 후에만 성공합니다.
커밋 시 구성된 동기 스탠바이를 사용할 수 없으면 커밋이 대기합니다. 이것이 이 기능의 요점이지만 장애 중에 팀을 놀라게 할 수 있습니다: 프라이머리는 여전히 작동 중일 수 있지만 PostgreSQL이 동기 승인을 기다리고 있기 때문에 쓰기가 중단된 것처럼 보입니다. 동기 스탠바이가 사라질 때 자동으로 비동기 커밋으로 "대체"되는 일반적인 PostgreSQL 설정은 없습니다. 이 동작을 원한다면 HA 도구가 synchronous_standby_names를 변경해야 하거나, 데이터 손실 제로보다 가용성이 더 중요하다고 결정한 후 수동으로 변경하기 위한 런북이 있어야 합니다.
더 안전한 2-스탠바이 패턴
단일 동기 스탠바이는 모든 것이 정상일 때 강력한 내구성을 제공하지만 쓰기 가용성의 단일 지점을 생성합니다. 해당 스탠바이가 다운되거나 느리거나 프라이머리에서 격리되면 커밋이 대기합니다. 프로덕션에서는 일반적으로 최소 두 개의 스탠바이를 실행하고 하나의 동기 승인을 요구하는 패턴이 사용됩니다:
synchronous_standby_names = 'ANY 1 (standby1, standby2)'
이 설정을 사용하면 두 스탠바이 중 하나가 커밋을 충족할 수 있습니다. standby1이 재부팅 중이면 standby2가 여전히 쓰기를 승인할 수 있습니다. 두 복제본을 모두 모니터링해야 합니다. 한 스탠바이의 장기 중단으로 인해 복제 슬롯이 많은 양의 WAL을 보유할 수 있지만, 단일 스탠바이 장애로 인해 프라이머리가 중단될 가능성은 줄어듭니다.
두 개의 승인을 요구하는 것도 가능합니다:
synchronous_standby_names = 'ANY 2 (standby1, standby2, standby3)'
이는 더 엄격한 내구성 선택입니다. 일반적으로 지연 시간이 매우 짧은 링크와 커밋 전에 둘 이상의 원격 복사본이 필요한 명확한 이유가 있는 환경에 예약됩니다. 많은 애플리케이션 데이터베이스의 경우 "근처에 있는 두 스탠바이 중 하나"가 더 나은 균형입니다.
활성화 후 모니터링할 사항
"스탠바이가 연결됨"에서 멈추지 마십시오. 동기 복제는 기술적으로 작동하는 동안 사용자 대면 지연 시간이 악화될 수 있습니다. 롤아웃 후 다음 신호를 관찰하십시오:
SELECT
application_name,
client_addr,
state,
sync_state,
write_lag,
flush_lag,
replay_lag
FROM pg_stat_replication;
프라이머리에서 sync_state = 'sync'는 현재 동기화된 스탠바이를 알려줍니다. write_lag, flush_lag, replay_lag는 시간이 어디에서 소비되는지 설명하는 데 도움이 됩니다. write_lag가 높으면 네트워크 또는 스탠바이 WAL 쓰기 압력을 의심하십시오. flush_lag가 높으면 스토리지를 의심하십시오. replay_lag만 높으면 스탠바이가 WAL을 수신하고 있지만 I/O, CPU, 잠금 또는 스탠바이의 장기 실행 쿼리로 인해 느리게 적용하고 있을 수 있습니다.
또한 슬롯 보존을 모니터링하십시오:
SELECT
slot_name,
active,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_wal
FROM pg_replication_slots;
복제 슬롯은 스탠바이를 보호하지만 디스크를 보호하지는 않습니다. 슬롯이 비활성 상태이고 보유된 WAL이 계속 증가하면 스탠바이를 신속하게 수정하거나 더 이상 필요하지 않은지 확인한 후 슬롯을 삭제하십시오.
실용적인 롤아웃 계획
바쁜 프로덕션 시스템의 경우 동기 복제를 한 줄의 구성 조정이 아닌 단계적 변경으로 취급하십시오.
먼저, 스탠바이를 비동기식으로 구축하고 잠시 실행하십시오. 피크 쓰기 기간 동안 따라잡을 수 있는지 확인하십시오. 비동기식으로 뒤쳐지면 동기식이 될 때 커밋 지연 시간에 영향을 미칩니다.
둘째, application_name을 설정하고 프라이머리가 pg_stat_replication에서 정확히 예상대로 스탠바이를 보는지 확인하십시오. synchronous_standby_names는 호스트 이름이나 슬롯이 아닌 런타임 application_name과 일치하므로 철자 오류가 흔합니다.
셋째, 트래픽이 적은 시간에 동기 복제를 활성화하고 애플리케이션 측에서 커밋 지연 시간을 관찰하십시오. PostgreSQL 메트릭은 괜찮아 보일 수 있지만 트랜잭션이 연결을 조금 더 오래 유지하므로 애플리케이션 연결 풀이 백업될 수 있습니다.
마지막으로, 장애 결정을 문서화하십시오. 동기 스탠바이가 사라지고 프라이머리가 커밋을 기다리고 있다면 누가 synchronous_standby_names를 완화할 수 있습니까? 어떤 조건에서? 노드를 다시 조인하기 전에 이전 프라이머리 또는 이전 스탠바이에 최신 데이터가 포함되어 있는지 어떻게 확인할 수 있습니까? 이는 데이터베이스 설정뿐만 아니라 운영 결정입니다.
동기 HA 모범 사례
- 전용 스탠바이 사용: 동기 복제 목록에는 프라이머리와 물리적으로 가까운(지연 시간이 낮은) 스탠바이만 할당하십시오. 지연 시간이 높으면 커밋 시간에 직접 나타납니다.
- 복제 지연 모니터링: 동기 모드에서도 스탠바이 지연을 모니터링하십시오. 기술적으로 '동기' 상태이지만 WAL 처리에 너무 오래 걸리는 느린 스탠바이는 사용자 경험에 영향을 줄 수 있습니다.
- 가용성 트레이드오프 계획: 운영자가 인시던트 중에 누락된 스탠바이를
synchronous_standby_names에서 일시적으로 제거할 수 있는지 미리 결정하십시오. - 다중 스탠바이 사용: 쓰기 가용성을 높이려면
synchronous_standby_names = 'ANY 1 (standby1, standby2)'를 구성하여 두 스탠바이 중 하나가 커밋을 승인할 수 있도록 하십시오.