PostgreSQL, Bloat은 버그가 아닌 설계적 특징?
PostgreSQL은 MVCC(Multi-Version Concurrency Control) 아키텍처로 인해 Bloat 현상이 발생하며, 이는 데이터베이스 성능 저하의 원인이 됨
UPDATE는 기존 튜플을 수정하지 않고 새로운 튜플을 생성하며, DELETE는 튜플을 삭제하지 않고 사용 불가 상태로 표시함
VACUUM은 Bloat 현상을 해결하기 위한 메커니즘이지만, VACUUM FULL은 테이블 잠금(Table Lock)으로 인한 가용성 저하를 야기할 수 있음
Autovacuum은 자동 정리 기능이지만, 과도한 쓰기 부하(Write Load) 환경에서는 Bloat 현상을 완전히 제어하기 어려움
MVCC 아키텍처와 Bloat의 관계
PostgreSQL은 MVCC(Multi-Version Concurrency Control)를 사용하여 동시성(Concurrency)을 관리한다. UPDATE 연산은 기존 데이터를 덮어쓰는 대신 새로운 튜플을 생성하고, DELETE 연산은 데이터를 삭제하지 않고 사용 불가 상태로 표시한다. 이러한 설계는 읽기-쓰기(Read-Write) 충돌을 방지하지만, 튜플이 계속 쌓여 Bloat 현상을 유발한다. 결과적으로, 데이터베이스의 디스크 사용량이 증가하고 쿼리 성능이 저하될 수 있다. 특히, 빈번한 업데이트가 발생하는 테이블에서 Bloat 현상이 심화될 수 있다.
VACUUM과 Autovacuum의 작동 원리
VACUUM은 Bloat 현상을 해결하기 위한 핵심 도구이다. VACUUM은 사용되지 않는 튜플을 제거하고, 해당 공간을 재사용 가능하도록 표시한다. 하지만 VACUUM은 테이블 파일의 크기를 줄이지는 않는다. VACUUM FULL은 테이블을 재작성하여 디스크 공간을 회수하지만, 테이블 잠금(Table Lock)으로 인해 가용성(Availability)에 영향을 미칠 수 있다. Autovacuum은 자동으로 VACUUM을 실행하는 백그라운드 프로세스이지만, 과도한 쓰기 부하 환경에서는 Autovacuum이 Bloat 현상을 따라가지 못할 수 있다.
인덱스 Bloat과 REINDEX의 필요성
삭제 및 업데이트는 테이블뿐만 아니라 인덱스(Index)에도 Bloat을 유발한다. 삭제된 튜플을 가리키는 인덱스 항목은 공간을 차지하며, 이는 쿼리 성능 저하로 이어진다. REINDEX는 인덱스를 재구성하여 Bloat을 제거한다. REINDEX INDEX CONCURRENTLY는 테이블 잠금 없이 인덱스를 재구성할 수 있지만, 시간이 더 오래 걸린다. 따라서, 인덱스 Bloat을 주기적으로 관리하여 데이터베이스 성능을 최적화해야 한다.
커뮤니티의 Bloat 관리 경험
댓글에서는 데이터 파이프라인(Data Pipeline) 재작성 후 50GB로 증가한 분석 테이블의 Bloat 문제를 경험한 사례가 공유되었다. VACUUM FULL로 인한 장시간의 테이블 잠금 문제를 해결하기 위해 pg_repack을 사용한 경험을 공유하며, 20% 이상의 Bloat 발생 시 pg_repack을 실행하는 전략을 제시했다. 이는 Bloat 관리에 대한 실질적인 팁을 제공하며, 데이터베이스 관리의 중요성을 강조한다.