2. DBMS이야기/01. PostgreSQL

[PostgreSQL ADMIN] 정기적인 Vacuum 작업2

OSSW(Open Source System SoftWare 2014. 11. 26. 10:28

1.4. 실자료 지도 갱신

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

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

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

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

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

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

표준 VACUUM 작업은 한 자료 페이지에 모든 자료가 UPDATE나, DELETE 명령 없이 오직 INSERT 명령으로 자료가 입력된 것만 있다면, 그 페이지는 작업 대상에서 제외 한다. 그렇지만, 그 자료 페이지에도 아직 FrozenXID로 바뀌지 않은 XID들이 있을 것이다. 이 XID들도 당연히 FrozenXID로 바뀌어야 XID 겹침 오류를 피해갈 수 있다. 이 작업을 위해서, vacuum_freeze_table_age 환경 변수 값을 조정 해서 VACUUM 작업에서 모든 자료 페이지를 대상으로 이 XID 변경 작업 할 것인지를 결정 할 수 있다. 해당 테이블의 나이가 vacuum_freeze_table_age - vacuum_freeze_min_age 값 보다 크면 VACUUM 작업은 모든 자료 페이지를 검사해서 변경 작업을 진행한다. vacuum_freeze_table_age 값을 0으로 지정하면 효율적인 작업을 위해, 실자료 지도를 참조하지 않고, 항상 모든 페이지를 검사한다.

한 테이블이 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_clog 디렉토리의 디스크 사용량이 커진다는 점이다. 왜냐하면, 그 만큼의 트랜잭션 커밋 정보를 보관하고 있어야 하기 때문이다. 트랜잭션 커밋 정보는 2비트를 사용함으로 autovacuum_freeze_max_age 갑을 최대치인 20억으로 지정 했다면, 그 디렉토리는 0.5GB의 공간이 필요하다. 이 정도의 크기가 데이터 클러스터 전체 크기에 비해서 별로 신경 쓸 크기가 아니라면, autovacuum_freeze_max_age 값으로 최대값을 지정하는 것이 좋을 것이다. 그렇지 않은 경우라면, 이 값을 적당히 조절 해서, pg_clog 디렉토리의 저장 공간을 조절할 필요가 있을 것이다. (기본값인 2억 트랜잭션이라면 pg_clog 디렉토리는 최대 50MB 공간을 사용한다.)

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 작업은 마지막 vacuum 작업이 있은 뒤부터 변경된 데이터 페이지들에 대해서만 작업 대상으로 삼는다. 하지만, 다음 세가지 경우에는 테이블의 모든 페이지를 조사한다. 첫번째, 이 relfrozenxid 값을 조사해서, 이 값의 나이가 vacuum_freeze_table_age 값 보다 크다면 해당 테이블의 모든 페이지를 조사한다. 두번째, VACUUM 명령어에서 FREEZE 옵션을 사용할 때도 모든 페이지를 조사한다. 마지막으로 더 이상 사용되지 않는 자료를 정리하는 작업이 모든 페이지에 걸쳐 있어야 하는 경우에도 같은 작업을 한다. 보통은 이렇게 VACUUM 작업을 통해 모든 페이지가 조사 되었다면, 그 작업이 끝난 뒤 해당 테이블의 age(relfrozenxid) 값은 vacuum_freeze_min_age 약간 큰 값으로 보여진다. (VACUUM 작업이 시작되고, 확인 할 때까지의 트랜잭션 수 만큼 증가한다) 만일, VACUUM 작업을 계속 주기적으로 했으나, 항상 변경된 페이지들만 조사하는 작업만 했다면, age(relfrozenxid) 값이 autovacuum_freeze_max_age 값만큼 이르렀을 때 autovacuum 데몬에 의해 강제로 테이블의 모든 페이지를 조사해서 트랜잭션 ID 겹침 오류를 방지한다.

만일 어떤 알 수 없는 오류로, autovacuum 작업이 제대로 진행 되지 못하는 경우를 대비 해서, 그 데이이터베이스의 가장 오래된 트랜잭션이 위험 수위에 다다르면 - 통상 1천만 트랜잭션 정도 밖에 처리 할 수 없는 상황이면, 다음과 같은 서버 경고 메시지를 보여준다:

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

(서버 힌트처럼 이런 경우는 관리자가 직접 수동으로 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 설명서를 참조 하라.

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.

During a VACUUM table scan, either partial or of the whole table, any multixact ID older than vacuum_multixact_freeze_min_age is replaced 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, a whole-table scan is forced. Whole-table 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, a whole-table vacuum scan will occur for any table whose multixact-age is greater than autovacuum_multixact_freeze_max_age. This will occur even if autovacuum is nominally disabled.

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에서 참조 한다. 더 이상 사용하지 않는, 쓸모 없는 로우 정보는 통계 수집기가 수집한 정보를 사용한다. 이 정보는 UPDATE 명령이나, DELETE 명령에 의해서 갱신된 비교적 정확한 수치다. (비교적 정확하다고 한 이유는 서버 과부하시에는 이 값이 정확하게 수집되지 않을 수도 있기 때문이다.) 테이블의 relfrozenxid 나이값이 vacuum_freeze_table_age 설정값보다 크다면, 테이블의 모든 로우를 조사해서, 영구 보관용 XID로 바꾸고, relfrozenxid 값을 변경 한다. 그렇지 않은 경우는 마지막 vacuum 작업 뒤 변경된 자료 페이지만을 대상으로 작업한다.

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

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

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

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

임기치와 배율값은 기본적으로 postgresql.conf 파일에서 지정한다. 하지만, 각 테이블 별로도 개별 지정이 가능하다. 이 부분에 대한 것은 Storage Parameters에서 자세히 다룬다. 이 값들이 각 테이블 별로 지정 되면, 그 테이블에 대해서는 서버 전역으로 설정된 값이 무시된다. autovacuum에 대한 환경 설정 매개변수들의 자세한 설명은 18.10절에서 다룬다.

위에서 설명 한 것 외에 테이블 단위 저장 옵션 환경 설정 매개변수는 여섯 개가 더 있다. 하나는 autovacuum_enabled 설정으로 이 값을 false로 지정하면, autovacuum 데몬은 트랜잭션 ID 겹침 방지에 대한 검사만 하고, 나머지 모든 작업을 하지 않는다. 다른 두 개는 autovacuum_vacuum_cost_delay, autovacuum_vacuum_cost_limit 인데, 테이블 단위 vacuum 작업 지연 기능에 관계된다. (18.4.4절 참조) 나머지 autovacuum_freeze_min_age, autovacuum_freeze_max_age, autovacuum_freeze_table_age 설정은 각각 vacuum_freeze_min_age autovacuum_freeze_max_age vacuum_freeze_table_age 설정과 같은 역할을 한다.

worker 프로세스가 여러 개인 경우, 위에서 지정한 비용 처리는 "균등배분" 방식이다. 즉, 실행 되는 프로세스가 많다고 해도, 시스템 전체적인 입장에서 그 비용은 동일하다.

 

출처 : http://postgresql.kr/docs/9.3/routine-vacuuming.html

by. 백준 (2014.11.26)