24.1. 정기적인 Vacuum 작업

24.1.1. Vacuum 기초
24.1.2. 디스크 여유 공간 확보
24.1.3. 실행계획 통계 정보 갱신
24.1.4. 실자료 지도 갱신
24.1.5. 트랜잭션 ID 겹침 오류 방지
24.1.6. Autovacuum 데몬

PostgreSQL 데이터베이스에서는 vacuum (배큠이라고 읽는다) 이라는 주기적인 관리 작업이 필요하다. (영어권에서는 vacuuming 단어는 '청소기 돌리기'라는 뜻으로 이미 일상 용어로 사용되고 있고, 이것을 데이터베이스 용어로 사용하였다. 이 글에서는 그냥 vacuum을 그대로 사용한다. - 옮긴이) 이 작업은 대부분의 서버 환경에서는 autovacuum 데몬이 담당해서 자동으로 처리 되기 때문에, 특별히 신경 쓸 필요는 없다. 이 부분은 24.1.6절에서 자세히 소개한다. autovacuum 관련 환경 설정값을 바꾸어서 그 데몬의 동작 상태를 조절 할 수 있다. 또한 몇몇 데이터베이스 관리자는 VACUUM 명령을 직접 실행하는 것이 데이터베이스를 효율적으로 사용할 수 있다고 판단해서, cron 이나, 작업 스케줄러 같은 프로그램을 이용해서, 이런 주기적인 정리 작업을 한다. 이런 작업을 잘 하기 위해서는 여기서 설명하고 있는 내용을 잘 이해하고 있어야한다. autovacuum 기능을 이용하는 일반적인 환경에서 특별히 문제가 발생하지 않는다면, 게다가 다른 바쁜 일들이 많은 관리자라면 이 부분은 대충 읽어도 좋다.

24.1.1. Vacuum 기초

PostgreSQL에서 VACUUM 명령은 다음과 같은 여러 가지 이유로 정기적으로 각 테이블 단위로 실행되어야 한다:

  1. 변경 또는 삭제된 자료들이 차지 하고 있는 디스크 공간을 다시 사용하기 위한 디스크 공간 확보 작업이 필요하다.
  2. PostgreSQL 쿼리 실행 계획기가 사용할 자료 통계 정보를 갱신할 필요가 있다.
  3. index-전용 조회 속도를 높이기 위해, 실자료 지도를 갱신할 필요가 있다.
  4. 트랜잭션 ID 겹침이나, 다중 트랜잭션 ID 겹침 상황으로 오래된 자료가 손실 될 가능성을 방지해야할 필요가 있다.

이런 이유로 VACUUM 작업은 그 작업 이유에 맞게 다양한 주기로, 다양한 대상으로 진행된다. 이 부분에 대한 자세한 설명은 이 글의 하위 항목에서 각각 설명한다.

VACUUM 작업은 두 가지 종류가 있다: 표준 VACUUMVACUUM FULL이다. VACUUM FULL 작업은 물리적인 디스크 여유 공간을 확보할 수 있으나 그 작업 속도가 매우 느리다. 하지만 표준 VACUUM 작업은 운영 환경에서도 사용할 수 있도록 여러 다른 작업들(SELECT, INSERT, UPDATE, DELETE 이런 명령어로 수행되는 작업들)이 실행 되고 있어도 동시에 사용할 수 있다. (하지만, ALTER TABLE 명령과 같은 명령은 VACUUM 작업이 실행되고 있는 상황에서는 사용할 수 없다.) VACUUM FULL 명령은 해당 테이블에 대한 배타적 잠금(ACCESS EXCLUSIVE)을 지정하기 때문에, 어떤 작업도 할 수 없게 된다. 이렇기 때문에, 일반적 상황에서는 관리자는 VACUUM FULL 작업을 되도록이면 피하고, 표준 VACUUM 작업을 하겠끔 신경 써야한다.

VACUUM 작업은 추가적으로 디스크 입출력 부하를 만든다. 이 때문에 동시에 작업하고 있는 다른 세션의 성능을 떨어뜨린다. 이 부분은 19.4.4절 에서 소개하고 있는 VACUUM 작업의 비용 조절 관련 환경 설정 변경으로 어느 정도는 조절이 가능하다.

24.1.2. 디스크 여유 공간 확보

PostgreSQL에서는 UPDATEDELETE 작업 대상이 된 해당 자료의 옛 버전을 작업 완료 후 바로 버리지 않는다. 이 작업은 다중 버전 동시성 제어(multiversion concurrency control, MVCC - 13장 참조) 기법을 구현하는데 이점이 있기 때문이다. 삭제된 자료를 다른 트랜잭션에서 사용하고 있다면, 그 자료가 삭제되면 안되기 때문이다. 하지만, 다른 트랜잭션이 더 이상이 그 옛 버전 자료에 대한 접근이 필요 없다면, 옛 버전 자료는 쓸모 없는 자료가 된다. 이 상태로 계속 운영 된다면, 디스크에는 쓸모 없는 자료들이 넘처나게 될 것이다. 이런 더 이상 사용할 수 없는, 사용해서는 안될 자료들을 정리해서 그 자료가 있었던 공간을 빈 공간으로 바꾸는 작업을 VACUUM 명령이 담당한다.

