본문 바로가기
엑셈 경쟁력/DB 인사이드

DB 인사이드 | PostgreSQL Vacuum - 3. Age

by EXEM 2022. 4. 29.

 

 

📢 본 문서에서는 XID의 연장선상에 있는 Age에 대해 알아보도록 하겠습니다. ①Age의 개념과 도입 배경을 우선 확인한 후, ②Table 및 Row에서 사용되는 Age의 의미와 차이점에 대해 알아보도록 하겠습니다.

Age 란?

Age = Current XID - 생성시점의 XID

앞서 Data Freezing과정을 설명하며 “Current XID - 생성(입력) 시점의 XID” 값이 21억을 초과하기 전에 Frozen XID로 변경돼야 XID Wraparound 상황을 피할 수 있다고 이야기했습니다.

이처럼 Data Freezing 대상을 선정하기 위해서는 XID값 그 자체보다는 입력(생성) 후 얼마나 오래되었는지 측정할 방법이 필요해졌으며 이를 위한 개념으로 Age가 도입되었습니다.

Age의 특징은 다음과 같습니다.

  • Age 측정의 대상은 DB / Table / Row이다.
  • Object 생성 시점의 Age는 1이다.
  • Row 입력 시점의 Age는 1이다.
  • 트랜잭션 발생 시 각각의 Age는 증가한다.
  • Object의 Age는 Row의 Age와 같거나 크다.
  • Age가 큰 Object 및 Row는 Vacuum의 대상이 된다.
  • Vacuum 수행 후 Age는 낮아진다.

Table과 Row의 Age는 age 함수를 이용하여 계산이 가능한데, 이렇게 계산되는 Age는 Vacuum의 대상을 선정하거나 내부적인 동작 방식을 결정하는데 기준점 역할을 합니다. Age를 이용한 내부 동작 과정에 관해서는 이후 Manual Vacuum과정을 통해 보다 자세히 알아보도록 하겠습니다.

 

Table과 Row의 Age

Age개념은 XID와 분리해서 생각할 수 없습니다. Age 자체가 생성 시점의 XID와 Current XID의 차이를 의미하기 때문인데, 같은 이유로 Age가 사용되는 곳 역시 XID와 동일한 Database, Table, Row입니다. 이 중 Row의 Age에 대해 먼저 알아보겠습니다.

Row Age

--Row(Tuple) Age
insert into age_table values(1);
insert into age_table values(2);
insert into age_table values(3);

select age(xmin),
    txid_current() as current_xid,
    xmin,
    c1 
from age_Table;

age|current_xid|xmin    |c1|
---+-----------+--------+--+
  3|   19314384|19314381| 1|
  2|   19314384|19314382| 2|
  1|   19314384|19314383| 3|

Row의 Age는 XMIN을 이용하여 계산할 수 있습니다.

위 예제에서 데이터 입력 시점의 XID가 XMIN으로 설정되는데, XMINAge함수를 적용하면 Row의 Age를 계산할 수 있습니다.

"Row" 그 자체는 Data Freezing의 대상이지만 "Row의 Age"는 Data Freezing의 기준이 됩니다. 데이터 입력 후 Transaction이 발생할수록 Row의 Age 역시 증가하기 마련이며, Row의 Age가 VACUUM_FREEZE_MIN_AGE 수치에 도달하면 Data Freezing의 대상이 됩니다. 이후 Vacuum작업 시 실제 Data Freezing이 발생합니다.

📌 vacuum_freeze_min_age
Vacuum이 Freeze 할 Row의 최소 Age이며, 기본값은 5천만(50,000,000)입니다.

 

이러한 Data Freezing작업은 9.4 버전 이전까지는 실제 XMIN값을 Frozen XID로 변경하는 방식으로 동작했습니다. 하지만 9.4 버전부터는 XMIN을 직접 변경하는 대신 t_infomask의 10번째 Bit값을 1로 변경하는 방식으로 변경되었습니다.

VACUUM FREEZE명령을 통해 강제로 Data Freezing을 발생시킨 후 변화를 살펴보겠습니다. 명령어 수행 후 XMIN값의 변경은 없지만 t_infomask값은 변경된 것을 확인할 수 있습니다.

