데이터베이스 기술/POSTGRESQL

Vacuum이해와 사용법

(주)비트나인 2023. 8. 28. 11:19

PostgreSQL에서 Vacuum을 이해하기 위해서는 Dead Tuple존재의미를 알아야 하며 Dead Tuple은 어떻게 해서 생기게 된 것인지 알아야 한다. PostgreSQL에는 여러 좋은 기능들이 많지만 Dead Tuple의 존재는 가장 아픈 곳이기도 하다. 아픈 곳을 Vacuum이라는 기능으로 최적화하며 고쳐 나가는 것이 중요하다.

Vacuum을 잘 활용 하여 수집된 통계 정보로 최적의 쿼리 플랜을 유도 할 수도 있다.

이번 장에서는 Vacuum의 생겨난 배경부터 최적화까지 설명하면서 Vacuum을 좀더 쉽게 이해하고 관리방안을 찾도록 하는데 목적을 두고 있다.

[PostgreSQL에서 Vacuum 생겨난 배경]

PostgreSQL에서 Dead Tuple이 생겨난 배경을 설명하기 위해서는 트랜잭션 관련 아키텍처를 언급하지 않을 수 없다.

일반적으로 RDBMS에서는 2가지의 중요한 트랜잭션 아키텍처를 갖고 있다.

하나는 ACID(원자성,일관성,고립성,지속성)이며 다른 하나는 MVCC(Multi-Version

Concurrency Control, 다중 동시성제어)이다.

ACID는 예상치 못한 오류가 발생한 후에도 데이터베이스를 유효한 상태로 유지하는 데이터베이스 속성이다. 예를 들어, 많은 데이터의 행을 업데이트했는데 중간에 시스템이 실패하는 경우 행이 수정되어서는 안 되며 변경 전 상태로 유지 되어야 한다.

다중 버전 동시성 제어(MVCC)는 레코드의 중복 사본을 생성하여 동일한 데이터를 병렬로 안전하게 읽고 업데이트하게 하는 고급 데이터베이스 기능이다.

DBMS에서 ACID는 트랜잭션 고립 Level에 따라 데이터를 보호하고 데이터 무결성을 보장하기 위해 Database를 사용하는데 따르는 제약사항이 다르다.

모든 트랜잭션 Level을 설명하는 대신 DBMS마다 가장 많이Default로 적용되고 있는 Read Commited에 대해 잠깐 언급하기로 한다.

Read Commited 모드는 COMMIT이 완료된 데이터만 SELECT시에 보이는 수준을
보장하는 Level이다.
하지만, 이 모드에서 문제는 트랜잭션 진행 중일 때 SELECT하는 데이터가 동일하다는 보장을 해주지 않는다는 것이다.
데이터정합성이 보장되는 데이터를 읽기위해서는 트랜잭션이 Commit되기를
기다려야 하거나 변경 전 데이터로 복구해서 봐야 한다.
이러한 문제를 해결할 수 있는 것이 MVCC이다. MVCC는 트랜잭션 진행 과정에서 변경 전 데이터를 Snapshot으로 보관하여 특정 시점에서 SELECT하는 경우 해당 지점의 Snapshot을 읽어 보여준다.

따라서 MVCC에 의해 트랜잭션 진행 중에도 데이터 무결성을 손상시키지 않고 동시에 다른 사용자에 의해 데이터 접근 및 조회를 할 수 있게 되었다.

이때, 변경 전 데이터를 보관하고 Commit된 이후에도 변경 전 데이터를 참조하는 경우가 없다면 Vacuum 기능을 사용하여 변경 전 데이터를 삭제대상 데이터로 표시

해 둔다.

이러한 MVCC아키텍처 구현을 위해서 PostgreSQL에서 Vacuum이라는 특별한 기능을 만들었다. 오라클에서는 MVCC구현을 위해 Undo라는 별도 저장소를 통해 변경 전 데이터를 저장하고 있다. 오라클에서는 Vacuum 프로세스는 존재하지 않음.

이번 장에서는 PostgreSQL에서 Vacuum이 왜 중요하고, 어떻게 동작하며, 어떻게 활용해야 하는지 설명하고자 한다.

[PostgreSQL에서 Vacuum이란]

PostgreSQL에서 Vacuum은 아키텍처 측면과 DB 운영 측면에서 매우 중요한 부분을 차지하며 최적화된 운영시스템을 만들기 위해서 잘 활용하는 것이 중요하다.

Vacuum을 쉽게 설명하면 디스크 조각모음 또는 Garbage Data(Dead Tuple)를 정리

하는 작업이라고 비유 할 수 있다.

아래 그림은 예전에Windows환경에서 조각모음 전에 보았던 이미지로 빨간색으로 표시된 부분이 Garbage Data에 해당되며 데이터가 변경되는 과정에서 크고 작은 의미 없는 공간이 생겨난다.

