AWS re:Post을(를) 사용하면 다음에 동의하게 됩니다. 이용 약관

Amazon Aurora PostgreSQL Auto Vacuum 이해하기 (1/2)

9분 분량
고급
2

AWS Aurora PostgreSQL에서 발생하는 Vacuum에 대한 원리 및 목적을 이해하고, 성능 향상을 위한 최적화 전략에 대해서 알아봅니다.

Amazon Aurora PostgreSQL Auto Vacuum 이해하기 (1/2)

Author : Sungwook Kang(강성욱), AWS Solutions Architect

PostgreSQL은 오픈소스 관계형 데이터베이스로 Amazon Aurora (https://aws.amazon.com/ko/rds/aurora/) PostgreSQL은 오픈소스 PostgreSQL과 완전히 호환된 완전 관리형 데이터베이스 서비스입니다. 많은 사용자들이 PostgreSQL(이하 PG)을 사용할 때 Vacuum 동작으로 인해 예상하지 못한 성능 저하 문제를 겪고 있는데 Vacuum이 수행되었을 때 발생하는 문제는 무엇이 있는지, 그리고 이러한 문제를 최소화하기 위한 전략이 무엇이 있는지에 대해서 알아보겠습니다.

Vacuum은 왜 하는 것일까?

Vacuum은 일반 적으로 진공 청소기라는 의미와 비슷하게 PG에서 더 이상 사용되지 않는 데이터를 정리해주는 역할을 합니다. 쉽게 예를 들면 디스크 조각모음과 같습니다. postgresql vacuum

PG는 MVCC (Multi Version Concurrency Control, 다중 버전 동시성 제어)를 지원하기 때문에 데이터의 삭제, 수정이 발생하면 더 이상 사용하지 않는 여러 버전의 데이터가 존재합니다. 만약 Vacuum을 진행하지 않으면 이러한 데이터가 지속적으로 쌓여 실제 테이블 데이터 자체는 적은데 디스크의 공간을 차지하여 테이블이 지속적으로 커지는 문제가 발생합니다. 그러면 당연히 불필요하거나 부적절한 인덱스가 증가하여 조회속도가 느려지고, I/O 오버헤드가 증가할 수 있습니다. 또한 트랜잭션 ID 겹침이나, 다중 트랜잭션 ID 겹침 상황으로 오래된 자료가 손실될 수도 있으며 이러한 현상이 지속되면 트랜잭션 ID를 재활용하지 못해서 최악의 상황에는 데이터베이스가 멈추는 상황까지 발생할 수 있습니다. 이와 같은 여러 이유로 Vacuum 작업은 그 이유에 맞게 다양한 주기로, 다양한 대상으로 진행됩니다.

MVCC
동시접근을 허용하는 데이터베이스에서 동시성을 제어하기 위해 사용하는 방법입니다. MVCC 모델에서 데이터에 접근하는 사용자는 접근한 시점에서의 데이터베이스의 Snapshot을 읽는데, 이 snapshot 데이터에 대한 변경이 완료될 때(트랜잭션이 commit 될 때)까지 만들어진 변경사항은 다른 데이터베이스 사용자가 볼 수 없습니다. 이러한 개념에 의해 사용자가 데이터를 업데이트하면 이전의 데이터를 덮어 씌우는게 아닌 새로운 버전의 데이터를 생성 합니다. 대신 이전 버전의 데이터와 비교하여 변경된 내용을 기록합니다. 이러한 방식으로 하나의 데이터에 대해 여러 버전의 데이터가 존재하게 되고, 사용자는 마지막 버전의 데이터를 읽게 됩니다.

Vacuum이 하는 일은 무엇인가?

Vacuum이 실행되면 사용되지 않는 Dead Tuple (이하 데드 튜플)을 FSM(Free Space Map)에 반환합니다. 데드 튜플은 Vacuum 작업을 통해 FSM에 반환되기 전까지는 그 자리에 새로운 데이터를 저장할 수 없습니다. 예를 들어, 10만개 레코드를 가지고 있는 테이블에 업데이트를 10만개를 했다면 10만개의 데드 튜플이 생기고, 또 다시 10만개의 업데이트를 했다면 Vacuum이 실행되지 않은 상태에서는 또 다시 10만개의 데드 튜플이 발생합니다. 즉, 해당 테이블은 실제 데이터 10만개와 20만개의 데드 튜플이 발생하게 됩니다. 이때 Vacuum을 실행하면 20만개의 데드 튜플 공간을 FSM에 반환하게 되며 다음 업데이트부터는 해당 공간을 재활용할 수 있습니다. 하지만 Vacuum을 실행한다고 해서 이미 늘어난 테이블의 크기는 줄어들지는 않지만, 해당 공간이 재활용되므로 테이블 크기가 더 이상 늘어나지는 않습니다. Vacuum 이 실행되므로써 FSM에 공간반환 뿐만 아니라, 인덱스 전용 검색 성능을 향상하는데 참고하는 실 자료 지도 (VM, Visibility Map)정보를 갱신합니다. 또한 삭제된 데이터 뿐만 아니라 남아 있는 데이터에 대해서도 Frozen XID(XID 2)로 변경해 주어 앞으로 XID wrap around가 발생하더라도 트랜잭션 ID 겹침을 방지할 수 있습니다.

PG에서는 트랜잭션 ID의 크기가 32bit 정수형 크기이며, 하나의 서버에서 해당 크기를 넘기면 트랜잭션 ID가 겹치는 현상이 발생합니다.

Vacuum 실행

하지만 Vacuum 작업은 기본적으로 디스크 I/O 오버헤드를 유발합니다. 이 때문에 동시에 작업하고 있는 다른 세션의 성능을 저하시킬 수 있습니다. Vacuum 작업에 대한 비용은 아래 링크를 참고할 수 있습니다.

Vacuum은 수동 또는 자동으로 실행될 수 있으며, Vacuum 실행 시 옵션에 따른 특징이 있습니다. 수동으로 실행할 경우 아래와 같은 명령으로 실행할 수 있습니다.

-- DB 전체 full vacuum
vacuum full analyze;
 
-- DB 전체 간단하게 실행
vacuum verbose analyze;
 
-- 특정 테이블만 간단하게 실행
vacuum analyze [테이블 명];
 
-- 특정 테이블만 full vacuum
vacuum full [테이블 명];
  • Vacuum : 데드 튜플을 FSM에 반환하는 작업을 하며, 운영 환경에서 DML (SELECT, INSERT, UPDATE, DELETE)이 실행되고 있어도 동시에 사용할 수 있습니다. 하지만 DDL (ALTER TABLE) 명령은 Vacuum 작업이 실행되는 동안에는 사용할 수 없습니다.

  • Vacuum FULL : 해당 테이블의 사용할 수 있는 자료들만을 모아서 새 파일에 저장하는 방식을 이용하기 때문에 운영체제 입장에서 디스크 여유 공간을 확보할 수 있습니다. 작업 결과로 해당 테이블에 대해서 최적의 물리적 크기로 테이블이 만들어집니다. 하지만 작업 시 테이블에 대한 베타적 잠금(Exclusive Lock)을 지정하여 실행되기 때문에 해당 테이블에 대해 어떠한 작업도 할 수 없습니다. 따라서, 운영중인 데이터베이스에서는 사용하는 것을 권장하지 않습니다. 그리고 일반 VACUUM 작업에 비해 시간이 오래 걸리며, 이 작업이 완료되기 전까지 이 작업을 할 수 있는 여유 공간이 있어야 작업을 할 수 있습니다.

  • Vacuum Analyze : 통계 메타데이터를 업데이트하므로 쿼리 옵티마이저가 더 정확한 쿼리 계획을 생성할 수 있어 Vacuum 명령어 실행 시 같이 실행하는 것이 좋습니다.

Autovacuum(자동)은 내부 알고리즘으로 필요에 따라 Vacuum을 자동으로 처리해 주는 것으로 수동처럼 명령어로 테이블을 정리하는 것이 아닌 테이블 혹은 DB 단위의 설정을 통해서 vacuum이 자동으로 실행됩니다. 이때 설정된 값에 따라서 데드 튜플의 증가를 얼마나 제어할지가 정해지기 때문에 Autovacuum 을 사용할 때에는 현재 운영중인 서버의 최적화 값을 파악하고 있어야 합니다.

일반적인 Vacuum 전략은 주기적인 표준 Vacuum 작업을 진행하여 지속적으로 빈공간을 확보하여 디스크가 어느 정도 커지지만 더 이상 커지지 않게 하여 최대한 Vacuum FULL 작업을 방지하는 것입니다. Autovacuum 데몬은 이러한 전략에 따라 실행됩니다. 즉, Autovacuum 기능을 사용하되 Vacuum FULL 작업을 하지 않는 것을 기본 정책으로 설정하면 됩니다. 기본적으로 실시간(주기적) Vacuum(FULL Vacuum아님) 실시하며, autovacuum_freeze_max_age에 도달하면 강제로 Vacuum 작업이 실행되도록 설정할 수 있습니다.

정확한 데이터베이스 사용량을 파악하지 않은 상태에서 autovacuum 기능을 끄는 것은 현명하지 않은 방법일 수 있습니다. 다음 쿼리들로 튜플의 상태와 Vaccum 상태를 확인하여, Vacuum 전략을 수립하는데 도움을 받을 수 있습니다.

아래 쿼리로 튜플에 대한 정보를 확인할 수 있습니다.

SELECT
    n.nspname AS  schema_name,
    c.relname AS table_name,
     pg_stat_get_live_tuples(c.oid) + pg_stat_get_dead_tuples(c.oid) as  total_tuple,
    pg_stat_get_live_tuples(c.oid)  AS live_tuple,
     pg_stat_get_dead_tuples(c.oid) AS dead_tupple,
     round(100*pg_stat_get_live_tuples(c.oid) /  (pg_stat_get_live_tuples(c.oid) + pg_stat_get_dead_tuples(c.oid)),2) as  live_tuple_rate,
     round(100*pg_stat_get_dead_tuples(c.oid) /  (pg_stat_get_live_tuples(c.oid) + pg_stat_get_dead_tuples(c.oid)),2) as  dead_tuple_rate,
     pg_size_pretty(pg_total_relation_size(c.oid)) as total_relation_size,
     pg_size_pretty(pg_relation_size(c.oid)) as relation_size
FROM pg_class AS c
JOIN pg_catalog.pg_namespace AS n ON n.oid = c.relnamespace 
WHERE pg_stat_get_live_tuples(c.oid) > 0
AND c.relname NOT LIKE 'pg_%'
ORDER BY dead_tupple DESC;

아래 쿼리로 Vacuum 통계 정보를 확인합니다.

SELECT * FROM pg_stat_all_tables ORDER BY schemaname, relname;

Vacuum FULL 실행시 pg_class의 relfilenode 값이 변경되므로, 아래 쿼리로 relfilenode의 물리적인 파일 위치를 확인합니다.

SELECT oid, pg_relation_filepath(oid), relname, relfilenode FROM  pg_class LIMIT 10;

아래 쿼리는 현재 실행중인 Vacuum세션 정보를 확인할 수 있습니다.

SELECT
 datname,
 usename,
 pid,
 CURRENT_TIMESTAMP -  xact_start AS xact_runtime,
 query
FROM
 pg_stat_activity
WHERE
 upper(query)
 LIKE '%VACUUM%'
ORDER BY
 xact_start;

Autovacuum 데몬 워크플로우는 어떻게 구성될까요?

Autovacuum 데몬은 Autovacuum 실행기와 Autovacuum 작업자의 두 가지 다른 종류의 프로세스로 설계되어있습니다. vacuum workflow

Autovacuum 실행기는 Autovacuum 매개변수가 ON으로 설정될 때 포스트마스터가 시작하는 기본 실행 프로세스입니다. 포스트마스터는 PostgreSQL 시스템에 대한 요청에 대한 처리 메커니즘 역할을 합니다. 모든 프론트 엔드 프로그램은 시작 메시지를 포스트마스터에게 보내고 포스트마스터는 메시지의 정보를 사용하여 백엔드 프로세스를 시작합니다. Autovacuum 실행기 프로세스는 테이블에서 Vacuum 작업을 실행하기 위해 Autovacuum 작업자 프로세스를 시작할 적절한 시간을 결정합니다. Autovacuum 작업자는 테이블에서 vacuum 작업을 실행하는 실제 작업자 프로세스입니다. 실행 프로그램에서 예약한 대로 데이터베이스에 연결하고 카탈로그 테이블을 읽고 Vacuum 작업을 실행할 테이블을 선택합니다. Autovacuum 시작 프로그램 프로세스는 데이터베이스의 테이블을 계속 모니터링하고 테이블이 Autovacuum 임계값에 도달한 후 Vacuum 작업에 적합한 테이블을 선택합니다. 이 임계값은 아래와 같은 매개변수를 기반으로 합니다.

  • autovacuum_vacuum_threshold, autovacuum_analyze_threshold : 이 매개변수는 각각 Autovacuum 및 Autoanalyzer에 대해 예약할 테이블의 최소 업데이트 또는 삭제 수를 결정합니다. 둘 다 기본값은 50입니다.
  • autovacuum_vacuum_scale_factor, autovacuum_analyze_scale_factor : 이 매개변수는 각각 autovacuum 및 autoanalyzer에 대해 예약할 테이블에 대해 변경이 필요한 테이블의 백분율을 결정합니다. autovacuum_vacuum_scale_factor의 기본값은 0.2(20%)이고 autovacuum_analyze_scale_factor는 0.1(10%)입니다. 테이블의 행 수가 너무 많지 않은 경우 기본 값을 사용해도 되지만, 많은 수의 행이 있는 테이블의 경우에는 빈번한 Vacuum이 발생할 수 있어 적절한 값으로 조절하는 것이 좋습니다. 데이터베이스 내에서 큰 테이블이 일부 존재하는 경우 구성 파일보다 테이블 수준에서 이러한 매개변수를 설정하는 것이 좋습니다.

임계값 계산은 아래 공식을 사용할 수 있습니다.

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

  • Vacuum base threshold – autovacuum_vacuum_threshold
  • Vacuum scale factor – autovacuum_vacuum_scale_factor
  • Number of live tuples – The value of n_live_tup from pg_stat_all_tables view

Autovacuum 실행기는 자체적으로 Autovacuum 작업자 프로세스를 시작할 수 없으며 포스트마스터 프로세스에 의해 수행됩니다. 런처는 Autovacuum 공유 메모리 영역에 데이터베이스에 대한 정보를 저장하고 공유 메모리에 플래그를 설정하고 포스트마스터에게 신호를 보냅니다. 포스트마스터가 Autovacuum 작업자 프로세스를 시작합니다. 이 새로운 작업자 프로세스는 공유 메모리에서 정보를 읽고 필요한 데이터베이스에 연결하고 Vacuum 작업을 완료합니다. 포스트마스터가 작업자 프로세스 시작에 실패하면 공유 메모리에 플래그를 설정하고 런처 프로세스에 신호를 보냅니다. 포스트마스터의 신호를 읽고 실행기는 포스트마스터에게 신호를 전송하여 작업자 프로세스 시작을 다시 시도합니다. (포스트마스터가 작업자 프로세스를 시작하지 못하는 것은 로드 및 메모리 압력이 높거나 이미 실행 중인 프로세스가 너무 많기 때문일 수 있습니다). Autovacuum 작업자 프로세스가 Vacuum 작업을 완료하면, 런처에 신호를 보냅니다. 런처가 작업자로부터 신호를 받으면 런처가 시작되고 Vacuum 테이블 목록이 공유 메모리에 너무 많으면 다른 작업자를 시작하려고 시도합니다. 이는 다른 작업자가 해당 테이블에 대한 Vacuum 잠금을 기다리는데 차단되는 것을 방지하기 위한 것입니다. 또한 다른 작업자가 방금 정리를 완료하여 공유 메모리에 더 이상 기록되지 않은 테이블을 Vacuum하지 않도록 각 테이블을 Vacuum하기 직전에 pgstats 테이블의 데이터를 다시 로드 합니다. PostgreSQL의 일반적인 오해는 Autovacuum 프로세스가 I/O를 증가시킨다는 것입니다. 이러한 오해로 인해 많은 사람들이 Autovacuum 프로세스를 완전히 끄는 경우도 많습니다. 이러한 결정은 데이터베이스 운영 초기 단계에서는 효과적인 솔루션처럼 보일 수 있지만 데이터베이스 크기가 증가하기 시작하면 데드 튜플이 차지하는 공간이 빠르게 증가하고, 테이블 및 디스크 공간 증가와 함께 데이터베이스 속도가 느려지기 때문에 권장하지는 않습니다.

Autovacuum의 장점은 무엇일까요?

###통계 업데이트
PostgreSQL ANALYZE 데몬은 테이블의 통계를 수집하고 계산하고, 쿼리 플래너는 이러한 통계를 사용하여 쿼리 계획을 실행합니다. 이 통계정보는 ANALYZE 데몬에 의해 계산 및 수집되며 이러한 통계는 카탈로그 테이블에 저장됩니다. 그런 다음 쿼리 플래너는 데이터를 가져오기 위한 쿼리 실행 계획을 만들게 됩니다. 비슷한 시나리오에서 Autovacuum이 off로 설정되어 있으면 ANALYZE 데몬이 통계를 수집하지 않으며, 쿼리 플래너에는 테이블에 대한 통계정보가 없으므로 잘못된 쿼리 계획을 작성하게 되어 성능에 효율적이지 않습니다.

트랜잭션 warparound 방지

앞서 설명한 것처럼 PostgreSQL은 트랜잭션 ID로 트랜잭션에 숫자를 할당합니다. 트랜잭션 ID는 숫자이기 때문에 허용되는 최대값 및 최소값과 같은 제한이 있습니다. PostgreSQL은 트랜잭션 ID에4바이트 정수를 사용합니다. 즉, 4바이트로 생성할 수 있는 최대 트랜잭션 ID는 2^32으로 (~ 4294967296) 약 40억 개의 트랜잭션 ID를 사용할 수 있습니다. 그러나 PostgreSQL은 트랜잭션 ID를 1에서 2^31( ~ 2147483648)에서 회전시켜 4바이트 정수로 트랜잭션을 무제한으로 처리할 수 있습니다. 이를 경우, PostgreSQL은 트랜잭션 ID가 2147483648에 도달하면 트랜잭션 ID를 1에서 2로 변경하여 2^31까지 할당 트랜잭션을 관리하고, 이후 추가 할당 트랜잭션을 트랜잭션 ID를 1로 할당하여 사용하는데 이렇게 트랜잭션 ID를 교체하는 작업을 warparound라고 합니다. Autovacuum은 각 페이지의 각 행을 방문하여 트랜잭션 ID를 고정합니다. 데이터베이스 트랜잭션 ID 수명이 autovacuum_freeze_max_age에 도달할 때마다 PostgreSQL은 Autovacuum 프로세스를 즉시 시작하여 전체 데이터베이스에서 freeze작업을 수행합니다.

Next : Amazon Aurora PostgreSQL Auto Vacuum 이해하기 (2/2)