VACUUM 기본 작업은 테이블과 인덱스에서 삭제된 자료 (old version row, dead row 라고 한다) 를 정리하고, 그 자리를 다른 자료가 저장 될 수 있도록 빈공간으로 표시하는 것이다. 하지만, 이 작업은 운영체제 입장에서의 디스크 여유 공간을 확보하는 것을 의미하지는 않는다. 물론 한 테이블의 자료가 모두 지워졌고, 하나 또는 소수의 페이지만 없애면 되는데, 이 작업을 위해 테이블 전체의 배타적 잠금도 쉽게 할 수 있는 상황과 같이 특별한 경우는 해당 페이지를 삭제 해서 운영체제 입장의 디스크 여유 공간을 확보할 수도 있다. 이와 반대로, VACUUM FULL 작업은 해당 테이블의 사용할 수 있는 자료들만을 따로 모아 아에 새 파일에 저장하는 방식을 이용하기 때문에 운영체제 입장에서 디스크 여유 공간을 확보할 수 있다. 작업 결과로 해당 테이블에 대해서 최적의 물리적 크기로 테이블이 만들어진다. 하지만 그 작업은 일반 VACUUM 작업에 비해 시간이 꽤 걸린다. 또한 이 작업이 완료되기 전까지 이 작업을 할 수 있는 여유 공간이 있어야 작업을 할 수 있다.

일반적인 vacuum 전략은 주기적인 표준 VACUUM 작업을 해서, 꾸준히 빈 공간을 확보해서 디스크가 어느 정도 커지지만, 더 이상 크지지 않게 해서, 최대한 VACUUM FULL 작업을 해야하는 상황을 방지하는 것이다. autovacuum 데몬이 이런 전략으로 작업을 한다. 즉, autovacuum 기능을 사용한다면, VACUUM FULL 작업을 하지 않는 것을 기본 정책으로 하면 된다. 이런 전략은 각 테이블이 최소의 디스크 공간을 사용한다는 것을 의미하는 것이 아니라, 최적의 디스크 공간을 사용함을 의미한다. 각 테이블은 실재 자료가 저장되어 있는 공간과 함께 vacuum 작업으로 처리된 빈공간을 함께 사용함을 의미한다. 반면, 어떤 테이블은 더이상 변경, 삭제 작업이 없어, 최소의 디스크 공간만 사용하면 된다고 판단되면, VACUUM FULL 명령을 이용할 수도 있을 것이다. 이렇게 해서, 쓸모 없는 공간을 운영체제 쪽으로 반환할 수도 있다. 종합하면, 대용량 테이블을 관리하는 입장에서 보면, 비정기적인 VACUUM FULL 작업보다, 정기적인 표준 VACUUM 작업이 운영상 더 낫다.

어떤 관리자는 vacuum 작업을 사용량이 적은 밤 시간에 주기적으로 작업 하도록 직접 관리하려고 한다. 이렇게 특정 시간에 vacuum 작업을 할 때는 갑자기 자료 변경이 많은 작업이 생겨, 디스크 공간을 많이 쓰게 되는 경우도 함께 고려되어야 한다. 최악의 경우는 디스크 공간이 모자라 VACUUM FULL 작업을 선택해야 할 경우도 생기기 때문이다. autovacuum 데몬을 이용하면, 이런 예상치 못한 상황에 대해서도 자동으로 vacuum 작업이 진행되어 위와 같은 문제들을 피해갈 수 있다. 정확한 데이터베이스 사용량을 파악하지 않고 그냥 autovacuum 기능을 끄는 것은 현명하지 못한 선택이다. 한 타협점은 이 데몬의 실행 환경 설정값을 변경해서 예상치 못한 대량 변경 작업에 대해서만 vacuum 작업이 자동으로 실행 되겠끔 하는 것이다. 그래서, 주기적인 관리자 정의 VACUUM 작업을 유지하면서, 예외 상황에 대해서 자동으로 대처하도록 하는 것도 한 방법이 될 것이다.

autovacuum 기능을 사용하지 않는다면, 주의해야할 점은 해당 데이터베이스 서버에서 사용하고 있는 모든 데이터베이스에 대해서 VACUUM 작업을 해야한다는 것이다. 일반적으로 하루에 한 번 밤시간에 지정하는 것이 일반적이며, 자료가 빈번하게 변경되는 테이블에 대해서는 더 자주 vacuum 작업 하도록 설정한다. (아주 빈번한 테이블에 대해서 몇 분에 한 번씩 작업 하도록 설정해야할 필요도 있을 것이다.) 각 데이터베이스별 vacuum 작업을 할 때 vacuumdb 응용 프로그램을 이용하면 도움이 될 것이다.

작은 정보

표준 VACUUM 작업은 해당 테이블 전체를 대상으로 하는 변경 작업이나, 삭제 작업과 같은 대량의 작업에 대해서는 만족할 만한 결과를 제공하지는 않는다. 이런 경우, 디스크 여유 공간을 확보해야 할 필요성이 있다면, VACUUM FULL 명령이나, CLUSTER 또는 ALTER TABLE 명령(CLUSTER ON 옵션)을 이용해서, 테이블을 아에 새롭게 만드는 방법을 선택할 수 있다. 이들 명령을 사용할 때는 반드시 주의해야할 사항은 이들 명령은 테이블 대상으로 배타적 잠금(ACCESS EXCLUSIVE)을 한다는 점이다. 즉, 이 작업이 완료되기 전까지 그 테이블을 대상으로 하는 다른 모든 작업들을 다른 세션에서는 할 수 없게 됨을 염두해 두어야 하며, 이들 작업은 작업 도중 원본과 다른 새로운 복사본 자료를 만들기 때문에 그만큼의 디스크 공간이 필요하다는 것도 기억하고 있어야 한다.