빨간색 부분을 사용할 수 있는 의미 있는 공간으로 만들기 위해 Vacuum 수행이 필요하다.

[Dead Tuple이란]
PostgreSQL 에서 모든 데이터는 Tuple 이라 불리는 형태로 저장이 된다.
그리고 모든 tuple 은 Live tuple, Dead tuple 로 나뉘며, 더 이상 사용(참조)되지 않는 Tuple 을 Dead tuple 이라 부른다.
또한 Dead tuple 은 PostgreSQL이 MVCC를 구현한 방법으로 인해 발생한다

수많은 데이터가 사용자나 Batch 및 Interface를 통해 Database에 저장된다.

특히, Update와 Delete의 트랜잭션이 발생하는 동안 다른 사용자가 동일 데이터에

접근하여 조회 할 수 있어야 한다.

변경되고 있는 데이터에 대해서 다른 사용자도 조회 할 수 있도록 하는 것이 MVCC 메커니즘인데 트랜잭션이 빈번하게 발생되는 OLTP 환경의 DBMS에서는 중요한

아키텍처 중에 하나이다.

PostgreSQL에서는 값들이 갱신되거나 삭제할 때 변경 전 데이터를 동일한 데이터 저장소에 그대로 남겨두고 기존 데이터를 Overwrite하거나 정리하지 않고 삭제 대상으로 표기만 한 채 남겨두고 Update할 데이터는 새로운 Row에 저장한다.

이러한 것으로 UPDATE, DELETE Transaction 이벤트가 많아질수록 Dead Tuple 수가 늘어나 스캔 범위가 커지면서 Disk I/O가 많이 발생하게 되고 결국 성능 저하의 원인이 된다.

[비트나인_데이터 Update 시 발생되는 현상]

 

그래서, 이러한 Dead Tuple를 잘 관리하는 것이 매우 중요하다.

아래 그림에서 위에서 설명한 내용을 예제로 재현한 것이다.

최초 table_size가 304KB 였으나, Update 1000건 이후 데이터 사이즈가 736KB로 늘어났고 또 다시 1000건을 Update하면 Dead tuple 값이 2000건으로 늘어났다. 이때, 해당 테이블은 Bloating현상이 일어났다고 말하며 인덱스 없이 조회 시 테이블 전체 스캔 범위가 늘어나게 되므로 성능 저하의 원인이 된다.

이러한 성능저하를 막기위해 PostgreSQL에서는 Background로 수행되는 별도

프로세스(AutoVacuum)가 있으며, 데이터관리 최적화를 위해 추가로 튜닝 가능한 파라미터(AutoVacuum 관련)를 별도로 셋팅 해야 할 수도 있다.

[Vacuum 동작이해]

앞에서도 강조하였지만, Vacuum의 동작을 이해하기 위해서는 MVCC를 먼저 잘 이해하고 있어야 한다. 왜냐하면 MVCC를 구현하기 위한 문제점을 해결하기 위해 Vacuum의 기능을 만들어 문제점을 해결 할 수 있었다.

MVCC (Multi-Version Concurrency Control)구현으로 트랜잭션 발생 중에도 조회 시점에 따라 변경 전 데이터를 보여줌으로 조회 동시성을 높이고 데이터의 일관성(사용자 조회 시점에 따라 해당 데이터를 조회)도 보장할 수 있었다. PostgreSQL에서는 이러한 변경 전 데이터를 별도 저장소에서 관리하지 않고 데이터가 저장되는 일반 데이터파일에 같이 존재하면서 관리가 필요해 졌고 (Auto)Vacuum이라는 프로세스가 구현이 되어 역할을 하게 되었다.

아래 그림은 위에서 언급한 내용을 그림으로 표현한 것이다.

Session1에서 Update를 수행하는 동안 동시에 Session2, Session3에서 동일 데이터를 각각 조회 및 변경하는 모습이다.

Session2에서 Session1의 동일 데이터를 조회하고자 할 때 조회 시점이 Commit 전

이므로 변경 전의 데이터를 참조하게 된다.

Session2에서 Session1 트랜잭션 종료(Commit) 후 조회시에는 변경 후 데이터를 참조한다.

Session3에서는 Session1 변경 중에 동일 데이터 변경 시 Lock충돌에 의해 대기가

발생한다. Session1에서 Commit 이후 Lock이 해제되면 Session3에서 변경작업이

수행된다.

하지만, 이러한 과정으로 변경 전 데이터를 Overwrite하지 않고 새로운 Row를

생성하여 Update를 함으로 회색으로 표시된 Dead tuple이 생성된다.

Dead tuple이 오랫동안 쌓이게 되면 테이블 스캔에 범위가 커져 시스템에 성능 저하가 발생하게 된다.

