PostgreSQL 19 REPACK: nowa metoda defragmentacji tabel

Ze względu na sposób obsługi transakcji (MVCC) przez PostgreSQL, pliki danych na dysku mają skłonność do „puchnięcia” (bloat), czyli gromadzenia dużych ilości wolnego miejsca pozostałego po wyczyszczonych martwych rekordach (autovacuum). Z tego powodu administratorzy dbają o regularne defragmentowanie i kompaktowanie tabel o dużym stopniu tego typu fragmentacji. Przed PostgreSQL 19 służyła do tego celu komenda VACUUM FULL lub jej ulepszone alternatywy zewnętrzne typu pg_repack czy pg_squeeze. Zasadniczą wadą VACUUM FULL było blokowanie defragmentowanej tabeli w trybie wyłącznym, uniemożliwiającym wykonywanie nawet operacji SELECT.

PostgreSQL 19 przynosi nową, ulepszoną komendę standardową służącą do defragmentacji i/lub reorganizacji tabeli: REPACK. Jej podstawową zaletą jest możliwość pracy w trybie CONCURRENTLY, bez niewygodnego blokowania tabeli. W istocie REPACK zastępuje dwie stare komendy: VACUUM FULL i CLUSTER.

W celu oceny skuteczności i efektywności działania komendy REPACK, przeprowadzimy proste eksperymenty. Utworzymy sztuczną tabelę zawierającą milion rekordów, „naśmiecimy” wykonując masowe operacje DML niezmieniające jednakże liczby rekordów, usuniemy martwe rekordy, po czym przeprowadzimy defragmentację, zarówno za pomocą VACUUM FULL, REPACK jak i REPACK CONCURRENTLY, z pomiarem czasu (każdorazowo powtarzając eksperyment od początku).

Przygotowanie tabeli:

create table bloated_table (
id serial primary key, seqnum integer, filler char(500))
with (autovacuum_enabled = off);

insert into bloated_table (seqnum, filler)
select generate_series, 'X'
from generate_series(1,1000000);

select pg_relation_size('bloated_table')/1024/1024 as table_size_mb;
 table_size_mb
---------------
           520

update bloated_table set filler='Y' where seqnum % 3 = 0;

update bloated_table set filler='W' where seqnum % 3 = 0;


insert into bloated_table (seqnum, filler)

select generate_series, 'Z'
from generate_series(1000001,1100000);

delete from bloated_table where seqnum<=100000;


vacuum bloated_table;

select pg_relation_size('bloated_table')/1024/1024 as table_size_mb;
 table_size_mb
---------------
           920

Testowa tabela posiada aktualnie rozmiar 920 MB, wśród których mieści się ok. 400 MB wolnego miejsca po martwych rekordach.

Badanie efektywności VACUUM FULL:

vacuum full bloated_table; 
Time: 1854.049 ms

select pg_relation_size('bloated_table')/1024/1024 as table_size_mb;
 table_size_mb
---------------
           520

Badanie efektywności REPACK:

repack bloated_table;
Time: 1678.370 ms

select pg_relation_size('bloated_table')/1024/1024 as table_size_mb;
 table_size_mb
---------------
           520

Badanie efektywności REPACK CONCURRENTLY:

repack (concurrently) bloated_table;
Time: 2109.489 ms

select pg_relation_size('bloated_table')/1024/1024 as table_size_mb;
 table_size_mb
---------------
           521

Wnioski końcowe: skuteczność defragmentacji praktycznie jednakowa, wydajność podstawowego REPACK nie gorsza niż starego VACUUM FULL, wydajność REPACK CONCURRENTLY gorsza o ok. 30% ze względu na konieczność obsługi ewentualnych współbieżnych modyfikacji.

Warto jeszcze wspomnieć o ograniczeniach dla REPACK CONCURRENTLY. Tabela nie może być typu UNLOGGED, nie może być partycjonowana (ale może być partycją), nie może być systemową, musi posiadać klucz główny.

Dokumentacja: https://www.postgresql.org/docs/19/sql-repack.html

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Wymagane pola są oznaczone *