작은 정보

테이블의 모든 자료를 아에 지워버리고자 한다면, DELETE 명령보다는 TRUNCATE 명령을 사용하는 것이 낫다. 이 명령은 작업 뒤에, VACUUM 이나 VACUUM FULL 명령을 사용할 필요가 없다. 단, 이 명령을 사용하게 되면 MVCC 대상에서 제외되기 때문에, 다중 세션 다중 트랜잭션 환경에서는 위험하다.

24.1.3. 실행계획 통계 정보 갱신

PostgreSQL 쿼리 실행 계획기는 쿼리의 좋은 실행 계획을 짜기 위해서 각 테이블에 저장된 자료를 바탕으로 수집된 통계 정보를 이용한다. 이 통계 정보는 ANALYZE 명령을 이용해서 만든다. 또한 VACUUM 명령을 수행하면서 옵션으로 이 작업을 할 수 있다. 이 통계 정보 갱신 작업이 제대로 되지 않으면 의도 되지 않은 쿼리 실행 계획이 짜여질 것이고, 이것으로 전체적으로 데이터베이스 성능을 떨어뜨리는 결과를 초래하기 때문에, 바른 통계 정보 갱신 작업을 주기적으로 하는 것은 중요하다.

autovacuum 기능을 이용한다면, 통계 정보를 갱신해야할 필요성이 있는 테이블들에 대해서 주기적으로 ANALYZE 명령을 자동으로 수행한다. 반면, 자료 변경 작업이 어떤 칼럼에 일어나고, 그 변경 작업 때문에, 통계 정보를 갱신해서, 실행 계획이 잘 짜여지는 것과 관련 없다고 명확하게 판단되는 경우라면, 관리자는 이 통계 정보 갱신 작업을 직접 수행하거나, 아에 안할 수도 있다. autovacuum 데몬은 이런 세세한 경우까지는 고려하지 않고, 테이블의 자료가 새로 추가 되었거나, 변경, 삭제 된 경우라면, 무조건 통계 정보 갱신 작업의 고려 대상이 된다고 판단한다.

통계 정보 갱신 작업도 디스크 여유 공간 확보를 위한 vacuum 작업과 마찬가지로 테이블의 자료 변화량이 많은 경우는 보다 빈번하게, 그 반대인 경우는 좀 더 드물게 진행됨이 좋다. 물론, 테이블의 자료가 빈번하게 변경된다고 하더라도, 그 변경 내용이 수집할 통계 정보와 관련 없는 것이라면, 당연히 통계 정보 갱신 작업이 필요 없다. 통계 정보를 갱신 할 빈도를 추측하는 가장 간단한 방법은 그 칼럼의 최소값과 최대값이 얼마나 자주 바뀌느냐를 살펴보는 것이다. 예를 들어 웹서비스에서 각 페이지의 마지막 접근 정보를 기록하는 테이블을 고려해 본다면, 마지막 접근 시각을 기록하는 timestamp 자료형의 칼럼은 URL을 담고 있는 칼럼보다 훨씬 빈번하게 변경될 것이다. 따라서 URL 정보의 통계 정보 갱신 작업보다 마지막 접근 시각의 통계 정보 갱신이 더 빈번하게 일어나야 보다 정확한 실행 계획이 짜여질 것이다.

ANALYZE 명령을 사용자가 직접 실행 할 때는, 한 테이블의 특정 칼럼 정보에 대해서만 통계 정보를 갱신하도록 할 수 있다. 이렇게 함으로 위에서 언급한 것 처럼 칼럼별 통계 정보 갱신 작업 빈도를 칼럼별로 조절할 수 있다. 하지만, 운영 환경에서는 일반적으로 데이터베이스 전체를 대상으로 이 작업을 한다. 왜냐하면, 기본적으로 자료 통계 정보 갱신 작업은 전체 자료를 대상으로 하지 않고, 임의의 샘플 자료만을 대상으로 하기 때문에, 꽤 빨리 작업이 끝난다.

작은 정보

칼럼 단위로 ANALYZE 작업을 한다는 것이 운영 환경에서 사용하기에는 번거로운 일이긴 하지만, 보다 정확한 통계 정보를 싼 비용으로 갱신 할 수 있다는 점에서 장점이 있다. 예를 들어 한 칼럼의 자료 분포가 아주 넓고, 그 칼럼이 WHERE 절의 조건 검색으로 자주 사용된다면, 이 칼럼의 통계 정보는 다른 칼럼보다 더 꼼꼼하게 관리되는 것이 좋을 것이다. 이런 경우, ALTER TABLE SET STATISTICS 명령을 통해 해당 테이블의 개별 통계 수집 설정값을 바꾸어서 꼼꼼하게 관리 할 수 있다. 또한 서버 환경 설정인 default_statistics_target 값을 조정해서 데이터베이스 전체를 대상으로 조절할 수도 있다.

또한, 함수 사용에 대한 통계 정보는 기본적으로 제한된 정보만 제공 한다. 하지만, 함수 기반 인덱스를 만들었다면, 이 부분에 대해서는 통계 정보 갱신 작업이 함수 반환값을 대상으로 이루워 지기 때문에, 검색 조건으로 인덱스를 만들 때와 같이 해당 칼럼에 해당 함수를 사용하다면 쿼리 실행 계획기는 바르게 실행 계획을 짤 것이다.

