29.4. WAL 환경 설정

데이터베이스 성능에 영향을 미치는 WAL 관련 환경 설정 매개 변수가 몇 가지 있다. 이 절에서는 그것의 사용에 대해 설명한다. 서버 환경 설정 매개 변수의 설정에 대한 내용은 19장을 참조 바란다.

Checkpoints는 힙 및 인덱스 데이터 파일이 해당 checkpoint 전에 기록된 모든 정보로 업데이트되도록 보장하는 트랜잭션 시퀀스의 지점이다. checkpoint 시에, 모든 dirty 데이터 페이지는 디스크에 쓰기되고, 특수한 checkpoint 레코드는 로그 파일에 기록된다. (변경 레코드는 이전에 WAL 파일에 기록되었다.) 충돌 발생 시, 충돌 복구 프로시저는 REDO 명령을 시작해야 하는 로그의 지점을 판단하기 위해 최신 checkpoint 레코드를 찾아본다. 해당 지점 이전의 데이터 파일을 변경해도 디스크에는 남아 있다. 따라서, checkpoint 이후에 redo 레코드가 포함되기 이전의 로그 세그먼트는 더 이상 불필요하며, 재활용되거나 제거할 수 있다. (WAL 아카이빙이 완료되면 로그 세그먼트는 재활용 또는 제거되기 전에 아카이브되어야 한다.)

모든 dirty 데이터 페이지를 디스크에 쓰는 checkpoint 요구조건은 상당한 I/O 로드를 야기할 수 있다. 그러므로 checkpoint 시작 시 I/O가 시작되고, 다음 checkpoint가 시작되기 전에 완료되도록 checkpoint 활동이 조절된다. 이렇게 함으로써 checkpoint 시점에 성능 저하가 최소화된다.

서버의 체크포인트 프로세스는 매우 빈번하게 모든 checkpoint를 자동으로 수행한다. checkpoint는 매 checkpoint_timeout 초가 지나면 , 또는 트랜잭션 로그 전체 크기가 max_wal_size 값을 초과 할 때 마다 일어난다. 기본값은 각각 300초(5분), 1GB 이다. 이전 checkpoint 이후로 기록된 WAL이 없으면 checkpoint_timeout을 초과했더라도 새 checkpoint를 건너뛴다. (WAL 아카이빙을 사용 중이고, 데이터 손실 가능성에 대한 제한을 두려고 파일 아카이빙 간격에 대한 하한을 설정하고 싶으면 checkpoint 매개 변수가 아니라 archive_timeout 매개 변수를 조절해야 한다.) SQL 명령 CHECKPOINT를 사용하여 checkpoint를 강제 적용하는 것도 가능하다.

checkpoint_timeout 또는 max_wal_size 값을 줄이면 checkpoint가 좀 더 빈번하게 발생한다. 이렇게 하면 redo에 필요한 작업이 줄어들므로 충돌 후 복구가 빨라진다. 그러나 dirty 데이터 페이지를 빈번하게 기록함으로써 늘어나는 비용 간에 균형을 맞출 필요가 있다. full_page_writes가 설정된 경우(기본값), 다른 요소를 고려해야 한다. 데이터 페이지의 일관성을 유지하려면 각 checkpoint 후 데이터 페이지의 첫 번째 수정은 결과적으로 전체 페이지 내용을 로깅하는 것으로 이어진다. 이런 경우 checkpoint 간격이 짧을수록 WAL 로그로의 출력 볼륨이 증가하여 짧은 간격으로 사용하는 목적이 부분적으로 무력화되고, 경우에 따라서는 디스크 I/O가 늘어나기도 한다.

