Concurrency Control 챕터에서는 PostgreSQL이 어떻게 Concurrency Control을 구현하였는지, 이를 위해 어떤 동작을 수행하는지 설명하였습니다. 해당 챕터에서 “저런 식으로 계속 튜플을 생성하고 필요없는 튜플을 그대로 두는 것은 자원 낭비 아닌가?” 라는 의문을 가질 수 있습니다. 네 맞습니다. 자원 낭비입니다. 이를 관리하지 않고 가만히 둘 경우에는 결국 모든 자원이 고갈되고 해당 Instance는 더 이상 사용할 수 없는 지경이 될 것입니다. 이와같은 문제를 해결하기 위해 PostgreSQL은 Vacuum이라는 과정을 수행합니다.
공식 문서에 의하면 PostgreSQL은 Vacuum을 통해 다음과 같은 문제를 해결하고자 합니다.
- 더 이상 참조되지 않는 튜플이 차지하는 디스크 공간을 확보합니다.
- 쿼리 실행 계획 생성 시, 사용할 통계 정보를 갱신합니다.
- 인덱스 탐색 성능을 향상하는데 필요한 VM(Visibility Map) 정보를 갱신합니다.
- 트랜잭션 ID 혹은 다중 트랜잭션 ID 중복으로 인한 오래된 튜플의 손실을 방지합니다.
Reference. https://www.postgresql.org/docs/current/routine-vacuuming.html https://www.postgresql.org/docs/current/sql-vacuum.html
이번 장에서는 PostgreSQL에서 Concurrency Control을 유지하기 위한 동작인 Vacuum에 대해 알아볼 것입니다. Vacuum은 청소기를 돌린다는 의미입니다. 이를 인식하고 해당 챕터를 읽는다면 좀 더 쉽게 이해 할 수 있을 것입니다.
Vacuum
Vacuum은 데드 튜플(더 이상 참조되지 않는 튜플)이 차지하는 스토리지를 회수하는 작업을 수행합니다. 이는 Update 또는 Delete 동작으로 생성된(Insert 만 수행된 데이터는 최신 버전이므로 데드 튜플이 아닙니다) 튜플 중 더 이상 어느 트랜잭션에서도 참조하지 않는 튜플인 데드 튜플을 제거함으로서 이루어집니다.
Vacuum은 Update가 자주 발생하는 테이블에서 주로 수행된다는 점을 통해 위의 개념을 이해할 수 있습니다.
Vacuum reclaims storage occupied by dead tuples. In normal PostgreSQL operation, tuples that are deleted or obsoleted by an update are not physically removed from their table; they remain present until a vacuum is done. Therefore it’s necessary to do vacuum periodically, especially on frequently-updated tables.
– PostgreSQL 공식 문서 –
Vacuum Process
Vacuum은 다음과 같은 과정으로 수행됩니다. 상술하였던 Vacuum이 해결하고자 하는 4가지 주요 문제와 비교해가며 읽어봅시다.
- 대상 테이블에 대한 Lock을 획득합니다.
- [In Table] Phase 1
- 데드 튜플을 찾습니다.
- 오래된 튜플에 대하여 Freeze를 진행합니다.
- [In Table] Phase 2
- 데드 튜플을 제거하고 라이브 튜플을 재할당합니다.
- FSM(Freespace Map)과 VM(Visibility Map)을 업데이트합니다.
- [In Table] Phase 3
- 인덱스를 정리합니다.
- 마지막 페이지가 비어있고 가능하다면 이를 제거합니다.
- 대상 테이블의 통계와 System Catalog를 개인합니다.
- 대상 테이블에 대한 Lock을 해제합니다.
- Post Processing
- System Catalog를 업데이트합니다.
- 불필요한 페이지, Clog 파일 제거합니다.
(Standard) Vacuum vs Vacuum Full
PostgreSQL 공식 문서에서는 Vacuum을 Standard Vacuum과 Vacuum Full로 나누어 기술하고 있습니다(Standard Vacuum은 일반적인 Vacuum을 의미합니다 의미 구분을 위해 본문에서는 Standard Vacuum이라 전체 이름을 사용하겠습니다). 두 Vacuum의 차이는 테이블에 대한 Exclusive Lock의 획득 여부입니다. Standard Vacuum은 Exclusive Lock을 쉽게 획득 할 수 있는 특수한 경우에만 Exclusive Lock을 획득합니다. 따라서, DDL 명령어는 수행이 불가능하지만 DML 명령어는 동시에 수행이 가능합니다. 그러나, Vacuum Full의 경우에는 테이블에 대한 Exclusive Lock을 획득하기 때문에 DDL 뿐만 아니라 DML 또한 동시에 수행할 수 없습니다.
Reference. https://www.postgresql.org/docs/current/routine-vacuuming.html
Non-Aggressive vs Aggressive
Vacuum은 Non-Aggressive와 Aggressive 두 가지 전략을 활용합니다. 해당 전략은 Vacuum 과정 중, Freeze 과정에서 다르게 동작합니다. 이는 Freeze 문단에서 후술하겠습니다.
Autovacuum
Vacuum은 PostgreSQL이 사용하는 자원을 일정하게 유지하는 작업이기 때문에 수행 시점을 온전히 사용자에게 위임할 수 없습니다. 따라서, PostgreSQL은 autovacuum 데몬(백그라운드에서 수행되는 프로세스)를 통해 주기적으로 autovacuum_worker 프로세스를 호출하여 자동으로 vacuum을 수행하도록 합니다.
Vacuum은 문제를 어떻게 해결하는가?
본 문단에서는 Vacuum이 수행하는 주요 과정의 메커니즘에 대해 기술합니다.
Recovering Disk Space
Vacuum의 주요 역할 중 하나는 불필요하게 사용 중인 공간을 회수하는 것입니다. 이를 위해 Vacuum은 더 이상 어느 트랜잭션에서도 참조하지 않는 버전의 튜플(데드 튜플)을 제거하고 공간을 회수하는 작업을 수행합니다.
우리는 위에서 Standard Vacuum과 Vacuum Full에 대해서 언급하였습니다. Recovering Disk Space 단계에서는 두 가지 경우에 대해 다른 동작을 수행합니다. 이 둘의 차이는 실제 운영체제에 공간을 반환하는 지 여부입니다.
Standard Vacuum에서는 데드 튜플을 제거하고 재사용 할 수 있는 공간(Free Space)을 갱신합니다. 그러나, 테이블 끝에 하나 이상의 페이지가 완전히 비어있고 테이블의 Exclusive Lock을 획득할 수 없는 경우를 제외하고는 운영 체제에 공간을 반환하지 않습니다.
Vacuum Full에서는 테이블 파일을 아예 새로 작성하여 Fragment가 없는 압축된 형태의 테이블 파일을 제공합니다. 즉, 실제 운영체제에 사용하지 않는 공간을 반환합니다. 해당 방식은 새로운 테이블 파일을 만드는 동안 복사본을 만들고 완료 후 이를 삭제하는 과정을 거칩니다.
다음 예시를 통해 Standard Vacuum과 Vacuum Full의 차이를 이해해보겠습니다.