작은 정보

autovacuum 데몬은 외부 테이블(foreign table)에 대해서는 ANALYZE 작업을 하지 않는다. 외부 테이블을 사용하고, 그것의 통계 정보가 갱신 될 필요성이 있다면, 직접 ANALYZE 명령을 주기적으로 실행해야 할 것이다.

24.1.4. 실자료 지도 갱신

vacuum 작업은 실자료 지도를 갱신하는 작업을 한다. 실자료 지도(visibility map, vm)란 현재 작업 중인 트랜잭션들(또는 그 자료들이 변경 되기 전까지 이용할 미래의 모든 트랜잭션들)이 실제로 사용할 자료들에 대한 각 테이블별 지도다. 이 작업은 두가지 목적이 있다. 하나는 vacuum 작업은 이미 지도 정리 작업이 끝난 것에 대해서는 더 이상 그 작업을 하지 않는다는 것이다.

다른 하나는, 이 지도 정보는 인덱스 전용 쿼리들 - 더 이상 실제 테이블 자료를 검사 하지 않는 쿼리들 - 에 대해서 빠른 응답을 제공하는데 사용된다. PostgreSQL의 인덱스에는 실자료들에 대해서만 따로 모아서 그 정보를 제공하지 않는다. 즉, 어떤 자료를 해당 세션에게 보여 주어야 할지를 결정 하는 정보는 그 자료의 테이블 페이지까지 살펴 보아야 알 수 있다. 인덱스 전용 조회인 경우는 테이블 페이지를 검색하지 않고, 먼저 이 실자료 지도를 검색해서, 이곳에 해당 자료가 있다면, 그것을 사용한다. 그만큼 테이블 페이지 읽기 작업을 줄일 수 있는 있다. 특히나 테이블 크기가 큰 경우라면, 디스크 읽기 작업을 상당히 줄이는 효과를 볼 수 있다. 왜냐하면, 실제 테이블 페이지 보다, 이 실자료 지도의 크기는 훨씬 작기 때문이다.

24.1.5. 트랜잭션 ID 겹침 오류 방지

PostgreSQL에서는 트랜잭션 자료에 대한 MVCC 기법은 트랜잭션 ID (XID)를 숫자로 처리하고 그것을 비교하는 방식이다: 한 로우의 자료 입력 XID 값이 현재 트랜잭션 XID 보다 더 크다면 앞으로 생길 - 다른 트랜잭션에서 입력된 - 자료이며, 현재 트랜잭션에서는 보이지 말아야할 자료임을 뜻한다. 그런데, 이 트랜잭션 ID는 32bit 정수형 크기이며, 이 값은 하나의 클러스터 기준으로 관리되기 때문에, 서버가 오랫 동안 운영 되었다면, (40억 트랜잭션을 넘게 사용했다면) 트랜잭션 ID 겹침 오류를 발생할 수 있다: 트랜잭션 ID 계산기가 40억을 넘어 다시 0부터 시작하려고 하면, 보관 되어 있는 모든 자료의 XID 값이 0보다 크기 때문에, 모든 자료는 보이지 말아야할 자료로 처리할 것이다. 단순하게 말하면, 자료가 엄청나게 꼬여 버릴 것이다. (실제로는 유효한 자료임에도 불구하고, 그 자료를 볼 수 없는 황당한 사태가 발생할 것이다.) 이런 문제를 방지하기 위해서, 모든 데이터베이스의 모든 테이블에 대해서 20억 트랜잭션을 사용하기 전에 vacuum 작업이 필요하다.

주기적인 vacuum 작업이 이 문제를 해결 할 수 있는 이유는 영구 보관용 자료들에 대해서는 그 자료의 트랜잭션 ID를 달리 지정해 어떤 경우에도 항상 유효한 자료로 처리한다. PostgreSQLFrozenTransactionId 라는 특별 XID를 이 용도로 사용한다. 이 XID는 일반적인 XID 비교 대상에서 항상 제외되어 항상 보여지는(언제나 old version) XID이다. 일반 XID 비교 방법은 232 나머지 연산을 이용한다. 이 말은 20억 개의 XID와, 20억 개의 XID 로 나누고, 이 XID 값은 계속 순환 하며 사용한다는 뜻이다. 그래서, 한 XID로 저장 되었다면, 그 이후 20억 개의 트랜잭션이 생기기 전까지는 그 자료는 XID로 처리되지만, 그 이상의 트랜잭션이 생기면, 그 현재 트랜잭션 기준으로 봤을 때, 그 옛 XID는 앞으로 저장될 XID로 간주해 버린다. 이 문제를 피하는 방법은 20억 트랜잭션이 생기기 전에, 그 옛 XID 자료의 XID 값을 FrozenTransactionId로 바꾸는 것이다. 이렇게 영구 보관용 자료로 바꿔 놓으면, 트랜잭션 XID 비교 작업에서 항상 제외 되기 때문에, XID 겹침 오류를 피해갈 수 있게 된다. 이 XID 변경 작업을 바로 VACUUM 명령으로 한다. (이 작업을 자료 프리징(data freezing)이라고 한다. - 옮긴이)

참고

