핫 스탠바이
핫 스탠바이 (Hot Standby)
이번 페이지에서는 PostgreSQL의 **핫 스탠바이(hot standby)**를 자세히 살펴볼게요. 한마디로 "스탠바이 서버가 복구(recovery) 중인 동안에도 사용자가 접속해서 읽기 전용 쿼리를 돌릴 수 있게 해주는 기능"이에요. 옆에 앉아서 쓰기 쿼리 제약, 충돌(conflict) 처리, 관리 포인트까지 차근차근 짚어 드릴게요.
핫 스탠바이란 무엇인가 (What hot standby is)
**핫 스탠바이(hot standby)**란, 서버가 아카이브 복구(archive recovery) 또는 스탠바이 모드에 있는 동안에도 서버에 접속해서 읽기 전용(read-only) 쿼리를 실행할 수 있는 능력을 말해요. 이는 복제 목적과, 백업을 원하는 상태로 아주 정밀하게 복원하는 데 모두 유용해요. 또 핫 스탠바이라는 용어는 서버가 복구에서 정상 운영으로 전환되는 동안에도 사용자가 계속 쿼리를 실행하거나 커넥션을 유지할 수 있는 능력도 가리켜요.
핫 스탠바이 모드에서 쿼리를 실행하는 것은 일반 쿼리 운영과 비슷하지만, 아래에서 설명할 몇 가지 사용 및 관리상 차이가 있어요.
사용자 관점 (User's Overview)
스탠바이 서버에서 hot_standby 파라미터를 true로 설정하면, 복구가 시스템을 **일관된 상태(consistent state)**로 만들고 나서야 비로소 커넥션을 받기 시작해요. 그 모든 커넥션은 엄격하게 읽기 전용이에요. 임시 테이블조차도 쓸 수 없죠.
스탠바이의 데이터는 프라이머리 서버에서 도착하는 데 시간이 걸리므로, 프라이머리와 스탠바이 사이에는 측정 가능한 지연이 있어요. 두 곳에서 거의 동시에 똑같은 쿼리를 실행하면 서로 다른 결과가 나올 수 있어요. 스탠바이의 데이터는 프라이머리와 **결과적 일관성(eventually consistent)**을 가진다고 말해요. 트랜잭션의 커밋 레코드가 스탠바이에서 재생(replay)되면, 그 트랜잭션이 만든 변경 사항은 스탠바이에서 새로 찍은 스냅샷에 보이게 돼요. 스냅샷은 현재 트랜잭션 격리 수준에 따라 각 쿼리 시작 시점 또는 각 트랜잭션 시작 시점에 찍힐 수 있어요.
핫 스탠바이 중 허용되는 명령 (Allowed commands)
핫 스탠바이 중 시작된 트랜잭션은 다음 명령들을 사용할 수 있어요.
- 쿼리 접근:
SELECT,COPY TO - 커서 명령:
DECLARE,FETCH,CLOSE - 설정:
SHOW,SET,RESET - 트랜잭션 관리 명령:
BEGIN,END,ABORT,START TRANSACTIONSAVEPOINT,RELEASE,ROLLBACK TO SAVEPOINTEXCEPTION블록 및 기타 내부 서브트랜잭션
LOCK TABLE— 단,ACCESS SHARE,ROW SHARE,ROW EXCLUSIVE모드 중 하나를 명시적으로 쓸 때만요.- 플랜 및 리소스:
PREPARE,EXECUTE,DEALLOCATE,DISCARD - 플러그인 및 확장:
LOAD UNLISTEN
핫 스탠바이 중 금지되는 명령 (Disallowed commands)
핫 스탠바이 중 시작된 트랜잭션은 트랜잭션 ID를 부여받지 못하고, 시스템 WAL(쓰기 전 로그)에 쓸 수도 없어요. 따라서 아래 동작들은 오류 메시지를 만들어 내요.
- 데이터 조작 언어(DML):
INSERT,UPDATE,DELETE,MERGE,COPY FROM,TRUNCATE. 복구 중에는 트리거가 실행되게 하는 동작은 하나도 허용되지 않아요. 이 제한은 임시 테이블에도 적용돼요. 테이블 행을 읽거나 쓰려면 트랜잭션 ID가 필요한데, 현재 핫 스탠바이 환경에서는 그게 불가능하거든요. - 데이터 정의 언어(DDL):
CREATE,DROP,ALTER,COMMENT. 이 제한은 임시 테이블에도 적용돼요. 이 작업들은 시스템 카탈로그 테이블을 갱신해야 하므로요. SELECT ... FOR SHARE | UPDATE— 행 잠금(row lock)은 기본 데이터 파일을 갱신하지 않고는 걸 수 없어서요.- DML 명령을 생성하는
SELECT문의 규칙(rules). ROW EXCLUSIVE MODE보다 높은 모드를 명시적으로 요청하는LOCK.ACCESS EXCLUSIVE MODE를 요청하는 짧은 기본 형태의LOCK.- 명시적으로 비읽기전용 상태로 설정하는 트랜잭션 관리 명령:
BEGIN READ WRITE,START TRANSACTION READ WRITESET TRANSACTION READ WRITE,SET SESSION CHARACTERISTICS AS TRANSACTION READ WRITESET transaction_read_only = off
- 2단계 커밋(two-phase commit) 명령:
PREPARE TRANSACTION,COMMIT PREPARED,ROLLBACK PREPARED— 읽기 전용 트랜잭션조차 prepare 단계(2단계 커밋의 첫 단계)에서 WAL을 써야 하기 때문이에요. - 시퀀스 갱신:
nextval(),setval() LISTEN,NOTIFY
정상 운영에서는 "읽기 전용" 트랜잭션이 LISTEN과 NOTIFY를 사용할 수 있어요. 그래서 핫 스탠바이 세션은 일반 읽기 전용 세션보다 좀 더 빡빡한 제약 아래서 동작해요. 이런 제약 중 일부는 향후 릴리스에서 완화될 수도 있어요.
핫 스탠바이 중에는 transaction_read_only 파라미터가 항상 true이고 바꿀 수 없어요. 하지만 데이터베이스를 수정하려는 시도만 없다면, 핫 스탠바이 중의 커넥션은 다른 데이터베이스 커넥션과 비슷하게 동작해요. 페일오버나 스위치오버가 발생하면 데이터베이스는 정상 처리 모드로 전환돼요. 서버가 모드를 바꾸는 동안에도 세션은 연결된 상태로 유지돼요. 핫 스탠바이가 끝나면 (핫 스탠바이 중에 시작된 세션에서도) 읽기-쓰기 트랜잭션을 시작할 수 있게 돼요.
사용자는 SHOW in_hot_standby를 실행해서 자신의 세션에서 핫 스탠바이가 현재 활성인지 확인할 수 있어요. (14 버전 이전에는 in_hot_standby 파라미터가 없었어요. 구버전에서는 SHOW transaction_read_only가 대체 방법이에요.) 또한 일련의 함수들을 이용해 스탠바이 서버에 대한 정보에 접근할 수 있어요. 이 함수들은 데이터베이스의 현재 상태를 인지하는 프로그램을 작성할 수 있게 해주고, 복구 진행 상황을 모니터링하거나 데이터베이스를 특정 상태로 복원하는 복잡한 프로그램을 짜는 데 쓸 수 있어요.
쿼리 충돌 처리 (Handling Query Conflicts)
프라이머리와 스탠바이 서버는 여러 면에서 느슨하게 연결돼 있어요. 프라이머리에서의 동작은 스탠바이에 영향을 주죠. 그 결과 둘 사이에 부정적인 상호작용, 즉 **충돌(conflict)**이 발생할 가능성이 있어요.
가장 이해하기 쉬운 충돌은 성능이에요. 프라이머리에서 대규모 데이터 로드가 일어나면 스탠바이에서도 비슷한 WAL 레코드 스트림이 생겨서, 스탠바이의 쿼리들이 I/O 같은 시스템 자원을 두고 경쟁할 수 있어요.
핫 스탠바이에서 발생할 수 있는 추가 유형의 충돌도 있어요. 이런 충돌은 **하드 충돌(hard conflict)**인데, 해결하려면 쿼리가 취소되고 어떤 경우에는 세션이 끊겨야 할 수도 있어요. 사용자에게는 이런 충돌을 처리할 여러 방법이 제공돼요. 충돌 사례는 다음과 같아요.
- 프라이머리에서 취해진 Access Exclusive 잠금 (명시적
LOCK명령과 각종 DDL 동작 포함)은 스탠바이 쿼리의 테이블 접근과 충돌해요. - 프라이머리에서 테이블스페이스를 DROP하는 것은, 그 테이블스페이스를 임시 작업 파일에 사용하는 스탠바이 쿼리와 충돌해요.
- 프라이머리에서 데이터베이스를 DROP하는 것은, 스탠바이에서 그 데이터베이스에 연결된 세션과 충돌해요.
- WAL에서 가져온 vacuum 정리 레코드 적용은, 스탠바이 트랜잭션의 스냅샷이 제거될 행들 중 어떤 것이든 여전히 "볼 수 있는" 경우 충돌해요.
- WAL에서 가져온 vacuum 정리 레코드 적용은, 제거할 데이터가 보이는지와 관계없이 대상 페이지에 접근하는 스탠바이 쿼리와 충돌해요.
프라이머리 서버에서는 이런 경우 단순히 **대기(waiting)**할 뿐이고, 사용자가 충돌하는 동작 중 하나를 취소할 수도 있어요. 하지만 스탠바이에서는 선택의 여지가 없어요. WAL에 기록된 동작이 이미 프라이머리에서 일어났으므로, 스탠바이는 그 적용을 실패시킬 수 없죠. 게다가 WAL 적용이 무한정 기다리게 되는 것은 매우 바람직하지 않아요. 스탠바이의 상태가 프라이머리보다 점점 더 뒤처지게 되니까요. 그래서 적용될 WAL 레코드와 충돌하는 스탠바이 쿼리를 강제로 취소하는 메커니즘이 제공돼요.
문제 상황의 예로, 프라이머리 서버의 관리자가 스탠바이 서버에서 현재 쿼리가 실행 중인 테이블에 대해 DROP TABLE을 실행하는 경우를 들어 볼게요. DROP TABLE이 스탠바이에 적용되면 그 스탠바이 쿼리는 분명히 계속될 수 없어요. 이런 상황이 프라이머리에서 발생했다면 DROP TABLE은 다른 쿼리가 끝날 때까지 기다렸을 거예요. 하지만 프라이머리에서 DROP TABLE을 실행할 때, 프라이머리는 스탠바이에서 어떤 쿼리가 실행 중인지 알지 못하므로 그런 스탠바이 쿼리를 기다리지 않아요. WAL 변경 레코드가 스탠바이 쿼리가 아직 실행 중인 동안 도착해서 충돌을 일으켜요. 스탠바이 서버는 WAL 레코드(그 뒤의 모든 것도) 적용을 지연시키거나, 아니면 DROP TABLE이 적용될 수 있도록 충돌하는 쿼리를 취소해야 해요.
충돌하는 쿼리가 짧다면, WAL 적용을 잠시 지연시켜서 그 쿼리가 완료되도록 두는 게 보통 바람직해요. 하지만 WAL 적용의 긴 지연은 보통 바람직하지 않죠. 그래서 취소 메커니즘에는 max_standby_archive_delay와 max_standby_streaming_delay라는 파라미터가 있어서, WAL 적용에 허용되는 최대 지연을 정의해요. 새로 받은 WAL 데이터를 적용하는 데 해당 지연 설정보다 오래 걸리게 되면 충돌 쿼리가 취소돼요. 파라미터가 둘인 이유는, 아카이브에서 WAL을 읽는 경우(베이스 백업으로부터의 초기 복구, 또는 많이 뒤처진 스탠바이를 "따라잡기")와 스트리밍 복제로 WAL을 읽는 경우에 각각 다른 지연 값을 지정할 수 있게 하려는 거예요.
주로 고가용성(HA)을 위해 존재하는 스탠바이 서버에서는 지연 파라미터를 상대적으로 짧게 설정하는 게 좋아요. 그래야 스탠바이 쿼리로 인한 지연 때문에 서버가 프라이머리보다 많이 뒤처지지 않으니까요. 하지만 스탠바이 서버가 장시간 실행 쿼리를 위한 것이라면, 높은 — 심지어 무한대의 — 지연 값이 더 바람직할 수 있어요. 단, 장시간 실행 쿼리가 WAL 레코드 적용을 지연시키면, 스탠바이 서버의 다른 세션들이 프라이머리의 최근 변경 사항을 보지 못하게 될 수 있다는 점을 명심하세요.
max_standby_archive_delay나 max_standby_streaming_delay로 지정된 지연이 초과되면, 충돌하는 쿼리가 취소돼요. 이는 보통 그냥 취소 오류이지만, DROP DATABASE를 재생하는 경우에는 충돌하는 세션 전체가 종료돼요. 또한 유휴 트랜잭션이 가진 잠금 때문에 충돌이 생긴 경우에도 충돌 세션이 종료돼요. (이 동작은 미래에 바뀔 수 있어요.)
취소된 쿼리는 (물론 새 트랜잭션을 시작한 뒤) 즉시 재시도할 수 있어요. 쿼리 취소는 재생되는 WAL 레코드의 성격에 달려 있으므로, 취소됐던 쿼리를 다시 실행하면 성공할 가능성이 높아요.
지연 파라미터는 스탠바이 서버가 WAL 데이터를 받은 시점부터의 경과 시간과 비교된다는 점을 명심하세요. 따라서 스탠바이에서 어느 하나의 쿼리에 허용되는 유예 기간은 결코 지연 파라미터보다 길지 않고, 만약 스탠바이가 이전 쿼리들이 끝나기를 기다리느라 또는 대량 갱신 부하를 따라가지 못해서 이미 뒤처져 있다면 훨씬 짧을 수 있어요.
스탠바이 쿼리와 WAL 재생 사이의 충돌의 가장 흔한 원인은 **"조기 정리(early cleanup)"**예요. 보통 PostgreSQL은 MVCC 규칙에 따라 데이터 가시성을 보장하기 위해, 그 행들을 봐야 할 트랜잭션이 없을 때 오래된 행 버전의 정리를 허용해요. 하지만 이 규칙은 프라이머리에서 실행되는 트랜잭션에만 적용돼요. 그래서 프라이머리에서의 정리가, 스탠바이의 트랜잭션에는 여전히 보이는 행 버전을 제거할 수 있어요.
행 버전 정리만이 스탠바이 쿼리와의 충돌의 잠재적 원인은 아니에요. 모든 인덱스 전용 스캔(스탠바이에서 실행되는 것 포함)은 visibility map과 "일치하는" MVCC 스냅샷을 사용해야 해요. 따라서 VACUUM이 시정리할 행이 없는 페이지조차 visibility map에서 all-visible로 설정하면서, 그 페이지에 스탠바이 일부 쿼리에 보이지 않는 행이 하나 이상 포함돼 있으면 충돌이 요구돼요. 그래서 정리가 필요할 만큼 수정되거나 삭제된 행이 전혀 없는 테이블에 VACUUM을 실행하는 것조차 충돌을 일으킬 수 있어요.
프라이머리 서버에서 정기적으로 그리고 대량으로 갱신되는 테이블은, 스탠바이에서 오래 실행되는 쿼리의 취소를 빠르게 유발한다는 점을 분명히 알아두세요. 그런 경우 max_standby_archive_delay나 max_standby_streaming_delay에 유한한 값을 설정하는 것은 statement_timeout을 설정하는 것과 비슷하다고 볼 수 있어요.
완화 방법 (Remedies)
스탠바이 쿼리 취소 횟수가 받아들이기 어렵다면 완화 방법이 있어요.
첫 번째 옵션은 hot_standby_feedback 파라미터를 설정하는 거예요. 이 파라미터는 VACUUM이 최근에 죽은 행을 제거하지 못하게 해서, 정리 충돌이 발생하지 않게 해줘요. 이렇게 하면 프라이머리에서 죽은 행 정리가 지연돼서 바람직하지 않은 **테이블 블로트(bloat)**가 생길 수 있다는 점을 알아두세요. 다만 정리 상황은 스탠바이 쿼리가 프라이머리 서버에서 직접 실행되는 경우보다 더 나빠지진 않고, 여전히 실행을 스탠바이로 떠넘기는 이점은 누리고 있어요.
스탠바이 서버가 자주 연결/연결 해제한다면, hot_standby_feedback 피드백이 제공되지 않는 기간을 처리하도록 조정할 수 있어요. 예를 들어 max_standby_archive_delay를 늘려서, 연결이 끊긴 기간 동안 WAL 아카이브 파일의 충돌로 쿼리가 급격히 취소되지 않게 할 수 있어요. 또 재연결 후 새로 도착한 스트리밍 WAL 항목에 의한 급격한 취소를 피하려고 max_standby_streaming_delay를 늘리는 것도 고려해 보세요.
쿼리 취소 횟수와 그 이유는 스탠바이 서버에서 pg_stat_database_conflicts 시스템 뷰로 볼 수 있어요. pg_stat_database 시스템 뷰에도 요약 정보가 들어 있어요.
또 사용자는 WAL 재생이 deadlock_timeout보다 오래 충돌 때문에 기다릴 때 로그 메시지를 생성할지 제어할 수 있어요. 이는 log_recovery_conflict_waits 파라미터로 제어해요.
관리자 관점 (Administrator's Overview)
postgresql.conf에서 hot_standby가 on(기본값)이고, standby.signal 파일이 존재하면 서버는 핫 스탠바이 모드로 실행돼요. 다만 핫 스탠바이 커넥션이 허용되기까지 시간이 걸릴 수 있어요. 서버는 쿼리가 실행될 수 있는 일관된 상태를 제공할 수 있을 만큼 충분히 복구될 때까지 커넥션을 받지 않으니까요. 이 기간 동안 연결을 시도하는 클라이언트는 오류 메시지와 함께 거부돼요. 서버가 올라왔는지 확인하려면, 애플리케이션에서 연결을 반복해서 시도하거나 서버 로그에서 이런 메시지를 찾아보면 돼요.
LOG: entering standby mode
... then some time later ...
LOG: consistent recovery state reached
LOG: database system is ready to accept read-only connections
일관성 정보는 프라이머리에서 체크포인트마다 한 번씩 기록돼요. 프라이머리에서 wal_level이 replica나 logical로 설정되지 않은 동안 작성된 WAL을 읽을 때는 핫 스탠바이를 켤 수 없어요. 일관된 상태에 도달한 뒤에도, 아래 두 조건이 모두 충족되면 복구 스냅샷이 핫 스탠바이에 아직 준비되지 않아서 읽기 전용 커넥션 수락이 지연될 수 있어요. 핫 스탠바이를 켜려면, 64개가 넘는 서브트랜잭션을 가진 오래 지속되는 쓰기 트랜잭션을 프라이머리에서 닫아야 해요.
- 쓰기 트랜잭션이 64개가 넘는 서브트랜잭션을 가짐
- 매우 오래 지속되는 쓰기 트랜잭션
파일 기반 로그 시핑("웜 스탠바이")을 실행 중이라면, 다음 WAL 파일이 도착할 때까지 기다려야 할 수 있는데, 이는 프라이머리의 archive_timeout 설정만큼 길어질 수 있어요.
공유 메모리 설정 (Shared memory settings)
일부 파라미터의 설정값은 트랜잭션 ID, 잠금, prepared 트랜잭션을 추적하기 위한 공유 메모리 크기를 결정해요. 이런 공유 메모리 구조는 스탠바이가 복구 중 공유 메모리가 부족해지지 않도록 프라이머리의 것보다 작으면 안 돼요. 예를 들어 프라이머리가 prepared 트랜잭션을 사용했는데 스탠바이가 prepared 트랜잭션 추적용 공유 메모리를 전혀 할당하지 않았다면, 스탠바이 구성을 바꾸기 전까지 복구를 계속할 수 없어요. 영향을 받는 파라미터는 다음과 같아요.
max_connectionsmax_prepared_transactionsmax_locks_per_transactionmax_wal_sendersmax_worker_processes
문제가 생기지 않게 하는 가장 쉬운 방법은 스탠바이에서 이 파라미터들을 프라이머리와 같거나 더 큰 값으로 설정하는 거예요. 그래서 이 값들을 늘리려면, 먼저 모든 스탠바이 서버에 적용한 다음 프라이머리 서버에 적용해야 해요. 반대로 줄이려면, 먼저 프라이머리 서버에 적용한 다음 모든 스탠바이 서버에 적용해야 해요. 스탠바이가 프로모션되면 그 스탠바이가 뒤따르는 스탠바이들에게 필요한 파라미터 설정의 새 기준이 된다는 점도 기억하세요. 그래서 스위치오버나 페일오버 중에 문제가 되지 않도록, 모든 스탠바이 서버에서 이 설정들을 동일하게 유지하는 것이 권장돼요.
WAL은 프라이머리에서 이 파라미터들의 변경을 추적해요. 핫 스탠바이가 프라이머리의 현재 값이 자기 값보다 높음을 나타내는 WAL을 처리하면, 경고를 로그로 남기고 복구를 일시 중지해요. 예를 들어:
WARNING: hot standby is not possible because of insufficient parameter settings
DETAIL: max_connections = 80 is a lower setting than on the primary server, where its value was 100.
LOG: recovery has paused
DETAIL: If recovery is unpaused, the server will shut down.
HINT: You can then restart the server after making the necessary configuration changes.
이 시점에서 스탠바이 설정을 갱신하고 인스턴스를 재시작해야 복구가 계속돼요. 스탠바이가 핫 스탠바이가 아니라면, 호환되지 않는 파라미터 변경을 만나면 일시 중지하지 않고 즉시 종료돼요. 그 상태로 켜둘 가치가 없으니까요.
지연 파라미터 선택 (Choosing delay parameters)
관리자가 max_standby_archive_delay와 max_standby_streaming_delay에 적절한 설정을 고르는 것이 중요해요. 최선의 선택은 사업 우선순위에 따라 달라져요. 예를 들어 서버가 주로 고가용성(HA) 서버 역할을 한다면 낮은 지연 설정을 원할 거예요. 심지어 0도 좋은데, 다만 그건 매우 공격적인 설정이에요. 스탠바이 서버가 의사 결정 지원 쿼리를 위한 추가 서버 역할을 한다면 최대 지연 값을 몇 시간으로 — 심지어 -1(쿼리가 끝날 때까지 영원히 기다림)로 — 설정해도 괜찮을 수 있어요.
기타 동작 (Other behaviors)
프라이머리에서 기록되는 트랜잭션 상태 "힌트 비트(hint bits)"는 WAL에 기록되지 않으므로, 스탠바이의 데이터는 힌트를 스탠바이에서 다시 쓸 가능성이 높아요. 그래서 모든 사용자가 읽기 전용이라도 스탠바이 서버는 여전히 디스크 쓰기를 수행해요. 데이터 값 자체에는 변경이 없지만요. 사용자는 여전히 큰 정렬 임시 파일을 쓰고 relcache 정보 파일을 다시 생성하므로, 핫 스탠바이 모드에서 데이터베이스의 어떤 부분도 진짜 "읽기 전용"은 아니에요. 또 dblink 모듈을 사용한 원격 데이터베이스에 대한 쓰기와, PL 함수를 사용한 데이터베이스 밖의 다른 작업도 로컬 트랜잭션이 읽기 전용이라도 여전히 가능하다는 점에 주목하세요.
복구 모드 중에는 다음 유형의 관리 명령이 수락되지 않아요.
- 데이터 정의 언어(DDL): 예:
CREATE INDEX - 권한 및 소유권:
GRANT,REVOKE,REASSIGN - 유지보수 명령:
ANALYZE,VACUUM,CLUSTER,REINDEX
이 명령들 중 일부는 프라이머리에서 "읽기 전용" 모드 트랜잭션 중에는 실제로 허용된다는 점도 다시 짚어 둘게요.
결과적으로 스탠바이에만 존재하는 추가 인덱스나 스탠바이에만 존재하는 통계를 만들 수 없어요. 그런 관리 명령이 필요하면 프라이머리에서 실행해야 하고, 결국 그 변경 사항이 스탠바이로 전파돼요.
pg_cancel_backend()와 pg_terminate_backend()는 사용자 백엔드에는 동작하지만, 복구를 수행하는 startup 프로세스에는 동작하지 않아요. pg_stat_activity는 복구 중인 트랜잭션을 활성으로 표시하지 않아요. 그 결과 복구 중에는 pg_prepared_xacts가 항상 비어 있어요. 의심스러운(in-doubt) prepared 트랜잭션을 해결하려면 프라이머리에서 pg_prepared_xacts를 보고 그쪽에서 해결 명령을 내리거나, 복구가 끝난 뒤에 해결하면 돼요.
pg_locks는 평소처럼 백엔드가 가진 잠금을 보여줘요. pg_locks는 또 startup 프로세스가 관리하는, 복구로 재생되는 트랜잭션들이 가진 모든 AccessExclusiveLocks를 소유하는 가상 트랜잭션도 보여줘요. 단, startup 프로세스는 데이터베이스 변경을 위해 잠금을 획득하지 않으므로, Startup 프로세스의 pg_locks에는 AccessExclusiveLocks 외의 잠금은 나타나지 않아요. 그냥 존재하는 것으로 간주돼요.
Nagios 플러그인 check_pgsql은 동작할 거예요. 확인하는 간단한 정보가 존재하니까요. check_postgres 모니터링 스크립트도 동작하지만, 일부 보고 값이 다르거나 혼란스러운 결과를 줄 수 있어요. 예를 들어 마지막 vacuum 시간은 유지보수되지 않아요 — 스탠바이에서 vacuum이 발생하지 않으니까요. 프라이머리에서 실행되는 vacuum은 여전히 스탠바이에 변경 사항을 보내요.
WAL 파일 제어 명령(pg_backup_start, pg_switch_wal 등)은 복구 중에는 동작하지 않아요. 동적으로 로드 가능한 모듈(pg_stat_statements 포함)은 동작해요.
**어드바이저리 잠금(advisory lock)**은 교착 상태 감지를 포함해 복구 중에 정상적으로 동작해요. 어드바이저리 잠금은 절대 WAL에 로그되지 않으므로, 프라이머리나 스탠바이의 어드바이저리 잠금이 WAL 재생과 충돌하는 것은 불가능해요. 또 프라이머리에서 어드바이저리 잠금을 얻어서 스탠바이에서 비슷한 어드바이저리 잠금을 시작하게 하는 것도 불가능해요. 어드바이저리 잠금은 그것을 얻은 서버에만 관련돼요.
Slony, Londiste, Bucardo 같은 트리거 기반 복제 시스템은 스탠바이에서는 전혀 실행되지 않아요. 다만 변경 사항이 스탠바이 서버에 적용되도록 보내지지 않는 한 프라이머리 서버에서는 잘 동작해요. WAL 재생은 트리거 기반이 아니므로, 추가 데이터베이스 쓰기가 필요하거나 트리거 사용에 의존하는 시스템에는 스탠바이에서 릴레이할 수 없어요.
새 OID는 할당될 수 없어요. 다만 데이터베이스에 새 상태를 쓰는 데 의존하지 않는 한 일부 UUID 생성기는 여전히 동작할 수 있어요.
현재 읽기 전용 트랜잭션 중에는 임시 테이블 생성이 허용되지 않아서, 어떤 경우에는 기존 스크립트가 올바르게 실행되지 않을 수 있어요. 이 제한은 향후 릴리스에서 완화될 수 있어요. 이는 SQL 표준 준수 문제이자 기술적인 문제예요.
DROP TABLESPACE는 테이블스페이스가 비어 있을 때만 성공할 수 있어요. 일부 스탠바이 사용자가 자신의 temp_tablespaces 파라미터로 그 테이블스페이스를 적극적으로 사용 중일 수 있어요. 테이블스페이스에 임시 파일이 있으면, 임시 파일이 제거되도록 모든 활성 쿼리가 취소돼서 테이블스페이스를 제거하고 WAL 재생을 계속할 수 있어요.
프라이머리에서 DROP DATABASE나 ALTER DATABASE ... SET TABLESPACE를 실행하면, 스탠바이에서 그 데이터베이스에 연결된 모든 사용자를 강제로 연결 해제시키는 WAL 항목이 생성돼요. 이 동작은 max_standby_streaming_delay 설정과 무관하게 즉시 발생해요. ALTER DATABASE ... RENAME은 사용자를 연결 해제시키지 않는다는 점에 주목하세요. 대부분의 경우 눈에 띄지 않지만, 프로그램이 어떤 식으로든 데이터베이스 이름에 의존한다면 혼란을 일으킬 수 있어요.
정상(비복구) 모드에서 로그인 능력이 있는 역할에 대해 DROP USER나 DROP ROLE을 실행할 때 그 사용자가 여전히 연결되어 있으면, 연결된 사용자에게 아무 일도 일어나지 않아요. 그 사용자는 연결된 채로 남아요. 다만 재연결할 수는 없어요. 이 동작은 복구에서도 적용돼요. 그래서 프라이머리에서의 DROP USER는 스탠바이에서 그 사용자를 연결 해제시키지 않아요.
누적 통계(cumulative statistics) 시스템은 복구 중 활성 상태예요. 모든 스캔, 읽기, 블록, 인덱스 사용 등이 스탠바이에서 정상적으로 기록돼요. 다만 WAL 재생은 관계 및 데이터베이스별 카운터를 증가시키지 않아요. 즉 재생은 pg_stat_all_tables의 컬럼(예: n_tup_ins)을 증가시키지 않으며, startup 프로세스가 수행하는 읽기나 쓰기가 pg_statio_ 뷰에 추적되지도 않고, 관련 pg_stat_database 컬럼도 증가하지 않아요.
Autovacuum은 복구 중 활성화되지 않아요. 복구가 끝나면 정상적으로 시작돼요. checkout 프로세스와 background writer 프로세스는 복구 중 활성 상태예요. checkpointer 프로세스는 restartpoint(프라이머리의 체크포인트와 유사)를 수행하고, background writer 프로세스는 일반적인 블록 정리 활동을 수행해요. 여기에는 스탠바이 서버에 저장된 힌트 비트 정보의 갱신도 포함될 수 있어요. CHECKPOINT 명령은 복구 중 수락되지만, 새 체크포인트 대신 restartpoint를 수행해요.
핫 스탠바이 파라미터 참조 (Hot Standby Parameter Reference)
위에서 Section 26.4.2와 Section 26.4.3에서 여러 파라미터를 언급했어요.
프라이머리에서는 wal_level 파라미터를 쓸 수 있어요. max_standby_archive_delay와 max_standby_streaming_delay는 프라이머리에서 설정해도 효과가 없어요.
스탠바이에서는 hot_standby, max_standby_archive_delay, max_standby_streaming_delay 파라미터를 쓸 수 있어요.
주의사항 (Caveats)
핫 스탠바이에는 몇 가지 제한이 있어요. 이들은 향후 릴리스에서 고쳐질 수 있고 아마 고쳐질 거예요.
- 스냅샷을 찍기 전에 실행 중인 트랜잭션에 대한 완전한 지식이 필요해요. 많은 수의 서브트랜잭션(현재 64개 초과)을 사용하는 트랜잭션은, 가장 오래 실행되는 쓰기 트랜잭션의 완료까지 읽기 전용 커넥션의 시작을 지연시켜요. 이런 상황이 발생하면 설명 메시지가 서버 로그로 보내져요.
- 스탠바이 쿼리의 유효한 시작 지점은 프라이머리의 각 체크포인트에서 생성돼요. 프라이머리가 종료(shutdown) 상태인 동안 스탠바이가 종료되면, 프라이머리가 시작되어 WAL 로그에 추가 시작 지점을 생성할 때까지 핫 스탠바이에 다시 들어갈 수 없을 수도 있어요. 이런 상황은 발생할 수 있는 가장 흔한 상황에서는 문제가 아니에요. 일반적으로 프라이머리가 종료되고 더 이상 사용할 수 없으면, 이는 스탠바이를 새 프라이머리로 전환해야 하는 심각한 실패 때문일 가능성이 높아요. 그리고 프라이머리를 의도적으로 내려놓는 상황에서도 스탠바이가 새 프라이머리가 되도록 원활히 조정하는 것이 표준 절차예요.
- 복구 끝에서 prepared 트랜잭션이 가진
AccessExclusiveLocks는 평소 잠금 테이블 항목 수의 두 배가 필요해요. 보통AccessExclusiveLocks를 취하는 많은 수의 동시 prepared 트랜잭션을 실행할 계획이거나, 많은AccessExclusiveLocks를 취하는 큰 트랜잭션 하나를 가질 계획이라면,max_locks_per_transaction을 더 큰 값으로 — 어쩌면 프라이머리 서버 값의 두 배까지 — 선택하는 것을 권장해요.max_prepared_transactions설정이 0이라면 전혀 고려할 필요가 없어요. - Serializable 트랜잭션 격리 수준은 핫 스탠바이에서 아직 사용할 수 없어요. 핫 스탠바이 모드에서 트랜잭션을 serializable 격리 수준으로 설정하려고 하면 오류가 발생해요.