checkpoint는, 첫째로 모든 현재의 dirty 버퍼를 기록해야 하고, 둘째로 위에서 설명한 대로 추후 WAL 트래픽이 추가 발생하기 때문에 매우 비싸다. 따라서, checkpoint가 너무 빈번하지 않도록 checkpointing 매개 변수를 최대한 크게 설정하는 것이 좋다. checkpointing 매개 변수의 간단한 정상 여부 검사로서 checkpoint_warning 매개 변수를 설정할 수 있다. checkpoint가 checkpoint_warning 초에서 설정된 것보다 간격이 짧은 경우 max_wal_size 값을 늘리라는 서버 로그 권고문이 메시지로 출력된다. 해당 메시지가 가끔씩 출현하면 경고가 발생하지 않지만 빈번하게 나타날 경우 checkpoint 제어 매개 변수를 증가해야 한다. max_wal_size를 충분히 크게 설정한 경우 거대(large) COPY 전송 같은 대량 작업으로 해당 경고가 다수 나타날 수 있다.

폭발적인 페이지 쓰기량으로 인한 I/O 시스템 폭주를 막기 위해 checkpoint 중 dirty 버퍼 쓰기는 일정 기간에 걸쳐 분산된다. 해당 기간은 checkpoint_completion_target에 의해 제어되며, 이것은 checkpoint 간격의 분획으로 지정된다. 체크포인트 작업은 지정된 checkpoint_timeout 초 안에, max_wal_size 크기를 초과하지 않는 범위 작업량을 모두 처리 하도록 I/O 속도를 조정한다. 기본값이 0.5인 PostgreSQL은 다음 checkpoint가 시작되기 전 시간의 약 절반이 지난 후 각 checkpoint를 완료하는 것으로 예상한다. 정상 실행 중에 최대 I/O 처리량에 매우 근접한 시스템에서는 I/O 로드를 checkpoint로부터 줄이고자 checkpoint_completion_target를 늘리려고 할 수 있다. 이것의 단점은, 복구 시에 사용할 수 있도록 WAL 세그먼트를 더 많이 확보해야 하기 때문에 연장된 checkpoint가 복구 시간에 영향을 준다는 것이다. checkpoint_completion_target을 1.0로 설정할 수는 있지만 checkpoint는 dirty 버퍼 쓰기 외에 다른 활동도 일부 포함하므로 그것보다는 낮게 유지하는 것이 좋다(최대 0.9). 1.0로 설정하면 checkpoint가 제시간에 완료되지 않을 가능성이 높으므로, 필요한 WAL 세그먼트 수의 예상치 못한 변동으로 성능 손실이 야기될 수 있다.

On Linux and POSIX platforms checkpoint_flush_after allows to force the OS that pages written by the checkpoint should be flushed to disk after a configurable number of bytes. Otherwise, these pages may be kept in the OS's page cache, inducing a stall when fsync is issued at the end of a checkpoint. This setting will often help to reduce transaction latency, but it also can have an adverse effect on performance; particularly for workloads that are bigger than shared_buffers, but smaller than the OS's page cache.

The number of WAL segment files in pg_wal directory depends on min_wal_size, max_wal_size and the amount of WAL generated in previous checkpoint cycles. When old log segment files are no longer needed, they are removed or recycled (that is, renamed to become future segments in the numbered sequence). If, due to a short-term peak of log output rate, max_wal_size is exceeded, the unneeded segment files will be removed until the system gets back under this limit. Below that limit, the system recycles enough WAL files to cover the estimated need until the next checkpoint, and removes the rest. The estimate is based on a moving average of the number of WAL files used in previous checkpoint cycles. The moving average is increased immediately if the actual usage exceeds the estimate, so it accommodates peak usage rather than average usage to some extent. min_wal_size puts a minimum on the amount of WAL files recycled for future usage; that much WAL is always recycled for future use, even if the system is idle and the WAL usage estimate suggests that little WAL is needed.

Independently of max_wal_size, the most recent wal_keep_size megabytes of WAL files plus one additional WAL file are kept at all times. Also, if WAL archiving is used, old segments cannot be removed or recycled until they are archived. If WAL archiving cannot keep up with the pace that WAL is generated, or if archive_command fails repeatedly, old WAL files will accumulate in pg_wal until the situation is resolved. A slow or failed standby server that uses a replication slot will have the same effect (see 26.2.6절).