PostgreSQL 9.4 버전까지는 이 프리징 작업은 해당 로우의 xmin 값 자체를 FrozenTransactionId (2) 로 바꾸었지만, 새 버전부터는 해당 로우가 영구 보관용으로 표시만 하고, (1 bit 플래그) xmin 값은 그대로 둔다. 이는 사고 원인 분석용도로 사용될 수 있다. 하지만, 9.4 이전부터 사용했던 데이터베이스를 pg_upgrade 명령을 이용해 업그레이드해서 사용하고 있다면, xmin 값이 FrozenTransactionId로 바뀐 자료들이 있을 것이다.

또한 시스템 카탈로그 자료인 경우는 xmin 값이 initdb 명령으로 만들어질 때 지정되는 BootstrapTransactionId (1) 로 되어 있는 경우도 있을 것이다. 이 경우도, FrozenTransactionId 와 같이 일반 XID 보다 항상 오래된 자료로 처리된다.

vacuum_freeze_min_age 환경 변수는 FrozenXID로 바꾸기 전 얼마나 옛 XID를 남길 것인가를 지정한다. 이 값이 크다면, 그 만큼 트랜잭션 정보를 많이 보관할 것이고, 이 값을 줄이면, 그 만큼 해당 테이블에 많은 트랜잭션을 vacuum 작업 없이 저장할 수 있게 된다.

VACUUM uses the visibility map to determine which pages of a table must be scanned. Normally, it will skip pages that don't have any dead row versions even if those pages might still have row versions with old XID values. Therefore, normal VACUUMs won't always freeze every old row version in the table. Periodically, VACUUM will perform an aggressive vacuum, skipping only those pages which contain neither dead rows nor any unfrozen XID or MXID values. vacuum_freeze_table_age controls when VACUUM does that: all-visible but not all-frozen pages are scanned if the number of transactions that have passed since the last such scan is greater than vacuum_freeze_table_age minus vacuum_freeze_min_age. Setting vacuum_freeze_table_age to 0 forces VACUUM to use this more aggressive strategy for all scans.

한 테이블이 vacuum 작업 없이 계속 트랜잭션 작업을 할 수 있는 간격은 그 테이블의 마지막 vacuum 이후부터 20억 - vacuum_freeze_min_age 값만큼의 트랜잭션이다. 즉, 이 이상 트랜잭션이 발생했고, vacuum 작업이 없었다면, 자료를 잃게 된다. 물론 현실적으로 이런 사태는 일어나지 않는다. 왜냐하면, autovacuum 기능을 사용하지 않더라도, autovacuum_freeze_max_age 환경 설정 값으로 지정한 간격이 생기면, 강제로 서버는 자체적으로 vacuum 작업을 진행하기 때문이다.

한 테이블에 대해 vacuum 작업을 한 번도 하지 않았더라도 autovacuum_freeze_max_age - vacuum_freeze_min_age 값 만큼의 트랜잭션이 발생했다면, autovacuum 작업이 자동으로 진행된다. 자료 변경, 삭제 작업이 빈번한 테이블들인 경우는 여유 공간 확보 작업과 동시에 진행 되기 때문에, 이 부분(트랜잭션 ID 겹침 방지 작업)은 별로 중요한 사항이 되지 않으나, 자료 추가만 계속 되는 테이블인 경우는 이 간격 만큼 주기적으로 vacuum 작업이 진행 됨을 알고 있어야 한다. 그래서 이 반복 주기를 크게 하려면, autovacuum_freeze_max_age 값을 보다 크게 설정하거나, vacuum_freeze_min_age 값을 보다 작게 설정한다.

vacuum_freeze_table_age 설정에 대한 최대값은 autovacuum_freeze_max_age * 0.95 이다. 이 보다 더 큰 값이 지정되어도 무시하고, 이 최대값이 사용된다. 95%로 지정한 이유는 이 값이 autovacuum_freeze_max_age 값보다 큰 경우는 어차피 autovacuum 작업 계획에 따라 무조건 vacuum 작업이 일어나기 때문에 의미가 없고, 이 정도의 여지를 둔것은 그 사이 사용자가 직접 VACUUM 작업을 할 것인지를 판단할 수 있도록 하기 위함이다. 대략, vacuum_freeze_table_age 값은 autovacuum_freeze_max_age 값보다 작은 값으로 지정해서, 그 차이값 만큼의 충분한 간격을 마련해 놓는 것이 좋다. 이렇게 해서, 사용자가 직접 실행하는 주기적인 VACUUM 작업이나, 자료 변경, 삭제 작업 때문에 자동으로 실행되는 autovacuum 작업들이 원활히 진행 되도록 한다. 이 값(autovacuum_freeze_max_age - vacuum_freeze_table_age)이 너무 작으면, 최근에 여유 공간 확보 작업을 위해 vacuum 작업을 했음에도 불구하고, 트랜잭션 ID 겹침 오류 방지 작업을 위해 또 vacuum 작업을 하는 빈도가 늘어날 것이며, 반대로 너무 크며, vacuum_freeze_table_age 작아서 잦은 테이블 자료 페이지 들을 전수 조사하는 빈도가 늘어날 것이다.