select * from heap_page_items(get_raw_page('age_table', 0))

lp|lp_off|lp_flags|lp_len|**t_xmin**  |t_xmax|t_field3|t_ctid|t_infomask2|**t_infomask**|t_hoff|t_bits|t_oid|t_data|
--+------+--------+------+--------+------+--------+------+-----------+----------+------+------+-----+------+
 1|  8160|       1|    28|**19314381**|0     |       0|(0,1) |          1|      **2304**|    24|      |     |      |
 2|  8128|       1|    28|**19314382**|0     |       0|(0,2) |          1|      **2304**|    24|      |     |      |
 3|  8096|       1|    28|**19314383**|0     |       0|(0,3) |          1|      **2304**|    24|      |     |      |

vacuum freeze age_table;

select * from heap_page_items(get_raw_page('age_table', 0))

 lp|lp_off|lp_flags|lp_len|**t_xmin**  |t_xmax|t_field3|t_ctid|t_infomask2|**t_infomask**|t_hoff|t_bits|t_oid|t_data|
--+------+--------+------+--------+------+--------+------+-----------+----------+------+------+-----+------+
 1|  8160|       1|    28|**19314381**|0     |       0|(0,1) |          1|      **2816**|    24|      |     |      |
 2|  8128|       1|    28|**19314382**|0     |       0|(0,2) |          1|      **2816**|    24|      |     |      |
 3|  8096|       1|    28|**19314383**|0     |       0|(0,3) |          1|      **2816**|    24|      |     |      |
📌 위 예제는 pageinspect Extension을 설치해야 확인이 가능합니다.
[PostgreSQL: Documentation: 14: F.23. pageinspect]

 

해당 t_infomask값을 Bit값으로 변환해보면 아래와 같이 10번째 Bit가 0에서 1로 변경되면서 실제 Data Freezing이 발생한 것을 확인할 수 있습니다.

select  2304::bit(16) as before, 2816::bit(16) as after

before          |after           |
----------------+----------------+
0000100100000000|0000101100000000|

Table Age

이번에는 Table의 Age에 대해 알아보도록 하겠습니다. Table의 Age는 pg_class.relfrozenxid를 이용해 계산할 수 있습니다.

--Object(Table) Age
create table age_table (c1 integer);

select age(relfrozenxid) as age, 
    txid_current() as current_xid, 
    relfrozenxid  as created_xid
from pg_class where relname ='age_table';

age|current_xid|created_xid|
---+-----------+-----------+
  1|   19314370|19314369   |

Object의 Age는 Row의 Age와 같거나 크다. (오래됐다)

Row와 달리 Table은 Freezing 대상이 아니며 대상일 필요도 없습니다. 그렇다면, Freezing의 대상도 아닌 Table의 Age가 왜 필요한지 누군가에겐 의문이 될 수도 있습니다. 하지만 위와 같은 Age의 특성으로 인해, Table의 Age는 Row들의 Age를 대표하는 역할이 가능하며, 이는 Data Freezing을 필요로 하는 Row의 존재 여부를 손쉽게 판단할 수 있는 기준으로서 그 가치가 있습니다.

사실, Table의 Age계산에 사용되는 relfrozenxid 역시 특정 조건 만족 시 그 값이 변경될 수 있으며, 이로 인해 Table의 Age 역시 낮아질 수 있습니다. 이때의 조건이란 전체 테이블에 대한 Data Freezing 필요 여부를 모두 확인한 시점을 일컫는데, 해당 내용의 이해를 위해서는 Eager Mode의 Vacuum 동작에 대한 이해가 필요하므로, 이후 Manual Vacuum 과정을 통해 다시 한번 언급하도록 하겠습니다.

 

지금까지 PostgreSQL의 MVCC모델을 시작으로 Transaction ID, Age 등 XID관련 내용을 다뤘습니다. 다음 문서에서는 Vacuum의 동작 방식을 이해하기 위해 꼭 필요한 Visibility Map에 대해 알아보도록 하겠습니다.

 

 

기획 및 글 | 기술기획팀

이미지 제작 | 디자인그룹 이민석

 

 

 

댓글