-
Notifications
You must be signed in to change notification settings - Fork 4
postgres Hot Heap Only Tuples fillfactor
Without HOT, every version of a row in an update chain has its own index entries, even if all indexed columns are the same. With HOT, a new tuple placed on the same page and with all indexed columns the same as its parent row version does not get new index entries. This means there is only one index entry for the entire update chain on the heap page. An index-entry-less tuple is marked with the HEAP_ONLY_TUPLE flag. The prior row version is marked HEAP_HOT_UPDATED, and (as always in an update chain) its t_ctid field links forward to the newer version.
A HOT update can occur when the data being updated is not an indexed column and there is free space on the data page for the new row version.
-- set the fill factor to a specific value at creation time
CREATE TABLE bluebox.inventory (
inventory_id serial4 NOT NULL,
film_id int4 NOT NULL,
store_id int4 NOT NULL,
last_update timestamptz DEFAULT now() NOT NULL
) WITH (fillfactor=95); -- Reduce the fill factor to allow more space for new and updated rows
ALTER TABLE bluebox.inventory SET (fillfactor=75);
Particularly for tables that you know to be update heavy, it’s worth keeping an eye on
a few columns in pg_stat_user_tables to monitor if HOT updates are occurring
SELECT schemaname, relname, n_tup_upd, n_tup_hot_upd
FROM pg_stat_user_tables;
When the number of HOT updates (n_tup_hot_upd) is growing at a similar rate with the number of updated tuples (n_tup_upd), then there is adequate free space for PostgreSQL to keep these modifications within the same data page. If HOT updates are not occurring at a similar rate and you believe that they should be, lowering the fill factor could increase the likelihood of HOT updates taking place.
If this were a live application and no HOT updates were happening on the table (n_tup_hot_upd is 0) lowering the fill factor to 75% or 80% would be a first step to see if HOT updates would begin to happen.
HOT (Heap-Only Tuples). Jeśli aktualizowana kolumna nie posiada indeksu, a na oryginalnej stronie jest wystarczająco dużo wolnego miejsca, nowa wersja zostaje dopisana na tej samej stronie. Stary wskaźnik indeksu pokazuje po prostu na stary wiersz, który w swoim nagłówku ma przepięcie (wskaźnik) na nową wersję. Operacja ta całkowicie omija kosztowną aktualizację całego drzewa B-Tree.
Aby to zadziałało, na stronie musi być jednak fizycznie wolna przestrzeń na nowe krotki. Obniżenie parametru FILLFACTOR (np. do 85) zmusza bazę, by przy operacjach wstawiania (INSERT) zapełniała stronę tylko w 85%, rezerwując pozostałe 15% bloków na przyszłe aktualizacje HOT.
Kluczem do wyliczenia idealnego współczynnika wypełnienia jest matematyka oparta na cyklach odśmiecania (AutoVacuum). Uproszczony model sprowadza się do obliczenia, ile maksymalnie wersji (aktualizacji) dany wiersz "przeżyje" na stronie, zanim Vacuum fizycznie go wyczyści:
FILLFACTOR = 100 / (1 + U) (gdzie U to szacunkowa liczba jednoczesnych aktualizacji tego samego wiersza między cyklami Vacuum).
- Tylko INSERT / Bardzo rzadkie UPDATE: Należy zostawić wartość 100. Dane są gęściej upakowane, co zmniejsza zużycie dysku i pozwala załadować więcej krotek do pamięci podręcznej (Shared Buffers), maksymalizując wskaźnik trafień.
- Intensywne UPDATE na kolumnach bez indeksu: Obniżenie FILLFACTOR do poziomu 70-90. Optymalizuje to I/O i ratuje CPU, ponieważ ogromna większość operacji wykona się jako lokalne modyfikacje krotek (HOT).
- Częste UPDATE na kolumnach indeksowanych: Mechanizm HOT i tak nie zadziała. Utrzymywanie niskiego FILLFACTOR nie przyniesie tu korzyści, a jedynie "nadmucha" rozmiar tabeli (tzw. bloat).
Kluczem jest sprawdzenie, czy obniżony FILLFACTOR faktycznie przełożył się na wykonywanie aktualizacji w trybie Heap-Only Tuples (HOT). Wykorzystujesz do tego systemowy widok statystyk pg_stat_user_tables.
Należy monitorować stosunek dwóch kolumn:
- n_tup_upd – całkowita liczba zaktualizowanych wierszy.
- n_tup_hot_upd – liczba wierszy zaktualizowanych w trybie HOT (bez dotykania indeksów).
Wzór na efektywność to: (n_tup_hot_upd / n_tup_upd) * 100.
- Wynik bliski 100%: FILLFACTOR jest dobrany idealnie, aktualizacje mieszczą się na oryginalnych stronach.
- Wynik niski (np. < 20%): Oznacza jeden z trzech problemów: brakuje wolnego miejsca na stronach (FILLFACTOR wciąż za wysoki), AutoVacuum włącza się za rzadko i nie zdąży usunąć martwych krotek, albo aktualizujesz kolumny, na których założony jest indeks (wtedy HOT z definicji nie zadziała).
Do niskopoziomowej analizy fragmentacji konkretnych 8-kilobajtowych stron warto użyć rozszerzenia pgstattuple. Zwraca ono m.in. free_percent (fizyczny procent pustego miejsca we wszystkich blokach) oraz dead_tuple_percent (poziom "zaśmiecenia" martwymi wersjami).
ALTER TABLE nazwa_tabeli SET (fillfactor = 80);
Aby zaaplikować nowy limit do już istniejących danych, musisz wymusić fizyczne przepisanie tabeli na nowo. Robi się to na dwa sposoby:
- VACUUM FULL nazwa_tabeli; (lub CLUSTER) – system alokuje nowy plik na dysku, przepisuje wiersze zachowując nowy FILLFACTOR i podmienia pliki. Wymaga to jednak blokady typu ACCESS EXCLUSIVE, co całkowicie zamraża tabelę dla aplikacji na czas operacji.
- pg_repack / pg_squeeze – zewnętrzne rozszerzenia, które pozwalają przepisać strukturę binarną tabeli w tle, nie blokując operacji odczytu i zapisu (tworzą tabelę cieni i synchronizują logi triggerami).
Test