autovacuum_freeze_max_age 값( vacuum_freeze_table_age 값도 포함해서)을 크게 설정 할 때의 유일한 단점은 데이터베이스 클러스터 디렉터리의 하위 디렉터리인 pg_xact 디렉터리와 pg_commit_ts 디렉터리의 디스크 사용량이 커진다는 점이다. 왜냐하면, 그 만큼의 트랜잭션 커밋 정보와 (track_commit_timestamp 설정값을 활성화 했다면) 그 시간 까지 보관하고 있어야 하기 때문이다. 트랜잭션 커밋 정보는 2비트를 사용함으로 autovacuum_freeze_max_age 값을 최대치인 20억으로 지정 했다면, 그 디렉터리는 0.5GB의 공간이 필요하며, pg_commit_ts 디렉터리는 20GB가 필요하다. 이 정도의 크기가 데이터 클러스터 전체 크기에 비해서 별로 신경 쓸 크기가 아니라면, autovacuum_freeze_max_age 값으로 최대값을 지정하는 것이 좋을 것이다. 그렇지 않은 경우라면, 이 값을 적당히 조절 해서, pg_clog 디렉터리의 저장 공간을 조절할 필요가 있을 것이다. (기본값인 2억 트랜잭션이라면 pg_clog 디렉터리는 최대 50MB 공간과, pg_commit_ts 디렉터리는 최대 2GB 공간이 필요하다.)

vacuum_freeze_min_age 환경 설정 값을 줄이는 것의 한 단점은 빈번한 FrozenXID 변환 작업을 통해 전체적으로 vacuum 작업 시간이 많이 걸린다는 것이다. (굳이 필요 없는 작업을 더 하는 꼴이 된다.) 따라서 이 값은 각 자료가 더 이상 변경 되지 않아, 영구 보관용으로 남기는 것이 좋겠다싶을 자료들이 대상이 되겠끔 충분히 큰 값으로 지정하는 것이 좋다. 이 값을 줄이는 것의 또 다른 단점은 해당 테이블의 많은 자료들이 FrozenXID로 변경 되어 버리면, 데이터베이스 장애 시 원인분석을 위해 참조할 정보가 그 만큼 줄어든다는 것이다. 그래서, 정적 테이블이 아니면, 이 값을 되도록이면 줄이지 않을 것을 권장한다.

한 데이터베이스에서 가장 오래된 XID 값을 조사하는 방법은, pg_classpg_database 테이블을 살펴보는 것이다. VACUUM 작업 뒤 XID 정보를 이 두 테이블에 보관하고 있기 때문이다. pg_class 테이블의 relfrozenxid 칼럼값은 VACUUM 작업으로 그 테이블 전체에 대해서 XID 정리 작업을 했던 트랜잭션 ID 값이다. 이 값은 FrozenXID로 변경 하는 작업 시, 이 값보다 오래된 트랜잭션에 대해서는 그 대상이 됨을 식별하는데 이용 된다. 이와 비슷하게, pg_database 테이블의 datfrozenxid 칼럼은 해당 데이터베이스 전체를 대상으로 각 테이블의 relfrozenxid 값들 가운데 가장 오래된 값이다. 이 정보를 살펴볼 방법은 다음과 같은 쿼리를 실행해 보면 된다:

SELECT c.oid::regclass as table_name,
       greatest(age(c.relfrozenxid),age(t.relfrozenxid)) as age
FROM pg_class c
LEFT JOIN pg_class t ON c.reltoastrelid = t.oid
WHERE c.relkind IN ('r', 'm');

SELECT datname, age(datfrozenxid) FROM pg_database;

age 칼럼 값은 현재 시점 기준으로 마지막 vacuum 작업으로 정리된 가장 오래된 XID의 간격(나이)를 말한다.

VACUUM normally only scans pages that have been modified since the last vacuum, but relfrozenxid can only be advanced when every page of the table that might contain unfrozen XIDs is scanned. This happens when relfrozenxid is more than vacuum_freeze_table_age transactions old, when VACUUM's FREEZE option is used, or when all pages that are not already all-frozen happen to require vacuuming to remove dead row versions. When VACUUM scans every page in the table that is not already all-frozen, it should set age(relfrozenxid) to a value just a little more than the vacuum_freeze_min_age setting that was used (more by the number of transcations started since the VACUUM started). If no relfrozenxid-advancing VACUUM is issued on the table until autovacuum_freeze_max_age is reached, an autovacuum will soon be forced for the table.

만일 어떤 이유로 한 테이블에서 오래된 XID를 정리하는 작업을 autovacuum에서 실패한다면, 그 XID가 계속 남아 있어, 트랜잭션ID 겹침이 발생할 시점 기준으로 처리 가능한 트랜잭션 수가 1천1백만 정도되면 다음과 같은 서버 경고 메시지를 보여준다:

WARNING:  database "mydb" must be vacuumed within 10985967 transactions
HINT:  To avoid a database shutdown, execute a database-wide VACUUM in that database.

(서버 힌트처럼 이런 경우는 관리자가 직접 수동으로 VACUUM 작업을 해야한다. 이 작업은 데이터베이스의 datfrozenxid 값까지 변경해야 함으로 반드시 데이터베이스 관리자 권한으로 진행되어야 한다.) 이 경고를 무시한다면 트랜잭션 ID 겹침 오류가 발생되기까지, 1백만 트랜잭션 정도 남았을 때, 데이터베이스 서버는 다음 오류 메시지를 남기고 이후 모든 트랜잭션 작업을 하지 않는다:

ERROR:  database is not accepting commands to avoid wraparound data loss in database "mydb"
HINT:  Stop the postmaster and use a standalone backend to VACUUM in "mydb".

