Proces checkpoint jest jednym z kluczowych mechanizmów silnika PostgreSQL, który ma bezpośredni wpływ na stabilność opóźnień, przepustowość I/O oraz czas odtwarzania po awarii (RTO). To właśnie podczas tego procesu (checkpoint) serwer wymusza zapis zmodyfikowanych stron danych do plików relacji i synchronizuje je na urządzeniu dyskowym, ustawiając jednocześnie nowy punkt odniesienia w dzienniku WAL. Niewłaściwie dobrane parametry checkpointów potrafią powodować wyraźne „piki” I/O zapisu, a tym samym degradację czasu odpowiedzi zapytań i operacji modyfikujących dane.
Celem tego artykułu jest przybliżenie mechanizmu checkpoint oraz pokazanie, jak świadomie nim zarządzać w środowiskach produkcyjnych. Tekst porusza kwestie:
- wyjaśnienia zależności między WAL, LSN/redo LSN, „brudnymi” stronami i checkpointem;
- dobrania parametrów checkpointów tak, by wygładzić profil I/O oraz ograniczyć wpływ na czasy odpowiedzi;
- diagnozowania problemów z checkpointami na podstawie twardych metryk;
- stosowania praktycznych procedur weryfikacji zmian konfiguracyjnych na rzeczywistych obciążeniach.
Zaczniemy od definicji LSN i redo LSN, wyjaśnienia pojęcia „brudnych” stron oraz przypomnienia roli buforów współdzielonych (shared_buffers), do których wczytywane są bloki danych z dysku i w których są modyfikowane przed ich utrwaleniem. Następnie omówimy rolę procesów biorących udział w utrwalaniu zmian (checkpointer, walwriter, background writer, backendy), a także warunki wyzwalające checkpoint oraz szczegółowy przebieg operacji wymuszonego zapisu i synchronizacji.
Spis treści:
- Kluczowe pojęcia i mechanizm WAL
- Procesy odpowiedzialne za zapis danych
- Anatomia checkpointu
- Konfiguracja i strojenie
- Monitoring i diagnostyka
- Podsumowanie i rekomendacje
Uwaga
Zakładamy, że przykłady zawarte we wpisie będziemy wykonywali na testowej instancji silnika PostgreSQL (17 lub 18).
Kluczowe pojęcia i mechanizm WAL
Aby w pełni zrozumieć działanie checkpointów, należy najpierw zdefiniować fundamentalne pojęcia związane z mechanizmem trwałości danych w PostgreSQL, czyli Write-Ahead Logging (WAL).
LSN (Log Sequence Number)
LSN to 64-bitowy wskaźnik bajtowej pozycji w strumieniu WAL. W psql prezentowany jest heksadecymalnie jako X/Y, gdzie X to wyższe 32 bity, a Y – niższe 32 bity. LSN rośnie monotonicznie w miarę dopisywania rekordów WAL. Każdy rekord WAL (np. zmiana strony, alokacja, metadane) ma własny LSN, a każda strona relacji (tabela/indeks/TOAST) w nagłówku przechowuje pd_lsn – LSN ostatniej modyfikacji tej strony. LSN jest globalny w obrębie instancji (dla danej linii czasu) i służy do logicznego porządkowania zmian w czasie.
Przykładowe zapytania diagnostyczne (wymaga pageinspect):
-- odczyt bieżącej pozycji w strumieniu wal
select pg_current_wal_lsn();
-- obliczenie, ile bajtów wal przybyło od początku istnienia strumienia
select pg_wal_lsn_diff(pg_current_wal_lsn(), '0/0') as wal_bytes_since_zero;
-- odczyt lsn pierwszej strony tabeli (wymaga rozszerzenia pageinspect)
create extension if not exists pageinspect;
-- utworzenie tabeli testowej (jeżeli nie istnieje)
create table if not exists public.checkpoint_example (
id bigint generated always as identity primary key,
city text not null
);
-- wstawienie rekordu referencyjnego tylko wtedy, gdy go jeszcze nie ma
insert into public.checkpoint_example (city)
select 'Poznań'
where not exists (
select 1 from public.checkpoint_example where city = 'Poznań' limit 1
);
-- pg_lsn na stronie/page 0
select lsn
from page_header(get_raw_page('public.checkpoint_example', 0));
redo LSN
redo LSN to pozycja w WAL, od której serwer rozpocznie REDO (zastosowanie zapisanych zmian) po restarcie lub awarii, aby przywrócić spójność plików danych. Wartość redo lsn jest zapisywana w rekordzie checkpointu. Po zakończeniu checkpointu PostgreSQL gwarantuje, że wszystkie strony danych, których modyfikacje mają LSN ≤ redo lsn, są trwale zapisane i zsynchronizowane na urządzeniu dyskowym. Dzięki temu po awarii REDO zaczyna się od redo lsn, co skraca czas odtwarzania (RTO).
Przykładowe zapytania diagnostyczne:
-- odczyt informacji o ostatnim checkpoincie, w tym redo_lsn
select * from pg_control_checkpoint();
-- w przypadku gdy zestawiliśmy replikację, weryfikacja stanu replikacji na serwerze standby
select pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn(), pg_is_in_recovery();
„Brudne” strony (dirty pages)
Strona w shared_buffers jest „brudna”, gdy jej zawartość w pamięci jest nowsza niż odpowiadający jej blok na dysku. Oznacza to, że modyfikacja nie została jeszcze zapisana do pliku relacji i zsynchronizowana (fsync) na urządzeniu dyskowym. Duża pula brudnych stron w połączeniu z niskim checkpoint_completion_target może prowadzić do pików I/O zapisu i wzrostu opóźnień podczas checkpointu.
Przykładowe zapytanie diagnostyczne (wymaga pg_buffercache):
-- weryfikacja liczby brudnych stron dla konkretnej tabeli
create extension if not exists pg_buffercache;
with t as (
-- pobranie informacji o buforach dla wskazanej relacji
select *
from pg_buffercache
where relfilenode = pg_relation_filenode('public.checkpoint_example'::regclass)
and reldatabase = (select oid from pg_database where datname = current_database())
)
select
count(*) filter (where isdirty) as dirty_buffers,
count(*) as total_buffers
from t;
Bloki danych w RAM (shared_buffers)
shared_buffers to współdzielona pula pamięci, w której PostgreSQL buforuje strony tabel i indeksów. Przyspiesza to odczyty (cache trafień) i pozwala wykonywać modyfikacje w pamięci, a fizyczny zapis rozłożyć w czasie. Strony czyste mogą być wyparte bez operacji I/O, natomiast strony brudne przed wyparciem wymagają zapisu do pliku. Checkpoint gwarantuje, że wszystkie wymagane strony (te o LSN ≤ redo lsn) będą zapisane i zsynchronizowane na dysku.
Mechanizm WAL i dwuetapowe utrwalanie
Mechanizm WAL (Write-Ahead Logging) zapewnia, że najpierw rejestrowany jest opis modyfikacji (rekord WAL), a dopiero potem odpowiednie strony danych trafiają do plików relacji.
- Rejestracja zmiany w WAL: Backend generuje rekordy WAL, które trafiają do wal_buffers, a następnie są zapisywane i synchronizowane w plikach WAL. Zapis do WAL jest sekwencyjny, co zapewnia niski koszt I/O (ściślej mówiąc, powinien być niski, co – w przypadku SSD/NVME – nie zawsze musi być zgodne z prawdą).
- Aktualizacja plików danych: Zmodyfikowane strony danych w shared_buffers są zapisywane do plików tabel i indeksów w dogodnym momencie, niekoniecznie w chwili wykonania commit.
Dzięki temu commit nie musi czekać na losowe zapisy do wielu plików danych – wystarczy zapis i synchronizacja odpowiednich rekordów WAL.
Poniższy diagram ilustruje przepływ danych:
[Zmiana w transakcji]
|
v
(1) Zapis rekordu do WAL ------------------------------+
-> nadany LSN (X/Y) |
| |
v |
(2) Modyfikacja strony w shared_buffers |
-> strona staje się „brudna” |
-> nagłówek strony: pd_lsn = LSN zmiany |
| |
v |
(3) Zapis strony do pliku relacji (tabela/indeks) |
| |
v |
(4) fsync zmodyfikowanych plików <---------------------+
| (gwarancja trwałości na urządzeniu)
v
(5) CHECKPOINT
-> zapis rekordu checkpointu do WAL
-> wyznaczenie redo LSN
-> gwarancja: wszystkie strony o LSN ≤ redo LSN są trwale zapisane i zsynchronizowane
Ścieżka commit i synchronous_commit
Proces walwriter okresowo zapisuje bufory WAL do plików, ograniczając liczbę operacji fsync inicjowanych przez backendy. Mechanizm Group commit pozwala wielu transakcjom współdzielić jedną operację fsync, co stabilizuje opóźnienie/czas odpowiedzi. Parametr synchronous_commit kontroluje moment, w którym commit jest uznawany za trwały:
- on (domyślnie): commit zwraca sterowanie po zapisie i synchronizacji WAL do odpowiedniego LSN;
- off: commit nie czeka na synchronizację WAL, co niesie ryzyko utraty ostatnich transakcji w razie awarii.
Struktura i archiwizacja WAL
WAL jest podzielony na segmenty (typowo 16 MiB). Stare segmenty mogą być recyklingowane (ponownie używane) do poziomu min_wal_size. Archiwizacja (archive_mode = on, archive_command) kopiuje segmenty WAL do zewnętrznego repozytorium, co jest wymagane dla strategii odtwarzania do punktu w czasie (PITR).
Pełny obraz strony (FPI) i full_page_writes
Gdy full_page_writes = on (zalecane w produkcji), pierwsza modyfikacja strony po checkpoincie powoduje dopisanie do WAL pełnego obrazu strony (FPI). FPI chroni przed skutkami częściowego zapisu strony („torn page”) w razie awarii. Rzadsze checkpointy oznaczają mniej generowanych FPI dla tych samych stron, ale potencjalnie dłuższy proces REDO.
Parametr wal_compression = on może zredukować koszt FPI kosztem większego użycia CPU.
Poniższy diagram ilustruje przepływ danych:
[Po zakończeniu CHECKPOINT]
|
v
Pierwsza modyfikacja danej strony
po checkpoincie (strona „nowa” w cyklu)
|
v
(1) WAL: zapis rekordu modyfikacji + FPI
-> FPI = Full Page Image (pełny obraz strony)
-> nadany LSN (X/Y)
|
v
(2) Strona w shared_buffers staje się „brudna”
-> pd_lsn = LSN zmiany
|
v
(3) Zapis strony do pliku relacji (później, w tle)
|
v
(4) fsync zmodyfikowanych plików
|
v
(5) Kolejny CHECKPOINT
-> wyznacza nowe redo LSN
Procesy odpowiedzialne za zapis danych
W PostgreSQL utrwalanie zmian to koordynacja pracy czterech typów procesów. Każdy z nich ma inną rolę, częstotliwość działania i cel.
walwriter – zapis i synchronizacja WAL
Periodycznie zapisuje bufory WAL (wal_buffers) do plików WAL oraz – zgodnie z polityką trwałości – synchronizuje je na urządzeniu dyskowym. Dzięki temu backendy rzadziej muszą same inicjować zapis/synchronizację WAL przy operacji commit. Jego pracą sterują parametry wal_writer_delay i wal_writer_flush_after.
checkpointer – checkpointy, fsync i redo LSN
W czasie checkpointu zapisuje rekord checkpointu do WAL, wyznacza redo LSN, zapisuje wszystkie „brudne” strony danych o LSN ≤ redo LSN i wykonuje fsync na wszystkich zmodyfikowanych plikach. Działanie checkpointera jest kluczowe dla ograniczenia zakresu REDO po awarii (krótsze RTO). Nieprawidłowo skonfigurowane parametry mogą prowadzić do pików I/O i długich czasów fsync.
background writer – odciążanie checkpointu w tle
W tle zapisuje część „brudnych” stron z shared_buffers (tzw. LRU-cleaning). Jego celem jest zmniejszenie liczby stron wymagających zapisu w chwili checkpointu, co pozwala zredukować skoki I/O i poprawić przewidywalność opóźnień. Jeśli w momencie checkpointu większość stron została już zapisana przez background writera, sam checkpoint trwa krócej i ma łagodniejszy profil I/O.
Backendy – zapisy incydentalne i pod presją
Procesy backendowe obsługujące zapytania standardowo wymuszają zapisy WAL. Czasami mogą również wymusić zapis strony relacji, gdy muszą zwolnić bufor z powodu braku „czystych” buforów lub gdy background writer nie nadąża. Zapisy inicjowane przez backend w krytycznych sekcjach zapytania mogą zwiększyć jego czas odpowiedzi. Celem jest, aby większość pracy była wykonywana w tle, a nie w wątku obsługującym użytkownika.
Anatomia checkpointu
Definicja checkpointu
Checkpoint to zdarzenie synchronizacji rejestrowane w dzienniku WAL, które wyznacza nowy redo LSN i wymusza zapis oraz synchronizację na dysku wszystkich stron danych, których modyfikacje mają LSN nie większy niż redo LSN. Skutkiem finalizacji checkpointu jest gwarancja, że stan plików danych na dysku odzwierciedla wszystkie zmiany do poziomu redo LSN. Stanowi to punkt równowagi pomiędzy czasem odtwarzania a kosztem I/O podczas normalnej pracy.
Warunki wyzwalające checkpoint
Checkpoint jest wykonywany w następujących sytuacjach:
- Na żądanie administracyjne – po wywołaniu polecenia checkpoint.
- Z interwału czasu – po upływie wartości checkpoint_timeout.
- Z przyrostu wolumenu WAL – gdy od poprzedniego checkpointu wygenerowano co najmniej max_wal_size.
- W zdarzeniach operacyjnych – m.in. rozpoczęcie/zakończenie tworzenia kopii zapasowej.
- Podczas kontrolowanego wyłączenia instancji.
- W wyniku wybranych operacji DDL (np.: CREATE/DROP DATABASE, ALTER DATABASE … SET TABLESPACE, CLUSTER, VACUUM FULL ).
Na replikach fizycznych wykonywane są restartpointy, które są odpowiednikiem checkpointu po stronie silnika standby.
Przebieg operacji checkpointu
- Zapis rekordu checkpointu w WAL i wyznaczenie redo LSN.
- Identyfikacja „brudnych” stron w shared_buffers o LSN ≤ redo LSN.
- Zapis stron do plików relacji (tabele, indeksy, struktury pomocnicze).
- Wymuszenie zapisu na urządzeniu dyskowym – wykonanie fsync() na wszystkich plikach, które uległy modyfikacji.
- Finalizacja – aktualizacja metadanych oraz udostępnienie metryk w widokach statystycznych.
Konfiguracja i strojenie
Prawidłowe działanie checkpointów zależy od świadomego doboru kilku kluczowych parametrów konfiguracyjnych.
checkpoint_timeout
Maksymalny dopuszczalny odstęp czasu między automatycznymi checkpointami (domyślnie 300 s). Zwiększanie wartości zmniejsza częstość checkpointów „z czasu”, ale wydłuża potencjalny zakres REDO.
max_wal_size oraz min_wal_size
- max_wal_size: górna granica przyrostu WAL pomiędzy checkpointami (domyślnie 1 GB). Zbyt niska wartość powoduje częste checkpointy wyzwalane rozmiarem WAL.
- min_wal_size: minimalna pula segmentów WAL utrzymywana w celu recyklingu (domyślnie 80 MB). Zbyt niska wartość zwiększa rotację plików WAL.
checkpoint_completion_target
Udział (w zakresie od 0.0 do 1.0) interwału (checkpoint_timeout), w którym powinna zostać rozłożona praca zapisu stron w ramach checkpointu. Wartości rzędu 0.7–0.8 często stanowią korzystny punkt startowy, ponieważ ograniczają skoki I/O.
checkpoint_flush_after
Wartość w stronach (8 kB). Po zapisaniu co najmniej tylu stron silnik prosi system operacyjny o niezwłoczne zrealizowanie tych zapisów na urządzeniu dyskowym. Ogranicza to kumulację danych w pamięci podręcznej jądra i skraca czas końcowego fsync. Wartość 0 wyłącza tę funkcję.
wal_buffers
Rozmiar buforów dla danych WAL w pamięci współdzielonej. Ustawienie -1 powoduje automatyczny dobór (około 1/32 shared_buffers), z ograniczeniem do maksymalnie jednego segmentu WAL (zwykle 16 MB). W trybie ustawienia „ręcznego” wartość parametru może być zwiększona powyżej 16 MB.
checkpoint_warning
Jeżeli wartość jest większa od 0, serwer rejestruje w dzienniku ostrzeżenie, gdy checkpointy wymuszane rozmiarem WAL występują częściej niż wskazany próg (w sekundach).
Parametry konfiguracji powiązane
- full_page_writes = on: rekomendowane w środowiskach produkcyjnych; wpływa na wolumen WAL i bezpieczeństwo;
- wal_compression = on: kompresja FPI; korzystna przy ograniczeniach I/O i dostępnych zasobach CPU;
- bgwriter_lru_*: parametry sterujące zachowaniem background writera, pośrednio wpływające na pracę checkpointu.
Ryzykowne ustawienia wal_buffers
Ustawienie automatyczne: wal_buffers = -1 (tryb domyślny) powoduje, że silnik PostgreSQL samodzielnie dobiera jego wartość. Robi to na podstawie aktualnego rozmiaru shared_buffers:
- sposób obliczania: wartość jest wyliczana jako około 1/32 rozmiaru shared_buffers;
- górny limit: w automatycznym trybie istnieje sztywny, wbudowany w kod źródłowy limit. Wartość dobrana automatycznie nigdy nie przekroczy rozmiaru jednego segmentu WAL, czyli zazwyczaj 16 MB.
Celem tego limitu jest zapewnienie bezpiecznego i wydajnego działania dla większości typowych obciążeń. Uznaje się, że 16 MB jest w zupełności wystarczające, aby efektywnie buforować zapisy i korzystać z mechanizmu group commit, bez ryzyka związanego ze zbyt dużym buforem w pamięci.
Ustawienie manualne: wal_buffers =
- ekstremalnie wysoka intensywność zapisu: dotyczy to systemów, które generują olbrzymią ilość rekordów WAL w bardzo krótkim czasie. Przykłady to masowe ładowanie danych (bulk loading) lub potężne, pojedyncze transakcje modyfikujące miliony wierszy;
- cel: większy bufor pozwala „wchłonąć” nagłe skoki generowania WAL bez konieczności natychmiastowego, ciągłego opróżniania bufora na dysk. Może to zredukować liczbę operacji I/O i poprawić szczytową przepustowość zapisu.
Jakie są zagrożenia i kompromisy?
Zwiększenie wal_buffers to kompromis, który niesie ze sobą istotne ryzyka:
- zwiększone ryzyko utraty danych: to najważniejsza konsekwencja. W przypadku nagłej awarii serwera (np. zanik zasilania, awaria systemu operacyjnego) wszystkie dane znajdujące się w wal_buffers, które nie zostały jeszcze zapisane na dysk, zostaną bezpowrotnie utracone. Jeśli bufor ma rozmiar 256 MB, potencjalnie można stracić transakcje o tej objętości, które z perspektywy klienta mogły już zostać zatwierdzone (szczególnie przy synchronous_commit = off);
- zużycie pamięci współdzielonej: większy bufor WAL to również mniej pamięci dostępnej dla shared_buffers, gdzie przechowywane są strony danych.
Reasumując
| Ustawienie | Jak działa | Kiedy stosować | Główne zalety / wady |
| wal_buffers = -1 (Auto) | Obliczany z shared_buffers, limitowany do 16 MB | Zalecane dla 99% standardowych wdrożeń. | Bezpieczeństwo: minimalizuje ryzyko utraty danych. Optymalny balans: dobra wydajność dla większości obciążeń. |
| wal_buffers > 16MB (Manualnie) | Ustawia bufor na jawną, dużą wartość, np. 256 MB. | Tylko w systemach o ekstremalnych, szczytowych zapisach, po dokładnej analizie i testach. | Ryzyko: znacząco zwiększa ilość danych, które można stracić przy awarii. Wydajność w szczycie: może poprawić przepustowość przy masowych zapisach. |
Monitoring i diagnostyka
Skuteczne monitorowanie I/O, w tym działania checkpointera, opiera się na kilku widokach statystycznych, które w wersjach 17 i 18 silnika zostały rozbudowane.
Kluczowe widoki
- pg_stat_checkpointer: liczniki i czasy pracy checkpointera;
- pg_stat_bgwriter: aktywność background writera;
- pg_stat_io: szczegółowy profil I/O w podziale na: typ procesu, obiekt i kontekst;
- pg_stat_wal: statystyki produkcji WAL (liczba rekordów, FPI, łączny wolumen).
Raport dla PostgreSQL 17
-- raport zbiorczy dla pg 17, łączący dane z checkpointera, bgwritera i i/o
with
chk as (
-- dane o aktywności checkpointera
select
num_timed as checkpoints_timed,
num_requested as checkpoints_req,
restartpoints_timed,
restartpoints_req,
restartpoints_done,
round((write_time/1000.0)::numeric, 2) as checkpoint_write_time_s,
round((sync_time/1000.0)::numeric, 2) as checkpoint_sync_time_s,
buffers_written
from pg_stat_checkpointer
),
bgw as (
-- dane o aktywności background writera
select
buffers_clean,
maxwritten_clean,
buffers_alloc
from pg_stat_bgwriter
),
cio_chk as (
-- dane i/o dla checkpointera (wolumen zapisu obliczany z op_bytes)
select
sum(writes) as writes_total,
(sum(writes * op_bytes))::numeric as write_bytes_total,
round((sum(write_time)/1000.0)::numeric, 2) as write_time_s,
sum(fsyncs) as fsyncs_total,
round((sum(fsync_time)/1000.0)::numeric, 2) as fsync_time_s
from pg_stat_io
where backend_type = 'checkpointer'
)
select * from chk, bgw, cio_chk;
Gdzie?
- Kolumny z pg_stat_checkpointer (CTE: chk) – sekcja opisuje ogólną aktywność procesu checkpointer od ostatniego resetu statystyk.
| Kolumna | Opis |
| checkpoints_timed | Liczba checkpointów wywołanych z powodu upłynięcia czasu (checkpoint_timeout). W idealnie skonfigurowanym systemie ta wartość powinna być dominująca. |
| checkpoints_req | Liczba checkpointów wywołanych z powodu zapełnienia przestrzeni WAL (max_wal_size). Wysoka wartość tej metryki jest sygnałem do rozważenia zwiększenia max_wal_size. |
| restartpoints_timed | Liczba restartpointów na replice wywołanych z powodu upłynięcia czasu (checkpoint_timeout). |
| restartpoints_req | Liczba restartpointów na replice wywołanych z powodu zapełnienia przestrzeni WAL (max_wal_size). |
| restartpoints_done | Liczba ukończonych restartpointów na replice. |
| checkpoint_write_time_s | Łączny czas w sekundach spędzony przez checkpointer na zapisywaniu plików na dysk. Czas ten nie obejmuje operacji synchronizacji (fsync). |
| checkpoint_sync_time_s | Łączny czas w sekundach spędzony przez checkpointer na synchronizacji plików z dyskiem. (operacje fsync). Wysoka wartość wskazuje na potencjalne problemy z I/O. |
| buffers_written | Łączna liczba buforów (stron) zapisanych na dysk w trakcie checkpointów.. Ta metryka pokazuje, jak dużą pracę wykonuje sam checkpointer. |
- Kolumny z pg_stat_bgwriter (CTE: bgw) – sekcja dostarcza informacji o aktywności procesu background writer, którego zadaniem jest odciążanie procesu checkpointer.
| Kolumna | Opis |
| buffers_clean | Liczba buforów (stron) zapisanych na dysk przez background writer Porównanie tej wartości z buffers_written z pg_stat_checkpointer pokazuje, jak skutecznie bgwriter odciąża checkpoint. |
| maxwritten_clean | Liczba zatrzymań procesu background writer, ponieważ zapisał on już maksymalną liczbę stron dozwoloną w jednym cyklu (bgwriter_lru_maxpages). Wartość różna od zera jest normalnym zjawiskiem. |
| buffers_alloc | Liczba buforów zaalokowanych bezpośrednio z dysku. W idealnym scenariuszu backendy powinny znajdować wolne bufory w pamięci, a ta wartość powinna być niska. |
- Kolumny z pg_stat_io (CTE: cio_chk) sekcja zawiera szczegółowe i niskopoziomowe metryki operacji wejścia/wyjścia (I/O) wykonanych wyłącznie przez proces checkpointer.
| Kolumna | Opis |
| writes_total | Łączna liczba operacji zapisu zainicjowanych przez checkpointer na poziomie systemu operacyjnego. |
| write_bytes_total | Całkowita liczba bajtów zapisanych na dysk przez checkpointer. Jest to najbardziej precyzyjna miara wolumenu danych przetworzonych podczas checkpointów. |
| write_time_s | Łączny czas w sekundach spędzony na wykonywaniu operacji zapisu. Wartość ta powinna być zbliżona do checkpoint_write_time_s z pg_stat_checkpointer. |
| fsyncs_total | Łączna liczba operacji synchronizacji (fsync) wykonanych przez checkpointer. |
| fsync_time_s | Łączny czas w sekundach spędzony na wykonywaniu operacji fsync. Wartość ta powinna być zbieżna z checkpoint_sync_time_s z pg_stat_checkpointer. Wysoka wskazuje na wolny podsystem I/O. |
Raport dla PostgreSQL 18
-- raport zbiorczy dla pg 18, z nowymi kolumnami w pg_stat_checkpointer i pg_stat_io
with
chk as (
-- dane o aktywności checkpointera (z num_done i slru_written)
select
num_timed as checkpoints_timed,
num_requested as checkpoints_req,
num_done as checkpoints_done,
restartpoints_timed,
restartpoints_req,
restartpoints_done,
round((write_time/1000.0)::numeric, 2) as checkpoint_write_time_s,
round((sync_time/1000.0)::numeric, 2) as checkpoint_sync_time_s,
buffers_written,
slru_written
from pg_stat_checkpointer
),
bgw as (
-- dane o aktywności background writera
select
buffers_clean,
maxwritten_clean,
buffers_alloc
from pg_stat_bgwriter
),
cio_chk as (
-- dane i/o dla checkpointera (z bezpośrednią kolumną write_bytes)
select
sum(writes) as writes_total,
sum(write_bytes) as write_bytes_total,
round((sum(write_time)/1000.0)::numeric, 2) as write_time_s,
sum(fsyncs) as fsyncs_total,
round((sum(fsync_time)/1000.0)::numeric, 2) as fsync_time_s
from pg_stat_io
where backend_type = 'checkpointer'
)
select * from chk, bgw, cio_chk;
Gdzie opis poszczególnych kolumn wynikowych jest bardzo podobny do wersji 17?
1. Dodatkowe kolumny z pg_stat_checkpointer (CTE: chk)
| Kolumna | Opis |
| (jak w wersji 17) | … |
| slru_written | (Nowość w PG18) Łączna liczba buforów (stron) zapisanych dla struktur SLRU (Simple LRU). Struktury te przechowują metadane transakcji (np. pg_xact, pg_commit_ts) i ich zapis jest częścią checkpointu. |
2. Kolumny z pg_stat_bgwriter (CTE: bgw) – jak w wersji 17.
3. Kolumny z pg_stat_io (CTE: cio_chk) – jak w wersji 17, przy czym PostgreSQL 18 przechowuje obliczone wartości (np.: write_bytes w pg_stat_io), co jest dokładniejsze niż wcześniejsze obliczanie/szacowanie.
Raport dotyczący produkcji WAL: silnik w wersji 17/18
Raport dostarcza kluczowych informacji o wolumenie i charakterystyce generowanego dziennika transakcyjnego (WAL):
-- raport dotyczący produkcji wal
select
wal_records,
wal_fpi,
wal_bytes,
to_char(stats_reset, 'yyyy-mm-dd hh24:mi:ss') as stats_reset
from pg_stat_wal;
Gdzie?
| Kolumna | Opis |
| wal_records | Całkowita liczba rekordów WAL wygenerowanych od ostatniego resetu statystyk. Daje ogólne pojęcie o intensywności operacji modyfikujących dane. |
| wal_fpi | Liczba rekordów WAL zawierających pełne obrazy stron (Full Page Images). Wysoka wartość może wskazywać na częste modyfikacje nowych stron po checkpointach. Warto wtedy rozważyć włączenie wal_compression. |
| wal_bytes | Całkowity wolumen (w bajtach) wygenerowanych danych WAL. Jest to kluczowa metryka do oceny obciążenia I/O związanego z zapisem dziennika transakcyjnego i do strojenia parametru max_wal_size. |
| stats_reset | Znacznik czasowy ostatniego resetu staty dla widoku pg_stat_wal. Pozwala odnieść zebrane wartości do konkretnego przedziału czasowego. |
Przykładowe wnioski diagnostyczne z raportów
- checkpoints_req > checkpoints_timed: należy rozważyć zwiększenie max_wal_size;
- wysokie checkpoint_sync_time_s sugeruje problemy z warstwą I/O. Należy zweryfikować nośnik, system plików i rozważyć użycie checkpoint_flush_after. Można również rozważyć rozłożenie WAL, plików relacji oraz wykorzystanie tablespaces na wielu dedykowanych urządzeniach „dyskowych”;
- niski buffers_clean: może wskazywać, że background writer nie nadąża z czyszczeniem buforów i konieczny jest tuning parametrów zarządzających procesem;
- wysoka wartość wal_fpi: warto rozważyć włączenie wal_compression = on.
Reset liczników
Do pomiarów w określonym oknie czasowym można zresetować statystyki, odpowiednio:
-- resetujemy na przykład liczniki m.in. pg_stat_checkpointer, pg_stat_bgwriter i pg_stat_io
select pg_stat_reset_shared('bgwriter');
select pg_stat_reset_shared('checkpointer');
select pg_stat_reset_shared('io');
Studium przypadku: zobaczyć checkpoint
Poniższy przykład ilustruje cykl życia „brudnej” strony oraz efekt działania procesu. Do analizy, wymaga zainstalowania rozszerzenia pg_buffercache, co nie powinno stanowić problemu, bo używamy instancji testowej silnika w wersjach: 17 i 18.
Przygotowanie środowiska i danych
Tworzymy rozszerzenie, tabele i dokonujemy pierwszego zapisu:
-- instalacja rozszerzenia pg_buffercache, jeśli nie istnieje
create extension if not exists pg_buffercache;
-- utworzenie tabeli testowej, jeśli nie istnieje
create table if not exists public.checkpoint_example (
id bigint generated always as identity primary key,
city text not null
);
-- wstawienie rekordu referencyjnego, jeśli tabela jest pusta
insert into public.checkpoint_example (city)
select 'Warszawa'
where not exists (
select 1 from public.checkpoint_example where city = 'Warszawa' limit 1
);
Modyfikacja danych i powstanie „brudnej” strony
Aktualizujemy dane, przenosząc stolicę do Krakowa:
-- aktualizacja rekordu, co powoduje „ubrudzenie” strony w shared_buffers
update public.checkpoint_example
set city = 'Kraków'
where city = 'Warszawa';
commit;
Weryfikacja stanu w buforach przed checkpointem
-- sprawdzenie, ile buforów dla danej tabeli jest oznaczonych jako „brudne”
with r as (
-- pobranie identyfikatorów relacji i bazy danych
select pg_relation_filenode('public.checkpoint_example'::regclass) as relfilenode,
(select oid from pg_database where datname = current_database()) as db_oid
),
t as (
-- odfiltrowanie buforów dla naszej tabeli
select b.isdirty
from pg_buffercache b
join r on r.relfilenode = b.relfilenode and r.db_oid = b.reldatabase
)
select
count(*) filter (where isdirty) as dirty_buffers,
count(*) as total_buffers
from t;
Oczekiwany wynik dirty_buffers powinien być większy od zera:
dirty_buffers | total_buffers
---------------+---------------
1 | 1
(1 row)
Wykonanie checkpointu i ponowna weryfikacja
Ręczne wyzwolenie checkpointu w celu zapisania zmian na dysku:
-- ręczne wyzwolenie checkpointu w celu zapisania zmian na dysku
checkpoint;
CHECKPOINT
Ponowne sprawdzenie stanu buforów po checkpoincie:
-- ponowne sprawdzenie stanu buforów po checkpoincie
with r as (
-- pobranie identyfikatorów relacji i bazy danych
select pg_relation_filenode('public.checkpoint_example'::regclass) as relfilenode,
(select oid from pg_database where datname = current_database()) as db_oid
),
t as (
-- odfiltrowanie buforów dla naszej tabeli
select b.isdirty
from pg_buffercache b
join r on r.relfilenode = b.relfilenode and r.db_oid = b.reldatabase
)
select
count(*) filter (where isdirty) as dirty_buffers_after,
count(*) as total_buffers_after
from t;
Po wykonaniu checkpointu, liczba dirty_buffers_after powinna spaść do zera (w izolowanym środowisku), ponieważ strony zostały zapisane i zsynchronizowane:
dirty_buffers_after | total_buffers_after
---------------------+---------------------
0 | 1
Podsumowanie i rekomendacje
- LSN porządkuje zmiany; redo lsn wyznacza minimalny punkt startowy REDO po awarii.
- „Brudne” strony to zmodyfikowane, jeszcze nieutrwalone bloki w pamięci, które muszą zostać zapisane i zsynchronizowane na dysku.
- Checkpoint zapewnia, że wszystkie modyfikacje do redo lsn są trwale na dysku. Jego częstotliwość to kompromis między RTO a kosztem I/O podczas pracy.
- Celem konfiguracji parametrów związanych z checkpointem jest osiągnięcie stanu, w którym checkpointy są wyzwalane czasowo, a nie z powodu zapełnienia przestrzeni WAL.
Rekomendacje konfiguracyjne
- Stabilizacja rozmiaru WAL: należy dążyć do tego, by typowy checkpoint był wyzwalany z czasu. max_wal_size powinno być ustawione powyżej przeciętnego przyrostu WAL w interwale checkpoint_timeout (np. 1.5–3× przyrost w 5 min).
- Wygładzanie I/O checkpointu: checkpoint_completion_target na poziomie 0.7–0.8 pozwala rozłożyć zapis w czasie. Można również rozważyć ustawienie checkpoint_flush_after na większą wartość, np. 64–256 stron.
- Kontrola FPI i wolumenu WAL: full_page_writes = on jest obligatoryjne w systemach produkcyjnych. wal_compression = on jest zalecane, gdy CPU na to pozwala, a zapis WAL jest wąskim gardłem.
- Monitoring: jeżeli checkpoints_req > checkpoints_timed, należy zwiększyć max_wal_size. Długie czasy checkpoint_sync_time wymagają analizy warstwy I/O.
- Bufory: shared_buffers należy dobrać do dostępnej pamięci RAM i profilu obciążenia; wal_buffers = -1 (ustawienie automatyczne) jest zazwyczaj wystarczające.