이때Vacuum 수행을 통해 회색으로 표시된 부분이 저장 가능한 Row로 표시되고 신규 데이터가 Insert될 때 저장가능한 Row를 확인하여 저장을 하게 함으로 Table 데이터 Row가 급격하게 늘어나는 것을 방지 할 수 있다.

[비트나인_PostgreSQL의 MVCC 수행 모습]

위 그림에서 언급되었던 내용을 Table page구조에서는 다음과 같이 작동 되는 것을

확인 할 수 있다.

Insert 트랜잭션 발생 시 XID가 채번되어 생성되고, 테이블 신규 레코드의 XMIN필드

에 채번 된 XID값이 업데이트 된다.

이렇게 각 테이블의 XMIN, XMAX 필드에 XID값으로 업데이트 되는 이유는 조회 시점의 Current XID 값과 비교하여 특정 시점의 데이터를 추출하기 위함이다.

A. XID=1001 시점에 (1, test1)값을 Insert, 해당 테이블의 XMIN에 1001 업데이트

B. XID=1002 시점에 (2, test2)값을 Insert, 해당 테이블의 XMIN에 1002 업데이트

C. 이후 XID=2001 시점에 (2, test2)값을 (2, test2_update1)로 Update하면 변경 전 레코드 (2, test2)의 XMAX값에 Update 시점의 XID를 설정

XID 트랜잭션이 정상적으로 Commit되면 XMAX값이 있는 과거 레코드는 Vacuum에 의해 삭제표시가 되어 다른 트랜잭션에서 재 사용된다.

정리를 하면 Vacuum은 다음 과정으로 진행된다.(아래 그림 참조)

A. 기존 테이블에서 Update 변경

B. Update가 일어난 변경 테이블에는 변경 전 데이터와 변경 후 데이터가 존재

- Commit 후 AutoVacuum에 의해 변경 전 데이터가 FSM에 사용가능한 Row로 등록

C. 신규데이터 Insert 시 FSM에 사용가능한 Row위치를 확인

D. 신규데이터가 FSM에 등록된 사용가능한 위치에 Insert

주의) Vacuum으로 해당 테이블의 dead tuple 정리를 해야 하지만 다음의 경우 정리 안되어 Table 부풀림 현상이 발생 할 수 있다.

1. 트랜잭션 종료가 안된 상태

아래 이미지에서 update 쿼리를 수행중인 세션의 state값이 idle in transaction으로 되어 있다. 트랜잭션 종류가 되지 않고 남아 있는 상태이며 오랫동안 종료가 되지 않고 해당 테이블에서 Update 및 Delete가 다른 Row에 의해 수행되는 경우 idle in transaction 발생 시점의 XID 이후 발생한 Dead tuple은 Vacuum에 의해 레코드 삭제 표시가 되지 않고 Table 부풀림 현상이 발생 할 수 있다.

Vauum test를 수행하였지만 n_dead_tup 값이 2로 남아 있는 것을 볼 수 있다.

아래 그림은 idle in transaction 세션에서 commit을 수행 후 Vacuum test를 수행한 결과이다. => n_dead_tup 값이 0 으로 변경됨.

2. Replication 노드구성에서 hot_standby_feedback = ON 설정 시

PostgreSQL Replication 아키텍처에서 발생할 수 있는 이슈로 Master에서는 Dead Tuple로 확인되어 Vacuum이 정리한 Tuple이 Replica server에서는 복제 지연이나 Long transaction 수행으로 인해 여전히 그 데이터를 필요로 할 수 있다. 이렇게 되면 replication conflict가 발생하기 때문에 PostgreSQL에서는

hot_standby_feedback = ON 설정을 통해 Replica 서버에서 수행한 쿼리와 현재 Replica 서버의 가장 오래된 트랜잭션 정보를 Master로 feedback을 주게 되고, 그때까지 Master는 해당 Dead Tuple을 정리할 수 없게 된다.

이러한 현상으로 Vacuum 수행 시 Dead tuple을 정리하지 못하는 상황이 발생 할 수 있다. => 모니터링 중요

[Vacuum 모니터링 및 활용방안]

Vacuum의 목적은 Dead tuple을 가능한 적게 발생하도록 하는데 있다.

Dead tuple을 잘못 관리되었을 때 문제점에 대해서는 앞에서 설명하였다.

vacuum은 DB파라미터 최적화 설정으로 autovacuum이 동작되도록 하는 것이 중요하다.

Vacuum 모니터링의 가장 일반적인 방법은 사용자 테이블의 Live tuple과 Dead tuple 비율(ratio)을 모니터링하고 80% 미만 인 경우 Vacuum 수행이 정상 동작하지 않거나 파라미터 설정을 다시 한번 점검할 필요가 있다.

