PostgreSQL에서 테이블 블로트(팽창)를 줄이기 위한 두 도구 pg_repack과 pg_squeeze를 비교하고, 각 도구의 장단점과 적용 시나리오를 정리한다.
URL: https://boringsql.com/posts/the-bloat-busters-pg-repack-pg-squeeze/
데이터베이스 크기가 커지고 초당 트랜잭션 수가 증가할수록, 결국 테이블 블로트(table bloat) 문제에 부딪히게 됩니다. PostgreSQL은 자동 VACUUM(autovacuum) 기능으로 가능한 한 많은 부분을 도와주지만, 언젠가는 VACUUM FULL을 실행할지 고민하게 되는 순간이 옵니다. 긴 다운타임 윈도우를 확보할 수 없다면, 이는 결코 쉬운 결정이 아닙니다.
다행히 PostgreSQL의 풍부한 생태계는 이를 더 쉽게 만들어 줄 해결책을 하나 이상 제공합니다. pg_reorg 같은 오래된 도구는 제외하고, 고려할 만한 두 경쟁자는 pg_repack과 pg_squeeze입니다. 이 글에서는 두 도구의 강점과 약점을 살펴보고, 여러분의 사용 사례에 더 적합한 선택을 할 수 있도록 돕습니다.
지속적이고 예측 가능한 트랜잭션 패턴이라면 보통 autovacuum에 의존해도 됩니다. 하지만 어떤 사용 사례에서는 이것만으로 충분하지 않습니다. 대표적으로 “벌크(bulk)” 작업—대량 입력, 대량 삭제, 또는 둘의 조합—이 포함된 경우가 그렇습니다. 예를 들어 다음과 같은 시나리오를 상상해 보세요.
ALTER COLUMN name TYPE new_type를 쓰는 것은 최선의 선택이 아닙니다).DELETE로 대량 데이터를 수정/정리해야 한다.DELETE해야 한다.이 모든 경우에서 테이블이 매우 커질 수 있고, 전통적인 해법은 VACUUM FULL이지만 이는 테이블 락(lock)으로 인한 상당한 다운타임을 동반합니다.
VACUUM FULL의 한계 때문에 여러 대안이 등장했습니다. 이 영역의 첫 주자는 pg_reorg(본 비교에서는 다루지 않음)였고, 이후 pg_repack이 이를 대체했습니다. 비교적 최근에 등장한 도구가 pg_squeeze입니다.
각 솔루션은 아키텍처, 배포 방식, 장단점이 서로 다릅니다.
추가로, 두 도구 모두 CLUSTER의 효과적인 대안으로도 사용할 수 있습니다(CLUSTER 역시 full vacuum과 유사하게 테이블에 배타적(exclusive) 락이 필요). 즉, 주어진 정렬 순서에 따라 데이터를 재배치해 스토리지를 최적화하는 데에도 유용합니다.
두 도구는 공통적인 동작 특성도 공유합니다. 블로트를 “마법처럼” 제거해 주진 않습니다. 새 테이블이 필요로 하는 만큼의 공간을 최소한 확보해야 합니다. 따라서 두 도구 모두, 사용 가능한 디스크 공간이 임계 수준에 도달하기 훨씬 전에 도입을 계획해야 합니다. 또한 두 도구 모두 상당한 양의 WAL을 생성하며, 이는 저장/처리/백업 등이 필요합니다.
이 도구들과 직접 비교할 수는 없지만, 수동으로 사용할 수 있는 또 다른 방법으로 테이블 파티셔닝(partitioning)이 있습니다. 이 비교에는 포함하지 않지만, 미리 계획해 두면 특정 유지보수 작업이 단순해지고 성능이 개선되며, 정기 VACUUM을 더 빠르고 효율적으로 만들 수 있습니다. 공정하게 말하자면—모든 것은 구체적인 시나리오와 사용 방식에 달려 있습니다.
이 글을 작성하는 시점에서 pg_repack은 PostgreSQL 생태계에서 테이블 블로트를 완화하는 가장 잘 알려진 솔루션으로 볼 수 있습니다. 확장(extension)으로 만들어져 설치가 쉽고(패키지 저장소 설치 또는 직접 컴파일한 산출물 사용), 설정을 위해 클러스터 재시작이 필요하지도 않습니다.
pg_repack은 처리 대상 테이블의 새 복사본을 만들고, 기존 데이터로 새 테이블을 채우는 동안 새로 발생하는 변경을 복제하기 위해 트리거(trigger)를 설정하는 방식으로 동작합니다. 그리고 프로세스 시작과 종료 시점(구 테이블과 신 테이블을 교체할 때)에는 배타적인 전체 테이블 락이 필요합니다.
유지보수는 CLI에서 시작하며, 개별 테이블의 repack 동작을 요구사항에 맞게 미세 조정할 수 있는 여러 인자를 지정할 수 있습니다. pg_repack은 컬럼 기반으로만 리클러스터링(re-clustering)이 가능하며, 즉 인덱스를 명시적으로 요구하지 않습니다. 따라서 이 지점에서는 pg_squeeze가 갖는 일부 제약을 우회할 수 있습니다.
pg_repack은 공간 회수에 매우 효과적이며 모든 유형의 블로트에 대응할 수 있습니다. 또한 Amazon RDS와 Google Cloud SQL 모두에서 기본 제공(out of the box)으로 사용할 수 있습니다.
단점으로는, 다양한 이유(예: 운영 중인 환경에 미치는 영향)로 프로세스를 중단해야 할 때, 대상 DB에 연결이 유지되지 않으므로 모든 조각(artefact)을 정리하지 못할 수 있다는 점입니다.
이전 솔루션과 비교해 pg_squeeze는 트리거 대신 논리 디코딩(logical decoding)에 의존하는 다른 방식으로 구현되어 있습니다. 핵심 장점은 테이블 재구성 동안 호스트 시스템에 미치는 영향이 더 낮아 가용성과 안정성이 향상된다는 점입니다.
pg_repack과 마찬가지로 pg_squeeze도 새 테이블을 만들고 블로트가 있는 테이블의 기존 데이터를 복사합니다. 여기에 논리 복제가 관여하여, 원본 테이블에서 발생하는 변경 사항을 실시간으로 새 테이블에 스트리밍합니다. 덕분에 블로트 테이블에서 수행되는 일반 작업에 불필요한 영향을 주지 않으면서, 프로세스 진행 중에도 새 테이블이 최신 상태를 유지할 수 있습니다. 결과적으로 pg_squeeze는 락 필요성을 크게 줄입니다. 배타적 락은 최종 단계(기존 테이블을 새로 최적화된 테이블로 교체하는 시점)에서만 필요합니다. 또한 배타적 락의 지속 시간도 설정할 수 있습니다.
pg_squeeze도 확장으로 제공되지만, 배포를 위해 wal_level, max_replication_slots, shared_preload_libraries 등의 설정 변경이 필요합니다. 따라서 클러스터 재시작이 요구됩니다.
한편 pg_squeeze는 일회성(ad-hoc) 처리보다는 정기적 처리(regular processing)를 염두에 두고 설계되었습니다. 테이블을 정기 처리 대상으로 등록할 수 있으며, 테이블이 “squeeze” 조건을 충족할 때마다 작업이 큐(queue)에 추가되고, 생성된 순서대로 순차 처리됩니다. 자동 처리는 대부분의 시나리오에서 쓸 수 있는 기본 옵션을 제공하지만, 클러스터/환경의 다른 운영 제약을 우회해야 하는 경우에는 제한적으로 느껴질 수 있습니다. 다만 테이블 유지보수는 수동으로 트리거할 수도 있습니다.
pg_squeeze가 유지보수 운영 및 영향 측면에서는 pg_repack보다 우수하다고 볼 수도 있지만, 공간 회수에서는 중요한 주의점이 있습니다. pg_repack과 달리, pg_squeeze는 행(row)을 있는 그대로 전체 복사합니다. 이 동작 때문에 “드롭된 컬럼(dropped columns)”으로 인해 생성된 블로트는 제거하지 못합니다(이 글을 작성한 2024년 4월 말 기준으로도 해당 동작이 유지됨).
앞서 언급했듯이 pg_squeeze는 필요 시 지정한 인덱스를 사용해 클러스터링할 수 있습니다. 다만 부분 인덱스(partial index)로는 클러스터링을 수행할 수 없다는 제약이 있습니다. pg_repack과 비교하면, 항상 실행 중인 워커 프로세스 덕분에 모든 산출물(artefact)을 적절히 정리하는 편으로 보입니다.
아쉽게도 이 글을 작성하는 시점에서 pg_squeeze는 Amazon RDS에서는 사용할 수 없고, Google Cloud SQL에서만 사용할 수 있습니다.
프로덕션에서 검증된 성숙한 도구가 두 가지나 있다는 것은 놀라운 일입니다. 둘 중 무엇을 선택할지는 구체적인 요구사항, 사용 사례, 그리고 비즈니스 서비스의 운영 특성에 달려 있습니다.
도구 간 차이를 매우 “의견 강하게(opinionated)” 구분하자면, pg_squeeze는 특정 테이블의 자동화된 지속 청소(continuous cleaning)에 사용하고, pg_repack은 통제된 셋업에서의 헤비급 챔피언으로 사용하는 방식이 될 수 있습니다.
(편집) Amazon RDS 및 Google Cloud SQL에서의 사용 가능 여부를 추가했습니다.