아카이브 복구 또는 스탠바이 모드에서 서버는 주기적으로 restartpoints,를 수행하는데, 이것은 정상 실행된 checkpoints와 유사하다. 서버는 모든 상태를 디스크에 강제로 기록하고, pg_control 파일을 업데이트하여 이미 처리된 WAL 데이터를 다시 스캔할 필요가 없음을 표시하여 pg_wal 디렉터리에 있는 예전 로그 세그먼트 파일을 재활용할 수 있게 한다. restartpoints는 checkpoint 레코드에서만 수행될 수 있으므로 restartpoints는 마스터에서의 수행 빈도가 checkpoints보다 적다. 마지막 restartpoint 이후에 최소한 checkpoint_timeout 초를 경과한 경우나, WAL 크기가 max_wal_size 값을 초과하는 경우 checkpoint 레코드에 도달하면 restartpoint가 트리거된다. However, because of limitations on when a restartpoint can be performed, max_wal_size is often exceeded during recovery, by up to one checkpoint cycle's worth of WAL. (max_wal_size is never a hard limit anyway, so you should always leave plenty of headroom to avoid running out of disk space.)

일반적으로 사용되는 내부 WAL 함수는 XLogInsertRecordXLogFlush의 두 가지가 있다. XLogInsertRecord는 공유 메모리에서 새 레코드를 WAL 버퍼에 배치할 때 사용된다. 새 레코드를 위한 공간이 없는 경우, XLogInsertRecord는 몇 개의 채워진 WAL 버퍼를 기록해야 한다(커널 캐시로 이동). 영향을 받는 데이터 페이지에 배타적 잠금이 걸려 있어서 명령이 가능한 빨라야 하는 경우, XLogInsertRecord가 모든 데이터베이스 저수준 변경(예를 들면, 행 삽입)에 사용되므로 이는 바람직하지 않다. 더 안 좋은 것은, WAL 버퍼 쓰기 작업 때문에 새로운 로그 세그먼트가 생성되어 시간이 더 늘어날 수도 있다는 것이다. 일반적으로, WAL 버퍼는 XLogFlush 요청에 의해 쓰기 되어야 하지만, 대부분의 경우 트랜잭션 레코드가 영구적인 저장소에 기록되도록 트랜잭션 커밋 시에 발생한다. 로그 출력이 많은 시스템에서 XLogFlush 요청은 XLogInsertRecord가 쓰기를 금지할 만큼 빈번하지 않다. 해당 시스템에서는 wal_bufferswal_buffers 매개 변수를 변경하여 WAL 버퍼 수를 늘려야 한다. full_page_writes가 설정된 경우 및 시스템이 매우 바쁜 경우, wal_buffers를 큰 값으로 설정하면 각 checkpoint 바로 다음 기간 중에 순조로운 반응 시간을 유도할 수 있다.

commit_delay 매개 변수는 그룹 커밋 리더 프로세스가 XLogFlushXLogFlush 내에서 잠금을 획득한 후에 슬립하는 마이크로초 시간을 정의하며, 그룹 커밋 팔로워는 리더 뒤에서 대기한다. 이러한 지연은 다른 서버 프로세스가 자신의 커밋 레코드를 WAL 버퍼에 추가하는 것을 허용하므로 이들 모두는 리더의 최종 동기화 명령에 의해 쓰기 된다. fsync가 활성화되지 않으면 슬립이 발생하지 않으며, commit_siblings보다 적을 경우 다른 세션이 현재 활성 트랜잭션이 된다. 이렇게 하면 다른 세션이 곧 커밋하지 않을 경우에 슬립을 방지할 수 있다. 일부 플랫폼에서 슬립 요청 시간은 10밀리초이므로 1에서 10000마이크로초 사이의 숫자에서 0이 아닌 값으로 commit_delay를 설정하면 동일한 효과를 갖는다. 일부 플랫폼에서 슬립 명령은 매개 변수에 의해 요청된 것보다 약간 길 수 있다.

