PG19 REPACK 온라인 재구성
PostgreSQL 19에 REPACK 명령이 추가됐고, CONCURRENTLY 옵션을 사용하면 읽기와 쓰기를 막지 않고 테이블을 다시 쓸 수 있어요. 지금까지 운영 중 bloat 정리는 확장 도구인 pg_repack에 맡기는 경우가 많았는데, 이제 코어가 그 역할을 맡게 됐어요. 이 글에서는 공식 개발 문서와 commit으로 동작을 확인하고, 19beta4 컨테이너에서 VACUUM FULL, REPACK (CONCURRENTLY), pg_repack을 같은 부하 조건에서 비교해 봤어요.
VACUUM FULL과 CLUSTER, 그리고 pg_repack
PostgreSQL 18까지 테이블 파일 전체를 다시 쓰는 명령은 두 가지였습니다. VACUUM FULL은 dead tuple을 제외한 행만 새 파일로 옮기고, CLUSTER는 같은 작업을 하면서 지정한 인덱스 순서로 행을 정렬합니다. 두 명령 모두 작업하는 동안 ACCESS EXCLUSIVE 잠금을 유지하므로 해당 테이블에는 SELECT도 들어가지 못합니다.
이름도 혼란스러웠습니다. VACUUM FULL은 이름에 vacuum이 들어가지만 일반 vacuum과 전혀 다른 작업을 합니다. CLUSTER는 IT 전반과 PostgreSQL 안에서 이미 "database cluster"라는 뜻으로 쓰이는 단어입니다. REPACK을 도입한 commit ac58465e0의 메시지도 이 두 가지를 이유로 제시합니다.
REPACK absorbs the functionality of VACUUM FULL and CLUSTER in a single command. Because this functionality is completely different from regular VACUUM, having it separate from VACUUM makes it easier for users to understand; as for CLUSTER, the term is heavily overloaded in the IT world and even in Postgres itself, so it's good that we can avoid it.
운영 중에 잠금 없이 테이블을 재구성하려면 지금까지는 코어 외부의 도구를 써야 했습니다. 대표적인 도구가 pg_repack입니다. 트리거와 로그 테이블로 변경 사항을 추적하면서 새 테이블을 만든 뒤 마지막에 교체합니다. dbalog의 VACUUM과 autovacuum 장과 인덱스 BLOAT와 REINDEX CONCURRENTLY 장도 운영 중 VACUUM FULL 대신 pg_repack을 쓰라고 안내합니다. PostgreSQL 19의 REPACK (CONCURRENTLY)는 이 역할을 코어 기능으로 제공하려는 시도입니다. 설계와 코드 대부분은 Antonin Houska(안토닌 하우스카)가 만든 서드파티 확장 pg_squeeze에 기반한다고 commit 28d534e2a에 적혀 있습니다.
REPACK 문법
공식 개발 문서의 synopsis는 다음과 같습니다.
REPACK [ ( option [, ...] ) ] [ table_and_columns [ USING INDEX [ index_name ] ] ]
REPACK [ ( option [, ...] ) ] USING INDEX
-- option:
-- VERBOSE [ boolean ]
-- ANALYZE [ boolean ]
-- CONCURRENTLY [ boolean ]
기존 명령과의 대응은 단순합니다. USING INDEX가 없으면 VACUUM FULL, 있으면 CLUSTER입니다.
REPACK t; -- VACUUM FULL t 와 같은 재작성
REPACK t USING INDEX t_pkey; -- CLUSTER t USING t_pkey 와 같은 재작성
REPACK (CONCURRENTLY) t; -- 교체 순간에만 ACCESS EXCLUSIVE
REPACK (CONCURRENTLY, ANALYZE) t; -- 재작성 후 ANALYZE까지
VACUUM FULL과 CLUSTER는 호환성을 위해 남아 있고, 문서에서는 비중이 줄었습니다. 19beta4에서 CLUSTER t USING t_pkey와 VACUUM FULL은 그대로 동작했습니다. 진행 상황을 보여 주는 뷰로 pg_stat_progress_repack이 새로 생겼습니다. VACUUM FULL로 실행해도 이 뷰의 command 컬럼에는 VACUUM FULL로 표시됐습니다. 기존 pg_stat_progress_cluster는 하위 호환용으로만 남아 있다고 문서에 적혀 있습니다.
테이블 이름 없이 REPACK만 실행하면 현재 사용자가 MAINTAIN 권한을 가진 모든 테이블과 materialized view를 처리합니다. REPACK USING INDEX만 실행하면 clustering 인덱스가 지정된 테이블이 대상입니다.
CONCURRENTLY 동작 구조
CONCURRENTLY 없이 실행하면 VACUUM FULL과 같은 방식으로 동작해 처음부터 끝까지 ACCESS EXCLUSIVE를 유지합니다. CONCURRENTLY를 사용하면 흐름이 달라집니다. commit 28d534e2a의 설명을 정리하면 다음과 같습니다.
- 원본 테이블에
SHARE UPDATE EXCLUSIVE만 획득합니다. 일반 vacuum과 같은 잠금이므로 DML과 충돌하지 않습니다. - 임시 logical replication slot을 만들고, 그 시점의 MVCC snapshot을 기준으로 기존 행을 새 파일에 복사합니다.
- 별도의 background worker(
REPACK decoding worker)가 slot에서 logical decoding을 진행하며 복사 중에 발생한 변경을 수집합니다. - 새 파일에 인덱스를 만든 뒤, 수집한 변경을 새 파일에 다시 적용합니다(catch-up).
- 마지막으로
ACCESS EXCLUSIVE를 획득해 남은 변경을 적용하고 relfilenode를 교체합니다.
19beta4에서 0.25초 간격으로 pg_stat_activity, pg_locks, pg_replication_slots를 샘플링하면 이 흐름을 그대로 확인할 수 있습니다. 실행 중에는 pg_repack_735처럼 pg_repack_ 접두사가 붙은 temporary logical slot이 생겼고, 명령이 끝나면 사라졌습니다. 확장 도구 pg_repack과 이름이 겹칩니다. 모니터링 쿼리에서 slot 이름으로 구분할 때 혼동하지 않도록 주의해야 합니다. 백엔드 목록에서는 backend_type이 REPACK decoding worker인 프로세스가 별도로 실행되고 있었습니다.
wal_level도 달라집니다. 설정값이 기본값 replica인 상태에서 REPACK (CONCURRENTLY)를 실행하자 effective_wal_level이 실행 중에는 logical로 올라갔다가, 끝난 뒤에는 replica로 돌아왔습니다. REPACK도 PostgreSQL 19의 동적 wal_level 동작(PG19 동적 wal_level 참고)을 그대로 따릅니다. 이는 명령 실행 중에 클러스터 전체의 DML이 logical decoding용 정보를 WAL에 추가로 기록한다는 뜻이기도 합니다. wal_level이 replica보다 낮으면 실행할 수 없습니다.
측정 환경
측정은 macOS 호스트의 Docker VM(2 CPU, 약 12GB 메모리, Linux 6.8 커널)에서 postgres:19beta4 이미지로 진행했습니다. 컨테이너는 --cpus 2 --memory 2g로 제한했고, shared_buffers=256MB, max_wal_size=4GB, checkpoint_timeout=30min만 변경했습니다. 이 VM은 다른 작업과 공유되고 있어 TPS 절대값은 회차마다 크게 달라졌습니다. 아래 수치는 방식 간 경향을 확인하는 용도로만 읽어 주세요.
테스트 테이블은 다음과 같이 만들었습니다. 400만 행을 넣고 75%를 지운 뒤 일반 VACUUM만 실행했습니다. 파일 크기는 그대로이고 살아 있는 행은 100만 개인 상태입니다.
CREATE TABLE t (id int PRIMARY KEY, v int NOT NULL DEFAULT 0, pad text NOT NULL)
WITH (autovacuum_enabled = off);
INSERT INTO t SELECT g, 0, repeat(md5(g::text), 3) FROM generate_series(1, 4000000) g;
DELETE FROM t WHERE id % 4 <> 0;
VACUUM (ANALYZE) t;
재작성 전 heap은 539MB, 인덱스는 86MB였고, 세 방식 모두 재작성 후 heap 137~140MB, 인덱스 21MB로 줄었습니다. 부하는 pgbench 4개 클라이언트가 남은 행을 무작위로 UPDATE하고 SELECT하는 스크립트를 45초 동안 실행하는 방식으로 생성했습니다. 시작 10초 뒤에 각 명령을 실행했습니다. 방식마다 테이블을 새로 만들어 3회씩 반복했습니다. pg_repack은 PGDG apt 저장소의 postgresql-19-repack 1.5.3 패키지를 컨테이너에 설치해 측정했습니다. pg_repack 공식 문서에는 지원 범위가 PostgreSQL 18까지로 적혀 있지만, 이 패키지는 19beta4에서 설치하고 실행할 수 있었습니다.
잠금 범위
pgbench 부하 중 세 방식의 결과입니다. 값은 3회 측정값을 순서대로 적었습니다.
| 방식 | 소요 시간(초) | pgbench 최대 지연(ms) | 명령 구간 WAL(MB) |
|---|---|---|---|
VACUUM FULL | 1.24 / 1.24 / 1.52 | 1,208 / 1,216 / 1,481 | 154 / 154 / 154 |
REPACK (CONCURRENTLY) | 5.30 / 1.47 / 1.43 | 132 / 63 / 122 | 181 / 178 / 176 |
| pg_repack 1.5.3 | 2.54 / 2.35 / 1.53 | 167 / 102 / 46 | 181 / 180 / 179 |
VACUUM FULL은 소요 시간이 가장 짧았지만, pgbench의 최대 지연은 명령 소요 시간과 거의 같았습니다. 샘플링 결과에서도 VACUUM FULL이 ACCESS EXCLUSIVE를 보유하는 동안 pgbench 세션 4개가 모두 Lock 대기 상태였습니다. 3회차에는 1초 동안 처리한 트랜잭션이 32건까지 줄어든 구간이 있었습니다. 또한 VACUUM FULL이 ACCESS EXCLUSIVE를 기다리는 동안 pgbench 세션 2개가 그 뒤에서 대기하는 샘플도 관찰됐습니다.
REPACK (CONCURRENTLY)는 전체 샘플 구간에서 SHARE UPDATE EXCLUSIVE만 보였고, Lock 대기 중인 pgbench 세션은 0개였습니다. 교체 순간의 ACCESS EXCLUSIVE는 0.25초 간격의 샘플에서 한 번도 관찰되지 않을 만큼 짧았습니다. 최대 지연 63~132ms는 잠금 대기보다 2 CPU VM에서 복사 작업과 CPU를 나눠 쓴 영향으로 보입니다. 다만 이번 측정으로 원인을 분리하지는 못했습니다. 1회차만 5.30초가 걸렸는데, 샘플상 seq scanning heap 단계가 다른 회차보다 길었습니다. 공유 VM이라 이 차이의 원인도 확인하지 못했습니다.
pg_repack도 pgbench를 멈추지 않았고, 최대 지연은 REPACK (CONCURRENTLY)와 비슷한 범위였습니다. pg_repack 문서에 따르면 초기 설정과 마지막 교체 단계에서 짧게 ACCESS EXCLUSIVE를 획득하고, 나머지 구간에서는 SHARE UPDATE EXCLUSIVE를 유지합니다. 이번 샘플링에서는 복사 세션의 AccessShareLock만 관찰됐습니다.
WAL과 디스크 공간
부하 중 WAL 양에는 pgbench가 생성한 WAL이 섞여 있습니다. 명령 없이 pgbench만 실행한 구간은 3초에 약 23MB였습니다. 명령 자체의 WAL을 확인하려고 부하 없이 한 번씩 더 실행하고 pg_waldump --stats로 구성을 확인했습니다. 크기는 pg_size_pretty()와 같은 1024 단위로 적었습니다.
| 방식 | 명령 구간 WAL | 주요 레코드 |
|---|---|---|
VACUUM FULL | 154MB | XLOG(full page image) 628건, 약 153MB |
REPACK (CONCURRENTLY) | 173MB | Heap 1,000,067건, 약 153MB / XLOG FPI 약 19MB |
| pg_repack 1.5.3 | 173MB | Heap 1,000,169건, 약 153MB / XLOG FPI 약 19MB |
VACUUM FULL은 새 heap을 페이지 이미지 단위로 WAL에 기록했습니다. 반면 REPACK (CONCURRENTLY)는 이번 측정에서 행 100만 개를 Heap 레코드 100만 건으로 하나씩 기록했습니다. 레코드 구성은 INSERT ... SELECT로 복사하는 pg_repack과 거의 같았습니다. 그 결과 무부하 기준 WAL은 VACUUM FULL보다 약 12% 많았습니다. 행 폭이 다르면 이 비율도 달라질 것으로 보입니다. 다만 이번에는 행 하나당 약 100바이트인 테이블 하나만 측정했습니다. standby나 WAL archive가 연결된 환경이라면 이 차이가 전송량과 보관량으로 이어집니다.
디스크 공간은 원본과 새 파일이 잠시 함께 존재하므로 기존 명령과 마찬가지로 넉넉해야 합니다. 문서는 인덱스 스캔이나 정렬이 없는 순차 스캔이면 테이블 크기와 인덱스 크기의 합만큼 여유 공간이 필요하다고 설명합니다. 순차 스캔 후 정렬이면 테이블 크기의 최대 두 배와 인덱스 크기만큼 필요합니다. CONCURRENTLY는 복사 중 들어온 변경을 새 파일에 반영할 수 있을 때까지 임시 파일에 따로 보관하므로 임시 공간을 조금 더 사용합니다.
logical slot이 WAL을 보존하는 문제도 있습니다. 개발 초기에는 slot의 restart_lsn이 가장 오래된 트랜잭션, 대개 REPACK 자신의 트랜잭션에 묶여 WAL이 필요 이상으로 남았습니다. commit 45b02984e는 WAL segment 경계를 넘을 때마다 slot을 전진시켜 실행 중에도 오래된 WAL을 재활용하도록 고쳤습니다. 이번 측정 규모에서는 이 동작을 따로 확인하지 않았습니다.
장점
가장 큰 변화는 온라인 재구성에 확장 설치가 필요 없어졌다는 점입니다. pg_repack은 대상 데이터베이스에서 CREATE EXTENSION pg_repack을 실행해야 하고, 서버 버전에 맞는 클라이언트 바이너리도 따로 관리해야 합니다. 관리형 서비스라면 허용된 확장 목록에도 포함돼야 합니다. REPACK (CONCURRENTLY)는 SQL 한 줄로 실행할 수 있고, 필요한 권한도 테이블의 MAINTAIN 권한입니다. commit 5dbb63fc8에서 REPLICATION이나 LOGIN 속성 요구도 제거했습니다.
변경 추적 방식도 다릅니다. pg_repack은 원본 테이블에 트리거를 추가해 모든 DML을 로그 테이블에 한 번 더 기록하지만, REPACK은 이미 기록되는 WAL을 logical decoding으로 읽습니다. 진행 상황은 pg_stat_progress_repack의 단계와 카운터로 확인합니다. 전용 slot 풀(max_repack_replication_slots, 기본값 5)이 따로 있어 logical replication용 slot을 소진하지 않는다는 점도 운영에 편리합니다(max_repack_replication_slots 참고).
단점과 제약
사용할 수 없는 테이블
문서에 적힌 제약을 19beta4에서 하나씩 실행해 봤습니다. 모두 명시적인 오류와 함께 거부됐습니다.
| 대상 | 19beta4 오류 메시지 |
|---|---|
| identity index 없는 테이블 | Relation "nopk" has no identity index. |
REPLICA IDENTITY FULL | This operation does not support tables with REPLICA IDENTITY FULL. |
| 트랜잭션 블록 안 | REPACK (CONCURRENTLY) cannot run inside a transaction block |
| 테이블 이름 생략 | REPACK (CONCURRENTLY) requires an explicit table name |
| 파티션 테이블 부모 | REPACK (CONCURRENTLY) is not supported for partitioned tables |
UNLOGGED 테이블 | This operation is only supported for permanent relations. |
| materialized view | This operation is not supported for materialized views. |
max_repack_replication_slots = 0 | REPACK can only be used if "max_repack_replication_slots" > 0 |
문서에는 조건이 "primary key와 index 기반 replica identity가 모두 없는 경우"로 적혀 있습니다. 실제로 primary key 없이 NOT NULL unique 인덱스를 REPLICA IDENTITY USING INDEX로 지정한 테이블은 통과했습니다. 파티션 테이블은 부모에서 거부되지만 파티션 하나를 지정하면 실행됐고, 오류 HINT도 파티션별로 실행하라고 안내합니다. 이 밖에도 시스템 카탈로그와 TOAST 테이블, heap이 아닌 access method, user_catalog_table 테이블은 문서상 제외 대상입니다. max_repack_replication_slots는 서버를 재시작해야 적용되는 파라미터입니다.
시작 단계의 대기
initializing 단계에서는 logical decoding을 준비하면서 XID를 할당받은 트랜잭션이 모두 끝나기를 기다립니다. 다른 테이블을 UPDATE한 뒤 5초 동안 커밋하지 않는 세션을 열어 두고, 0.5초 뒤 작은 테이블에 REPACK (CONCURRENTLY)를 실행했습니다. 단독 실행 때 0.13초 걸리던 명령이 4.70초 걸렸습니다. 기다리는 동안 백엔드에는 initializing 단계에서 IPC / RepackWorkerExport 대기 이벤트가 나타났습니다. 대상 테이블과 관계없는 트랜잭션이라도 XID를 가진 채 오래 열려 있으면 REPACK 시작이 그만큼 늦어집니다. 문서도 이 단계가 명령 시작 시점에 실행 중이던 가장 긴 트랜잭션보다 오래 걸릴 수 있다고 설명합니다.
한 번에 하나만 진행
PostgreSQL 19에서는 REPACK (CONCURRENTLY)가 클러스터 전체에서 한 번에 하나만 진행됩니다. 데이터베이스별 snapshot으로 이 제약을 풀려던 commit 0d3dba38c는 근본적인 결함이 발견돼 01a80f062로 되돌려졌고, 제약 해소는 다음 개발 주기로 넘어갔습니다. max_repack_replication_slots 기본값이 5여도 동시에 5개가 실행되지는 않습니다.
측정에서도 같은 결과가 나왔습니다. 큰 테이블 t에 REPACK (CONCURRENTLY)를 시작하고 0.4초 뒤 작은 테이블 t2에 같은 명령을 실행했습니다. t2는 오류 없이 initializing 단계에서 RepackWorkerExport를 기다리다가 t가 끝난 뒤 완료됐습니다. t2를 단독으로 실행했을 때는 0.13초였지만, 이때는 1.07초가 걸렸습니다. 여러 테이블을 정리하는 스크립트를 병렬로 실행해도 실제로는 직렬로 처리된다는 점을 고려해 일정을 세워야 합니다.
교체 단계의 잠금 대기
마지막 교체에는 여전히 ACCESS EXCLUSIVE가 필요합니다. 대상 테이블을 읽은 뒤 4초 동안 트랜잭션을 열어 둔 세션을 만들고 REPACK (CONCURRENTLY)를 실행했습니다. REPACK은 catch-up 단계에서 ACCESS EXCLUSIVE를 기다렸습니다. 그 뒤에 들어온 단순 SELECT도 REPACK의 잠금 요청 뒤에서 2.04초를 기다렸습니다. 당시 pg_locks에는 부여되지 않은 AccessExclusiveLock 1건(REPACK)과 AccessShareLock 1건(새 SELECT)이 함께 나타났습니다. ALTER TABLE 같은 DDL에서 흔히 나타나는 잠금 대기열과 같은 형태입니다. 따라서 긴 트랜잭션이 있는지 먼저 확인하고 실행하는 습관은 여전히 필요합니다.
MVCC 안전성
문서는 REPACK (CONCURRENTLY)를 TRUNCATE, 테이블을 재작성하는 ALTER TABLE과 함께 MVCC-safe하지 않은 명령으로 분류합니다(13.6절). 명령이 커밋되기 전에 snapshot을 얻었으며 그때까지 이 테이블에 접근하지 않은 트랜잭션은, 커밋 후 테이블이 비어 있는 것으로 볼 수 있습니다. 테이블에 한 번이라도 접근한 트랜잭션은 ACCESS SHARE를 보유합니다. REPACK의 교체는 이 트랜잭션이 끝날 때까지 기다리므로, 이런 트랜잭션은 해당하지 않습니다. REPEATABLE READ나 SERIALIZABLE로 긴 보고서 쿼리를 실행하는 시스템이라면 알아 둘 부분입니다. 이번 측정에서는 이 현상을 재현하지 않았습니다.
그 밖에 문서는 REPACK이 시작된 뒤 들어온 행은 USING INDEX 순서로 정렬되지 않으며, 실행 중 다른 트랜잭션이 DDL을 실행하면 REPACK이 실패할 수 있다고 설명합니다. 실행 도중 취소하면 어떻게 되는지도 확인했습니다. 실행 중 temporary logical slot이 생성된 것을 확인한 뒤 pg_cancel_backend()로 취소하자 slot은 남지 않았고 effective_wal_level도 replica로 돌아왔습니다.
pg_repack과 비교
문서와 측정에서 확인한 차이를 정리하면 다음과 같습니다.
| 항목 | REPACK (CONCURRENTLY) | pg_repack 1.5.3 |
|---|---|---|
| 설치 | 코어 명령 | 확장과 클라이언트 바이너리 |
| 변경 추적 | logical decoding (temporary slot) | 트리거와 로그 테이블 |
| 필요 키 | identity index (PK 또는 replica identity 인덱스) | PK 또는 NOT NULL 컬럼의 unique 인덱스 |
| 파티션 테이블 | 파티션 단위로만 | --parent-table 옵션 |
| 정렬 재작성 | USING INDEX | --order-by, --no-order 등 |
| wal_level 영향 | 실행 중 effective_wal_level = logical | 없음 |
| 무부하 WAL (이번 측정) | 173MB | 173MB |
어느 쪽이든 교체 순간의 ACCESS EXCLUSIVE와 원본 크기만큼의 추가 디스크는 피할 수 없습니다. PostgreSQL 19로 올린 뒤 바로 pg_repack을 제거하기보다는 파티션 테이블 일괄 처리(--parent-table)나 인덱스 병렬 재생성(--jobs)처럼 pg_repack에만 있는 기능을 사용하고 있는지 먼저 확인하는 것이 안전합니다.
진행 상황 확인 쿼리
측정 중에는 아래 쿼리에서 command, phase 컬럼과 slot 목록, 잠금 집계를 사용했습니다. 나머지 컬럼과 조건은 문서를 기준으로 덧붙였습니다. 진행 단계와 처리량은 pg_stat_progress_repack에서 확인합니다.
SELECT pid, relid::regclass, command, phase,
heap_blks_scanned, heap_blks_total,
heap_tuples_inserted, heap_tuples_updated, heap_tuples_deleted
FROM pg_stat_progress_repack;
initializing에 오래 머물면 XID를 가진 긴 트랜잭션이나 먼저 실행 중인 다른 REPACK (CONCURRENTLY)가 있는지 확인합니다. catch-up에서 멈춰 있으면 교체용 잠금을 기다리는 중인지 확인합니다. 문서도 catch-up 단계가 ACCESS EXCLUSIVE 대기 시간을 포함한다고 설명합니다.
-- REPACK이 만든 temporary slot과 wal_level 상태
SELECT slot_name, slot_type, temporary, restart_lsn
FROM pg_replication_slots
WHERE slot_name LIKE 'pg\_repack\_%';
SHOW effective_wal_level;
-- 대상 테이블의 잠금 대기열
SELECT mode, granted, count(*)
FROM pg_locks
WHERE relation = 't'::regclass
GROUP BY 1, 2
ORDER BY 1;
버전별 차이
REPACK은 PostgreSQL 19에 처음 들어온 명령이라 18 이하에는 없습니다. 18 이하에서는 VACUUM FULL, CLUSTER, pg_repack이 그대로 선택지입니다. 19 개발 주기에 확인한 주요 commit은 다음과 같습니다.
| 날짜(UTC) | commit | 내용 |
|---|---|---|
| 2026-03-10 | ac58465e0 | REPACK 명령 추가 |
| 2026-04-06 | 28d534e2a | CONCURRENTLY 옵션 추가 |
| 2026-04-07 | e76d8c749 | max_repack_replication_slots 추가 |
| 2026-04-20 | 5dbb63fc8 | REPLICATION, LOGIN 요구 제거 |
| 2026-05-24 | 01a80f062 | 데이터베이스별 snapshot revert, 동시 1개 제약 유지 |
| 2026-05-27 | 2af1dc892 | 실행 후 effective_wal_level이 logical에 머무는 문제 수정 |
| 2026-05-29 | 45b02984e | 실행 중 오래된 WAL 재활용 허용 |
19beta4 발표(2026-09-24)에도 REPACK의 crash, invalid 인덱스와 materialized view 처리, 권한과 오류 메시지 수정이 포함돼 있습니다. 발표 이후에도 ADD COLUMN으로 테이블을 재작성하지 않고 추가된 컬럼을 처리하는 수정(45da2c1d7)과 REPACK 스트레스 테스트에서 발견된 초기 decoding snapshot 문제 수정(46024c573)이 이어졌습니다. 후자는 모든 브랜치에 있던 버그라고 commit 메시지에 적혀 있습니다. RC는 10월 초로 예고돼 있으니, 운영 적용 여부는 GA 버전의 릴리스 노트를 다시 확인한 뒤 판단하는 편이 좋습니다.
측정해 보니 부하가 있을 때의 잠금 영향은 pg_repack과 비슷했고, 설치 없이 SQL 한 줄로 끝난다는 점이 가장 크게 느껴졌어요. 다만 동시에 하나만 실행된다는 제약과 시작, 교체 단계의 대기는 GA 이후에도 계속 확인해 볼 생각이에요.