위의 예시는 Standard Vacuum을 수행한 예시입니다. 회색으로 표시된 데드 튜플이 제거되었고 Free Space 공간이 늘어난 것을 확인할 수 있습니다. 그러나, 공간이 Fragment 되어있고, 실제 파일 사이즈는 그대로이므로 파일 시스템에 공간을 반환하지는 않았습니다. 해당 사항들은 Vacuum Full이 수행될 때, 정리됩니다.

위의 예시는 Vacuum Full의 수행예시입니다. 모든 튜플을 재배열하여 Fragment를 정리하였고 이로 인해 발생하는 빈 페이지는 모두 파일 시스템에 반환되었습니다. 해당 과정은 실제로는 새로운 테이블 파일을 작성함으로써 이루어집니다.
Transaction ID wraparound failure Prevention
위에서 우리는 Vacuum이 “트랜잭션 ID의 중복으로 인한 데이터 손실을 방지”한다는 사실에 대해 언급하였습니다.
Transaction ID는 2^31 (약 20억)개의 원형(Circular)으로 구성되어 있습니다. 즉, 현재 트랜잭션 ID가 한 바퀴를 돌게 되면 이전 트랜잭션 ID를 재사용하게 되는데 해당 트랜잭션 ID를 기존 트랜잭션이 아직 사용 중이라면 기존 트랜잭션의 데이터를 덮어쓰게되는 문제가 발생할 수 있음을 의미합니다. 이러한 상황을 일반적으로 “Transaction ID wraparound failures”라고 명명합니다.
PostgreSQL은 이 문제를 해결하기 위하여 Freezing이라는 개념을 적용하였습니다. 이를 위해 PostgreSQL은 모든 트랜잭션 ID보다 오래된 것으로 간주하는 XMIN_FROZEN이라는 개념을 사용합니다. 해당 Bit는 t_infomask에 표기되며 해당 튜플은 “항상” 과거로 간주합니다.
Postgres 9.4 이전에는 FROZENXID = 2(예약된 트랜잭션 아이디)를 t_xmin에 할당하는 방식을 사용하였으나, Postgres 9.4부터는 t_infomask에 XMIN_FROZEN bit를 표기하는 방식으로 바뀌었습니다.
또한, 위 문제를 해결하기 위해서는 현상 발생 이전에 필수적으로 Vacuum이 완료되어야 하므로 Vacuum이 수행되는 시점, 주기를 정의하는 것이 필수적입니다. 하단의 파라미터는 이를 정의하기 위한 값입니다.
- vacuum_freeze_min_age: 이 값보다 오래된 모든 트랜잭션에는 XMIN_FROZEN을 표기합니다.
Reference. https://www.postgresql.org/docs/current/routine-vacuuming.html
PostgreSQL은 매번 모든 페이지를 스캔하여 Freeze하는 것을 비효율적이라 생각하였습니다. 이를 해결하기 위하여 PostgreSQL은 Visibility Map을 활용(Visibility Map에는 페이지 내 데드 튜플 존재 여부를 기록하고 있습니다.) 하여 데드 튜플이 없는 페이지를 판단하고 해당 페이지는 스캔하지 않고 건너뛰는 규칙을 추가하였습니다.
이를 PostgreSQL 커뮤니티에서는 Frozen Lazily 또는 “Lazy Strategy를 사용하여 Freeze한다”라고 표현하고 있습니다. 해당 전략은 Non-aggressive Vacuum에서 사용됩니다.
ts: 2022-12-06 03:18:18 PST x: 0 v: 4/8515 p: 554727 LOG: automatic vacuum of table “regression.public.bmsql_order_line”: index scans: 1 pages: 0 removed, 16784562 remain, 4547881 scanned (27.10% of total)
tuples: 10112936 removed, 1023712482 remain, 5311068 are dead but not yet removable removable cutoff: 194441762, which was 7896967 XIDs old when operation ended
frozen: 152106 pages from table (0.91% of total) had 3757982 tuples frozen
index scan needed: 547657 pages from table (3.26% of total) had 1828207 dead item identifiers removed index “bmsql_order_line_pkey”: pages: 4448447 in total, 0 newly deleted, 0 currently deleted, 0 reusable I/O timings: read: 471323.673 ms, write: 23125.683 ms avg read rate: 35.226 MB/s, avg write rate: 14.049 MB/s buffer usage: 4666574 hits, 9431082 misses, 3761396 dirtied WAL usage: 4587410 records, 2030544 full page images, 13789178294 bytes system usage: CPU: user: 56.99 s, system: 74.71 s, elapsed: 2091.63 s
그러나… 특정 페이지에서 아주 오랜 시간동안 데드 튜플이 발생하지 않는다면 결국 해당 페이지에서 Transaction ID wraparound failures는 도래하게 됩니다. 사실 해당 문제는 데드 튜플 여부와는 직접적인 인과 관계가 없기 때문입니다. PostgreSQL는 이를 보완하기 위하여 특정 조건을 만족하면 모든 테이블의 페이지를 스캔하는 동작을 수행합니다. 해당 조건은 매우 직관적입니다. 데이터베이스에서 가장 최근 Freeze한 튜플의 트랜잭션 아이디를 의미하는 현재 datfrozenxid가 vacuum_freeze_table_age 파라미터보다 오래되었을 때, 동작합니다.
수식으로 나타내면 current_txid – datfrozenxid > vacuum_freeze_table_age가 됩니다.
해당 수식의 의미를 비유하여 설명하면, 좌변은 가장 오래 청소되지 않은 구역이 청소되지 않은 기간을 의미하고, 우변은 설정값입니다. 즉, 특정 조건에 따라 보이는 부분만 계속 청소(Lazy)하였더니 지나치게 오래 청소되지 않은 구간이 존재하므로 대청소(Eager)를 한다라고 비유할 수 있습니다.
이를 PostgreSQL 커뮤니티에서는 Frozen Eagerly 또는 “Eager Strategy를 사용하여 Freeze한다”라고 표현하고 있습니다. 해당 전략은 Aggressive Vacuum에서 사용됩니다.
ts: 2022-12-06 06:09:06 PST x: 0 v: 45/8157 p: 556501 LOG: automatic aggressive vacuum of table “regression.public.bmsql_order_line”: index scans: 1 pages: 0 removed, 18298517 remain, 16577256 scanned (90.59% of total) tuples: 12190981 removed, 1127721257 remain, 17548258 are dead but not yet removable removable cutoff: 213459729, which was 25528936 XIDs old when operation ended new relfrozenxid: 163469053, which is 148467571 XIDs ahead of previous value frozen: 10940612 pages from table (59.79% of total) had 640281019 tuples frozen index scan needed: 420621 pages from table (2.30% of total) had 1345098 dead item identifiers removed index “bmsql_order_line_pkey”: pages: 5219724 in total, 0 newly deleted, 0 currently deleted, 0 reusable I/O timings: read: 558603.794 ms, write: 61424.318 ms avg read rate: 23.087 MB/s, avg write rate: 16.189 MB/s buffer usage: 16666051 hits, 22135595 misses, 15521445 dirtied WAL usage: 25282446 records, 12943048 full page images, 89680003763 bytes system usage: CPU: user: 216.91 s, system: 298.49 s, elapsed: 7490.56 s
Reference. https://wiki.postgresql.org/wiki/Freezing/skipping_strategies_patch:_motivating_examples
다음 표와 같은 예시를 가정해봅시다. Is Dead는 임의로 설정한 가정입니다. 해당 상황에서 vacuum_freeze_min_age = 50000000, current_txid = 50002500 이라 가정해보겠습니다.
Page.No. | Tup.No. | t_xmin | t_max | t_infomask | Is Dead |
---|---|---|---|---|---|
1 | 1 | 1999 | 2000 | O | |
1 | 2 | 2000 | X | ||
1 | 3 | 2000 | X | ||
2 | 4 | 2200 | X | ||
2 | 5 | 2200 | X | ||
2 | 6 | 2210 | X | ||
3 | 7 | 2300 | 2301 | O | |
3 | 8 | 2301 | X | ||
3 | 9 | 3000 | X |
Page.No. | Tup.No. | t_xmin | t_max | t_infomask | Is Dead |
---|---|---|---|---|---|
1 | 1 | 1999 | 2000 | XMIN_FROZEN | O |
1 | 2 | 2000 | XMIN_FROZEN | X | |
1 | 3 | 2000 | XMIN_FROZEN | X | |
2 | 4 | 2200 | X | ||
2 | 5 | 2200 | X | ||
2 | 6 | 2210 | X | ||
3 | 7 | 2300 | 2301 | XMIN_FROZEN | O |
3 | 8 | 2301 | XMIN_FROZEN | X | |
3 | 9 | 3000 | X |
Page.No. | Tup.No. | t_xmin | t_max | t_infomask | Is Dead |
---|---|---|---|---|---|
1 | 1 | 1999 | 2000 | XMIN_FROZEN | O |
1 | 2 | 2000 | XMIN_FROZEN | X | |
1 | 3 | 2000 | XMIN_FROZEN | X | |
2 | 4 | 2200 | XMIN_FROZEN | X | |
2 | 5 | 2200 | XMIN_FROZEN | X | |
2 | 6 | 2210 | XMIN_FROZEN | X | |
3 | 7 | 2300 | 2301 | XMIN_FROZEN | O |
3 | 8 | 2301 | XMIN_FROZEN | X | |
3 | 9 | 3000 | X |
Transaction ID wraparound failure Prevention
위에서 우리는 Vacuum이 “트랜잭션 ID의 중복으로 인한 데이터 손실을 방지”한다는 사실에 대해 언급하였습니다.
Transaction ID는 2^31 (약 20억)개의 원형(Circular)으로 구성되어 있습니다. 즉, 현재 트랜잭션 ID가 한 바퀴를 돌게 되면 이전 트랜잭션 ID를 재사용하게 되는데 해당 트랜잭션 ID를 기존 트랜잭션이 아직 사용 중이라면 기존 트랜잭션의 데이터를 덮어쓰게되는 문제가 발생할 수 있음을 의미합니다. 이러한 상황을 일반적으로 “Transaction ID wraparound failures”라고 명명합니다.
PostgreSQL은 이 문제를 해결하기 위하여 Freezing이라는 개념을 적용하였습니다. 이를 위해 PostgreSQL은 모든 트랜잭션 ID보다 오래된 것으로 간주하는 XMIN_FROZEN이라는 개념을 사용합니다. 해당 Bit는 t_infomask에 표기되며 해당 튜플은 “항상” 과거로 간주합니다.
Postgres 9.4 이전에는 FROZENXID = 2(예약된 트랜잭션 아이디)를 t_xmin에 할당하는 방식을 사용하였으나, Postgres 9.4부터는 t_infomask에 XMIN_FROZEN bit를 표기하는 방식으로 바뀌었습니다.
또한, 위 문제를 해결하기 위해서는 현상 발생 이전에 필수적으로 Vacuum이 완료되어야 하므로 Vacuum이 수행되는 시점, 주기를 정의하는 것이 필수적입니다. 하단의 파라미터는 이를 정의하기 위한 값입니다.
- vacuum_freeze_min_age: 이 값보다 오래된 모든 트랜잭션에는 XMIN_FROZEN을 표기합니다.
Reference. https://www.postgresql.org/docs/current/routine-vacuuming.html
PostgreSQL은 매번 모든 페이지를 스캔하여 Freeze하는 것을 비효율적이라 생각하였습니다. 이를 해결하기 위하여 PostgreSQL은 Visibility Map을 활용(Visibility Map에는 페이지 내 데드 튜플 존재 여부를 기록하고 있습니다.) 하여 데드 튜플이 없는 페이지를 판단하고 해당 페이지는 스캔하지 않고 건너뛰는 규칙을 추가하였습니다.
이를 PostgreSQL 커뮤니티에서는 Frozen Lazily 또는 “Lazy Strategy를 사용하여 Freeze한다”라고 표현하고 있습니다. 해당 전략은 Non-aggressive Vacuum에서 사용됩니다.
ts: 2022-12-06 03:18:18 PST x: 0 v: 4/8515 p: 554727 LOG: automatic vacuum of table “regression.public.bmsql_order_line”: index scans: 1 pages: 0 removed, 16784562 remain, 4547881 scanned (27.10% of total) tuples: 10112936 removed, 1023712482 remain, 5311068 are dead but not yet removable removable cutoff: 194441762, which was 7896967 XIDs old when operation ended frozen: 152106 pages from table (0.91% of total) had 3757982 tuples frozen index scan needed: 547657 pages from table (3.26% of total) had 1828207 dead item identifiers removed index “bmsql_order_line_pkey”: pages: 4448447 in total, 0 newly deleted, 0 currently deleted, 0 reusable I/O timings: read: 471323.673 ms, write: 23125.683 ms avg read rate: 35.226 MB/s, avg write rate: 14.049 MB/s buffer usage: 4666574 hits, 9431082 misses, 3761396 dirtied WAL usage: 4587410 records, 2030544 full page images, 13789178294 bytes system usage: CPU: user: 56.99 s, system: 74.71 s, elapsed: 2091.63 s
그러나… 특정 페이지에서 아주 오랜 시간동안 데드 튜플이 발생하지 않는다면 결국 해당 페이지에서 Transaction ID wraparound failures는 도래하게 됩니다. 사실 해당 문제는 데드 튜플 여부와는 직접적인 인과 관계가 없기 때문입니다. PostgreSQL는 이를 보완하기 위하여 특정 조건을 만족하면 모든 테이블의 페이지를 스캔하는 동작을 수행합니다. 해당 조건은 매우 직관적입니다. 데이터베이스에서 가장 최근 Freeze한 튜플의 트랜잭션 아이디를 의미하는 현재 datfrozenxid가 vacuum_freeze_table_age 파라미터보다 오래되었을 때, 동작합니다.
수식으로 나타내면 current_txid – datfrozenxid > vacuum_freeze_table_age가 됩니다.
해당 수식의 의미를 비유하여 설명하면, 좌변은 가장 오래 청소되지 않은 구역이 청소되지 않은 기간을 의미하고, 우변은 설정값입니다. 즉, 특정 조건에 따라 보이는 부분만 계속 청소(Lazy)하였더니 지나치게 오래 청소되지 않은 구간이 존재하므로 대청소(Eager)를 한다라고 비유할 수 있습니다.
이를 PostgreSQL 커뮤니티에서는 Frozen Eagerly 또는 “Eager Strategy를 사용하여 Freeze한다”라고 표현하고 있습니다. 해당 전략은 Aggressive Vacuum에서 사용됩니다.
ts: 2022-12-06 06:09:06 PST x: 0 v: 45/8157 p: 556501 LOG: automatic aggressive vacuum of table “regression.public.bmsql_order_line”: index scans: 1 pages: 0 removed, 18298517 remain, 16577256 scanned (90.59% of total) tuples: 12190981 removed, 1127721257 remain, 17548258 are dead but not yet removable removable cutoff: 213459729, which was 25528936 XIDs old when operation ended new relfrozenxid: 163469053, which is 148467571 XIDs ahead of previous value frozen: 10940612 pages from table (59.79% of total) had 640281019 tuples frozen index scan needed: 420621 pages from table (2.30% of total) had 1345098 dead item identifiers removed index “bmsql_order_line_pkey”: pages: 5219724 in total, 0 newly deleted, 0 currently deleted, 0 reusable I/O timings: read: 558603.794 ms, write: 61424.318 ms avg read rate: 23.087 MB/s, avg write rate: 16.189 MB/s buffer usage: 16666051 hits, 22135595 misses, 15521445 dirtied WAL usage: 25282446 records, 12943048 full page images, 89680003763 bytes system usage: CPU: user: 216.91 s, system: 298.49 s, elapsed: 7490.56 s
Reference. https://wiki.postgresql.org/wiki/Freezing/skipping_strategies_patch:_motivating_examples
다음 표와 같은 예시를 가정해봅시다. Is Dead는 임의로 설정한 가정입니다. 해당 상황에서 vacuum_freeze_min_age = 50000000, current_txid = 50002500 이라 가정해보겠습니다.
Page.No. | Tup.No. | t_xmin | t_max | t_infomask | Is Dead |
---|---|---|---|---|---|
1 | 1 | 1999 | 2000 | O | |
1 | 2 | 2000 | X | ||
1 | 3 | 2000 | X | ||
2 | 4 | 2200 | X | ||
2 | 5 | 2200 | X | ||
2 | 6 | 2210 | X | ||
3 | 7 | 2300 | 2301 | O | |
3 | 8 | 2301 | X | ||
3 | 9 | 3000 | X |
주어진 상황에서 Lazy 하게 Freezing을 수행한 결과는 아래 표와 같습니다. Freezing이 수행된 튜플에 t_infomask가 표기된 것을 확인할 수 있습니다. 여기서 데드 튜플이 존재하지 않는 2번 페이지는 스캔을 수행하지 않아 4, 5, 6번 튜플은 Freezing의 조건에 만족함에도 불구하고 Freezing되지 않았습니다.
Page.No. | Tup.No. | t_xmin | t_max | t_infomask | Is Dead |
---|---|---|---|---|---|
1 | 1 | 1999 | 2000 | XMIN_FROZEN | O |
1 | 2 | 2000 | XMIN_FROZEN | X | |
1 | 3 | 2000 | XMIN_FROZEN | X | |
2 | 4 | 2200 | X | ||
2 | 5 | 2200 | X | ||
2 | 6 | 2210 | X | ||
3 | 7 | 2300 | 2301 | XMIN_FROZEN | O |
3 | 8 | 2301 | XMIN_FROZEN | X | |
3 | 9 | 3000 | X |
주어진 상황에서 Eager 하게 Freezing을 수행한 결과는 아래 표와 같습니다. Lazily Freezing과는 다르게 데드 튜플이 존재하지 않는 2번 페이지의 튜플들 또한 Freezing 조건에 만족하므로 Freezing되었습니다.
Page.No. | Tup.No. | t_xmin | t_max | t_infomask | Is Dead |
---|---|---|---|---|---|
1 | 1 | 1999 | 2000 | XMIN_FROZEN | O |
1 | 2 | 2000 | XMIN_FROZEN | X | |
1 | 3 | 2000 | XMIN_FROZEN | X | |
2 | 4 | 2200 | XMIN_FROZEN | X | |
2 | 5 | 2200 | XMIN_FROZEN | X | |
2 | 6 | 2210 | XMIN_FROZEN | X | |
3 | 7 | 2300 | 2301 | XMIN_FROZEN | O |
3 | 8 | 2301 | XMIN_FROZEN | X | |
3 | 9 | 3000 | X |
Remove Unnecessary Clog
우리는 Concurrency Control에서 트랜잭션의 상태를 저장하고 있는 Clog에 대해 설명한 바 있습니다. 튜플과 마찬가지로 Clog 역시 시간이 지나 사용되지 않을 경우에는 불필요한 정보의 대상이 됩니다. 따라서, Clog 역시 Vacuum의 정리 대상입니다. PostgreSQL에서는 pg_database.datfrozenxid를 기준으로 Clog를 정리합니다. datfrozenxid는 해당 데이터베이스 내 임의의 테이블에서 마지막으로 freezing한 txid인 relfrozenxid의 최소값입니다. 즉, datfrozenxid 이하의 트랜잭션 아이디는 데이터베이스 내에서 항상 과거에 해당합니다. 따라서, PostgreSQL은 datfrozenxid에 해당하는 트랜잭션 아이디 이전의 트랜잭션 상태정보만을 포함하고 있는 Clog들을 삭제합니다. 위 설명을 다음 예시와 비교해가며 이해해봅시다.

지금까지 PostgreSQL의 Vacuum에 관해 알아보았습니다
‘PostgreSQL의 Data Access’를 바로 이어서 확인해보세요!