1백만 트랜잭션을 남겨 놓은 이유는 관리자가 작업 - 수동 VACUUM 작업 - 할 트랜잭션을 보장하기 위해서다. 이 메시지가 발생하고 있는 상황에서는 어떠한 작업도 할 수 없기 때문에, 관리자는 서버를 중지하고, 관리자 단독 모드로 서버를 실행해서, VACUUM 작업을 진행해야 한다. 관리자 단독 모드로 서버를 실행 하는 방법에 대해서는 postgres 설명서를 참조 하라.

24.1.5.1. 다중 트랜잭션과 겹침

(이 부분은 9.3 버전부터 새로 추가 된 부분이며, 트랜잭션 ID 겹침 오류 방지에 대한 설명과 크게 다르지 않아서 일단은 생략한다. 정말 할 일 없을 때, 우리말로 옮길 예정이다. - 옮긴이) Multixact IDs are used to support row locking by multiple transactions. Since there is only limited space in a tuple header to store lock information, that information is encoded as a multiple transaction ID, or multixact ID for short, whenever there is more than one transaction concurrently locking a row. Information about which transaction IDs are included in any particular multixact ID is stored separately in the pg_multixact subdirectory, and only the multixact ID appears in the xmax field in the tuple header. Like transaction IDs, multixact IDs are implemented as a 32-bit counter and corresponding storage, all of which requires careful aging management, storage cleanup, and wraparound handling. There is a separate storage area which holds the list of members in each multixact, which also uses a 32-bit counter and which must also be managed.

Whenever VACUUM scans any part of a table, it will replace any multixact ID it encounters which is older than vacuum_multixact_freeze_min_age by a different value, which can be the zero value, a single transaction ID, or a newer multixact ID. For each table, pg_class.relminmxid stores the oldest possible multixact ID still appearing in any tuple of that table. If this value is older than vacuum_multixact_freeze_table_age, an aggressive vacuum is forced. As discussed in the previous section, an aggressive vacuum means that only those pages which are known to be all-frozen will be skipped. mxid_age() can be used on pg_class.relminmxid to find its age.

Aggressive VACUUM scans, regardless of what causes them, enable advancing the value for that table. Eventually, as all tables in all databases are scanned and their oldest multixact values are advanced, on-disk storage for older multixacts can be removed.

As a safety device, an aggressive vacuum scan will occur for any table whose multixact-age is greater than autovacuum_multixact_freeze_max_age. Aggressive vacuum scans will also occur progressively for all tables, starting with those that have the oldest multixact-age, if the amount of used member storage space exceeds the amount 50% of the addressable storage space. Both of these kinds of aggressive scans will occur even if autovacuum is nominally disabled.

24.1.6. Autovacuum 데몬

PostgreSQL에서는 추가적인 기능이기는 하지만, VACUUM 명령과 ANALYZE 명령을 주기적으로 자동 실행하는 autovacuum이라는 기능을 기본적으로 사용하길 권장한다. 이 기능을 켜두면, 테이블의 자료가 많이 변경 되었을 때 - 추가, 변경, 삭제 작업이 많이 있었을 때 자동으로 해당 테이블에 대해서 자동으로 윗 명령들을 실행한다. 이런 자동 실행이 가능 하려면, 먼저 track_counts 환경 설정 값이 true로 지정되어 데이터베이스 작업에 대한 통계 정보를 자동으로 수집해야 한다. 이런 설정은 모두 기본 설정이며, 이와 관련된 다른 설정들도 모두 적당하게 이미 설정 되어 있다. (관리자가 임의로 이 설정을 끄지 않는다면, 서버가 시작되면 자동으로 처리됨을 의미한다. - 옮긴이)

autovacuum 데몬은 내부적으로 여러개의 프로세스로 구성된다. autovacuum launcher라는 이름의 프로세스가 항상 실행 되어 있으면서, 그 프로세스가 autovacuum worker라는 이름의 하위 프로세스를 실행 해서 모든 데이터베이스에 대한 vacuum 작업을 하는 방식으로 운영된다. launcher 프로세스는 autovacuum_naptime 값으로 지정한 초 간격으로 한 번에 하나의 데이터베이스를 작업 할 수 있도록 worker 프로세스의 실행 시간을 관리한다. (즉, N개의 데이터베이스가 있다면, autovacuum_naptime/N 초 간격으로 worker 프로세스는 자신이 작업 해야할 데이터베이스를 대상으로 작업을 시작한다.) 동시에 실행 될 수 있는 worker 프로세스의 최대 개수는 autovacuum_max_workers 환경 변수 설정값으로 지정할 수 있다. 만일 이 개수 보다 많은 데이터베이스가 있다면, 가장 먼저 실행한 worker 프로세스가 종료되는 즉시 남은 데이터베이스 가운데 하나를 대상으로 작업한다. worker가 하는 작업은 먼저 각 테이블의 상태를 조사해서, 필요하다면, VACUUM 또는 ANALYZE 명령 수행한다. log_autovacuum_min_duration 환경 변수 설정 값을 지정해서 autovacuum 상태를 지켜볼 수 있다.

만일 worker 프로세스가 각각 아주 큰 테이블의 vacuum 작업을 하게 된다면 동시에 모든 worker 프로세스가 아주 오랜 시간 작업을 하게 될 것이다. 이런 동안에는 다른 테이블들에 대해서는 autovacuum 프로세스가 vacuum 작업을 할 수 없게 됨을 관리자는 알고 있어야 한다. 하나의 데이터베이스에 대해서 동시에 실행 될 수 있는 worker 프로세스의 수는 제한이 없다. 각 프로세스들은 이미 다른 프로세스가 작업 중인 테이블에 대해서는 통과하고 다른 테이블을 조사해서 필요한 작업을 한다. 실행 되는 worker 프로세스 개수는 max_connections 설정값에 포함되지 않으며, superuser_reserved_connections 설정값에도 포함되지 않음을 알고 있어야 한다.