하지만, 80% 기준에는 함정이 있을 수 있다.

PostgreSQL의 default 설정에서는

10GB 테이블은 2GB의 Dead Tuple이 발생했을 때,

1TB 테이블은 200GB의 Dead Tuple이 발생했을 때 AutoVacuum이 수행된다.

작은 테이블에서는 티가 나지 않겠지만 테이블이 점점 커질수록 AutoVacuum이 수행됐을 때 정리해야 하는 Dead Tuple이 너무 많아지고 부하가 커지는 상황이 발생할 수 있기 때문에 조금 더 자주 수행될 수 있도록 조정이 필요하다.

Autovacuum의 디폴트 설정값으로 어느정도 Dead tuple 발생을 억제할 수 있으나 빈번한 트랜잭션 발생하는 테이블과 데이터 사이즈가 큰 경우 디폴트값 대신 테이블 데이터 변경량을 고려하여 설정 값을 다르게 하여 최적화를 진행 할 수 있다.

아래 그림은 특정 문제되는 테이블에 대해서 디폴트 값을 변경하는 방법이다.

autovacuum_vacuum_scale_factor 값을 0으로 하여 live tuple 대비 dead tuple 의 비율 의해 수행되기 보다 autovacuum_vacuum_threshold 값을 지정하여 해당 테이블의 Dead tuple 개수가 지정된 값 이상인 경우(예 : 100,000 개) autovacuum을 수행하게 설정할 수 있으며, 설정 값은 동적으로 적용가능하여 운영중에도 설정값을 변경할 수 있다.

이외에 AutoVacuum의 성능 관련하여 최적화를 할 수 있는 파라미터는 다음과 같다.

  • AutoVacuum 수행 Term이 긴 경우
    • autovacuum_vacuum_scale_factor & autovacuum_vacuum_scale_threshold
    • autovacuum_vacuum_insert_scale_factor & autovacuum_vacuum_scale_threshold

  • AutoVacuum 수행이 느린 경우
    • autovacuum_vacuum_cost_delay
    • autovacuum_vacuum_cost_limit
    • autovacuum_naptime
    • autovacuum_max_workers
    • autovacuum_work_mem
    • max_parallel_maintenance_workers

[참고사항]

  • 일반 Vacuum, AutoVacuum과 Vacuum full의 차이
    • Vacuum, AutoVacuum을 수행했을 때는 OS 디스크의 공간 반환까지는 처리되지 못하고 FSM에만 반환되어 이 공간(위에서 흰 빈 공간)을 재사용할 수 있게끔만 처리
      • Online으로 수행되는 동안 DML 가능하며, DDL에 대해서는 제약을 받는다
    • Vacuum Full은 OS 디스크의 공간 반환까지 처리
      • Access Exclusive Lock을 획득해 DML은 물론 SELECT도 대기하게 되어 운영 중에는 할 수 없는 작업
  • 정기적으로 시스템 최적화를 위해 문제가 되는 테이블단위로 Vacuum Full 기능을 활용하여 Reorg하는 것도 고려해 볼만 하다.
  • Vacuum full을 수행을 고려 해야하는 Case
    • Autovacuum을 적절히 수행하고 있지만 시간이 지나면서 Dead Tuple의 비율이 높아지는 경우
    • Disk 증설보다 여유공간을 확보 해야 하는 경우
    • SQL튜닝등을 진행 하였으나 크게 성능이 나아지지 않는 경우
    • 충분한 Disk 공간 필요 -> 테이블 마다 1Copy 추가 Disk 소요
  • Oracle, MySQL에서는 변경데이터를 Overwrite하고 변경 전 데이터는 Undo라는 별도 데이터파일에 저장을 하며, 또한 Undo영역은 주어진 사이즈에서 Recycle로 Overwrite하게 되어 있다. 이런 메커니즘으로 Vacuum과 같은 별도 관리 프로세스가 없지만 오라클 역시 Undo 영역의 tablespace관리와 얼마나 변경 전 데이터를 보관 할 것인지에 대한 설정 관리는 필요하다.

PostgreSQL의 Dead tuple을 최소화하는 아키텍처를 갖고 있지만 대신 복잡한 메커니즘으로 구성되어 변경 전 데이터를 조회하는데 DB 내외부의 많은 자원을 필요로 할 수 밖에 없다.

[결론]

Vacuum에 대해서 설명을 하였으나 Dead Tuple에 대해 너무 두려움을 가질 필요는 없다.

Vacuum 특성을 잘 이해하고 현재 운영되고 있는 시스템 환경과 테이블 특성을 파악하여 적용한다면 오히려 Vacuum을 효과적으로 활용하여 좀더 정확한 통계정보 수집을 통해 최적의 플랜을 유도 할 수도 있다.

글 : 김영우 센터장 ( 비트나인 기술서비스센터 )