commit_delay의 목적은 각 쓰기 명령의 비용이 동시 커밋된 트랜잭션 간에 분할되도록 하는 것이므로(트랜잭션 대기 시간 비용) 설정 전에 비용을 적절하게 선택할 수 있도록 정량화할 필요가 있다. 비용이 클수록 트랜잭션 처리량을 증가시키는 데 commit_delay의 효율이 어느 정도까지 커진다. pg_test_fsync 프로그램을 사용하면 단일 WAL 쓰기 명령을 수행할 때의 평균 시간을 마이크로초 단위로 측정할 수 있다. 단일 8kB 쓰기 명령 후에 쓰기에 소요되는 것으로 프로그램이 리포트한 평균 시간의 절반 값은 commit_delay에 가장 효율적인 설정이므로 이 값은 특정 작업 부하를 최적화할 때 사용되는 시작점으로 권장된다. commit_delay 튜닝은 WAL 로그가 고비용의 대기 회전 디스크(high-latency rotating disk)에 저장된 경우에 특히 유용하며, solid-state drive 또는 배터리 백업 쓰기 캐시가 있는 RAID 배열 같이 동기화 시간이 매우 빠른 저장 매체에서도 장점을 발휘한다. 단, 이것은 대표적인 작업 부하에 대해 테스트해야 한다. 그런 경우 commit_siblings에 더 큰 값을 설정해야 하는데, commit_siblings 값을 작게 하면 대기 시간이 긴 매체에서 종종 유용하다. commit_delay 설정이 너무 크면 총 트랜잭션 처리에 걸리는 시간만큼 트랜잭션 대기 시간이 늘어날 수 있다.

commit_delay가 0으로 설정된 경우(기본값), 발생하는 그룹 커밋 형태로 여전히 가능하지만 각 그룹은 이전 쓰기 명령(있을 경우)이 발생한 시간에 커밋 레코드를 기록해야 하는 지점에 도달하는 세션만으로 환경 설정된다. 높은 클라이언트 카운트에서 통로 효과(gangway effect)가 발생하는 추세이면 commit_delay가 0일 때 그룹 커밋의 효과는 상당히 크며, 따라서 commit_delay의 명시적 설정은 무용지물이 될 가능성이 높다. commit_delay 설정은, (1) 일부 동시 커밋 트랜잭션이 있는 경우 및 (2) 처리량이 커밋 속도에 의해 일정 수준으로 제한되는 경우에만 도움이 된다. 그러나 높은 회전 대기 시간을 사용하는 경우 두 개의 클라이언트만큼의 트랜잭션 처리량 증가 시 이 설정이 효율적일 수 있다(즉, 형제 트랜잭션이 1개 있는 단일 커밋 클라이언트).

wal_sync_method 매개 변수는 PostgreSQL이 디스크로의 WAL 업데이트를 커널에 요청하는 빈도를 결정한다. 다른 옵션은 그렇지 않지만, 디스크 캐시에 강제로 쓰기할 수 있는 fsync_writethrough를 제외하고는 안정성 측면에서 모든 옵션은 동일해야 한다. 그러나 어떤 것이 가장 빠른지는 플랫폼에 따라 다르다. pg_test_fsync 프로그램을 사용하면 서로 다른 옵션의 속도를 테스트해 볼 수 있다. fsync가 해제된 경우에는 이 매개 변수가 무효화된다.

wal_debug 환경 설정 매개 변수(PostgreSQL이 지원을 사용하여 컴파일된 경우)를 활성화하면, 결과적으로 각 XLogInsertRecordXLogFlush WAL 호출이 서버 로그에 로깅된다. 이 옵션은 나중에 좀 더 일반적인 메커니즘으로 교체될 수 있다.