테이블의 나이(relfrozenxid 칼럼 값을 age() 함수로 조사한 값)가 autovacuum_freeze_max_age 설정으로 지정한 트랜잭션 수 보다 많다면, 그 테이블은 무조건 vacuum 작업을 한다. (또한 각 테이블 별로 저장 환경 설정 - 아래 참조 - 인 freeze 최대 나이값을 지정했다면, 그것을 참조해서 작업한다.) 또한, 테이블의 자료가 변경 되어, vacuum 임계치를 초과했다면, vacuum 작업을 진행 한다. 이 임계치 계산은 다음 식으로 산정한다:

vacuum 임계치 = vacuum 초기 임계치 + vacuum 배율값 * 로우수

vacuum 초기 임계치는 autovacuum_vacuum_threshold에서, vacuum 배율값은 autovacuum_vacuum_scale_factor에서, 로우수는 pg_class.reltuples에서 참조 한다.

The table is also vacuumed if the number of tuples inserted since the last vacuum has exceeded the defined insert threshold, which is defined as:

vacuum insert threshold = vacuum base insert threshold + vacuum insert scale factor * number of tuples

where the vacuum insert base threshold is autovacuum_vacuum_insert_threshold, and vacuum insert scale factor is autovacuum_vacuum_insert_scale_factor. Such vacuums may allow portions of the table to be marked as all visible and also allow tuples to be frozen, which can reduce the work required in subsequent vacuums. For tables which receive INSERT operations but no or almost no UPDATE/DELETE operations, it may be beneficial to lower the table's autovacuum_freeze_min_age as this may allow tuples to be frozen by earlier vacuums. The number of obsolete tuples and the number of inserted tuples are obtained from the statistics collector; it is a semi-accurate count updated by each UPDATE, DELETE and INSERT operation. (It is only semi-accurate because some information might be lost under heavy load.) If the relfrozenxid value of the table is more than vacuum_freeze_table_age transactions old, an aggressive vacuum is performed to freeze old tuples and advance relfrozenxid; otherwise, only pages that have been modified since the last vacuum are scanned.

analyze 작업도 위에서 설명한 방식과 비슷하게 진행 된다. 이 임계치 계산식은 다음과 같다:

analyze 임계치 = analyze 초기 임계치 + analyze 배율값 * 로우수

이렇게 계산된 임계치와 마지막 ANALYZE 작업이 있은 뒤 발생한 INSERT, UPDATE, DELETE 작업의 대상이 된 모든 로우수와 비교해서, 작업을 진행한다.

임시 테이블은 autovacuum 대상에서 제외된다. vacuum 작업과, analyze 작업이 필요하다면, 해당 세션에서 SQL 명령으로 사용자가 직접 작업 해야 한다.

임계치와 배율값은 기본적으로 postgresql.conf 파일에서 지정한다. 하지만, 각 테이블 별로도 개별 지정이 가능하다. 이 부분에 대한 것은 스토리지 매개 변수에서 자세히 다룬다. 이 값들과 autovacuum 제어 매개 변수 값들이 각 테이블 별로 지정 되면, 그 테이블에 대해서는 서버 전역으로 설정된 값이 무시된다. 그외 테이블에 대해서는 시스템 전역 환경 설정값이 적용된다. 자세한 전역 환경 설정은 19.10절에서 다룬다.

worker 프로세스가 여러 개인 경우, autovacuum 비용 지연 설정값들(19.4.4절 참조)은 균등배분 방식이다. 즉, 실행 되는 프로세스가 많다고 해도, 시스템 전체적인 입장에서 그 비용은 동일하다. 반면, 특정 테이블에 개별적으로 지정한 autovacuum_vacuum_cost_delay 또는 autovacuum_vacuum_cost_limit 설정은 위 균등 배분 비용 처리 계산에 포함되지 않는다.

autovacuum worker 프로세스가 하는 작업 때문에, 다른 작업들이 대기 상태가 되는 경우는 없다. 만일 그 다른 작업이 SHARE UPDATE EXCLUSIVE 잠금을 사용하는 경우는 autovacuum 작업이 중지 된다. 잠금과 충돌에 따른 대기 상황에 대한 자세한 설명은 xref linkend="table-lock-compatibility"/>에서 다룬다. 하지만, 트랙잭션 ID 겹침 방지를 위한 autovacuum 작업(예, pg_stat_activity 뷰에서 autovacuum 실행 쿼리 끝부분이 (to prevent wraparound) 인 경우)은 autovacuum 작업이 자동 중지 되지 않는다. (즉, 사용자의 작업이 대기 상태로 빠진다. - 옮긴이)

주의

위에서 설명한 특성 때문에, SHARE UPDATE EXCLUSIVE 잠금을 사용하는 작업 (예, ANALYZE)들이 정기적으로 실행된다면, autovacuum 작업이 계속 중지되면서 해당 테이블의 autovacuum 작업이 정상적으로 완료되지 않을 수 있다. (의역) 다음은 영어 원문, Regularly running commands that acquire locks conflicting with a SHARE UPDATE EXCLUSIVE lock (e.g., ANALYZE) can effectively prevent autovacuums from ever completing.