Blog JSystems - uwalniamy wiedzę!

Szukaj

PostgreSQL

Stary primary po failoverze nie wraca jako replika - pg_rewind i oś czasu (timeline)

W skrócie

  • Problem: po failoverze dawny primary uruchomiony jako replika w kółko loguje new timeline 2 forked off current database system timeline 1 i nie odtwarza zmian z nowego primary.
  • Dlaczego: przy awansie repliki powstała nowa oś czasu (timeline), a stary primary zdążył zapisać WAL, którego nowy primary nie ma, więc ich historie się rozeszły.
  • Rozwiązanie: cofamy stary primary narzędziem pg_rewind, które wymaga wal_log_hints = on albo sum kontrolnych oraz plików WAL od punktu rozejścia, a gdy warunki nie są spełnione, budujemy replikę od nowa.

Ten problem dotyczy każdego, kto po awarii przełączył zapis na replikę (failover), a teraz chce przywrócić dawny primary do klastra jako serwer zapasowy. Po kontrolowanym przełączeniu czasem wystarczy dopisać standby.signal i primary_conninfo, ale po awarii stary serwer mógł zdążyć coś zapisać po rozdzieleniu i wtedy to nie działa. Odtworzyliśmy to na PostgreSQL 16 i przeszliśmy całą drogę: od komunikatu w logu, przez kolejne błędy pg_rewind, do repliki strumieniującej na nowej osi czasu. Pokażemy też, co w tej sytuacji robi Patroni.

Jak to wygląda w praktyce

Dawny primary dostaje plik standby.signal i primary_conninfo wskazujące nowy primary. Startuje, ale w jego logu w kółko powtarza się:

LOG:  new timeline 2 forked off current database system timeline 1 before current recovery point 0/82137D0
LOG:  started streaming WAL from primary at 0/8000000 on timeline 1
LOG:  replication terminated by primary server
DETAIL:  End of WAL reached on timeline 1 at 0/820F818.

Serwer jest w trybie odtwarzania (pg_is_in_recovery() zwraca t), lecz nie odtwarza niczego z nowego primary. Próba naprawy narzędziem pg_rewind na klastrze utworzonym z domyślnymi ustawieniami kończy się od razu:

pg_rewind: error: target server needs to use either data checksums or "wal_log_hints = on"

Dlaczego tak się dzieje

Przy każdym awansie repliki PostgreSQL podnosi numer osi czasu (timeline), czyli generacji klastra. Numer jest pierwszym członem nazwy pliku WAL, czyli dziennika zmian: po naszym failoverze nowy primary pisał do 000000020000000000000008, a stary dalej do plików z prefiksem 00000001. Plik 00000002.history zapisuje miejsce w WAL, w którym linie się rozdzieliły (u nas 0/820F818). Stary primary przyjął po tym punkcie jeszcze zapisy, więc ma zmiany, których nowy primary nie zna, i nie może go po prostu dogonić.

pg_rewind cofa katalog danych starego primary do punktu rozejścia. Przegląda jego WAL od ostatniego wspólnego checkpointu, notuje zmienione bloki plików danych i kopiuje je z nowego primary, a pozostałe pliki, w tym konfigurację i WAL, przenosi w całości. Zmiany samych bitów podpowiedzi (hint bits) trafiają do WAL wyłącznie przy wal_log_hints = on albo przy sumach kontrolnych stron (initdb --data-checksums), a żadne z nich nie jest domyślnie włączone. wal_log_hints ma kontekst postmaster, więc wymaga restartu i trzeba go włączyć przed awarią. Narzędzie potrzebuje też plików WAL starego primary od tego checkpointu, a nowy primary musi mieć włączone full_page_writes.

Jak to rozwiązać krok po kroku

  1. Zatrzymaj stary primary i upewnij się, że nie przyjmuje zapisów. Zapisy, które przyjął po rozejściu, pg_rewind usunie, więc jeśli mogą być ważne, skopiuj teraz jego katalog danych w bezpieczne miejsce. pg_rewind wymaga czysto zamkniętego klastra docelowego. Gdy serwer padł, narzędzie samo uruchomi go w trybie single-user, żeby dokończyć odtwarzanie po awarii, a potem go zatrzyma.
  2. Sprawdź warunki: pg_controldata /sciezka/pgdata | grep -E 'wal_log_hints|checksum'. Przy wal_log_hints setting: off i Data page checksum version: 0 pg_rewind odmówi pracy. Na nowym primary musi też działać full_page_writes = on (wartość domyślna), inaczej dostaniesz pg_rewind: error: full_page_writes must be enabled in the source server. Gdy wal_log_hints i sumy kontrolne są wyłączone, zbuduj replikę od nowa (pg_basebackup albo patronictl reinit w Patroni) i na przyszłość ustaw wal_log_hints = on na wszystkich węzłach, z restartem.
  3. Na nowym primary przygotuj dostęp: zwykłe (nie replikacyjne) połączenie do bazy postgres jako superuser albo rola z prawem wykonania funkcji pg_ls_dir, pg_stat_file i pg_read_binary_file, plus wpis w pg_hba.conf i SELECT pg_reload_conf();.
  4. Wykonaj próbę na sucho: pg_rewind --target-pgdata=/sciezka/pgdata --source-server='host=NOWY_PRIMARY port=5432 user=postgres dbname=postgres' --restore-target-wal --dry-run. Opcja --restore-target-wal (-c) pobiera brakujące pliki WAL z archiwum przez restore_command ustawiony na starym primary. Bez niej dostaliśmy could not open file ".../pg_wal/000000010000000000000002": No such file or directory, a bez restore_command błąd restore_command is not set in the target cluster. Poprawny wynik kończy się linią pg_rewind: Done!.
  5. Uruchom właściwe przewinięcie: to samo polecenie bez --dry-run, z --write-recovery-conf (-R). pg_rewind utworzy standby.signal i dopisze primary_conninfo do postgresql.auto.conf. Pliki konfiguracyjne kopiuje z nowego primary, więc przed startem przejrzyj postgresql.auto.conf, bo u nas trafiły tam ustawienia właściwe tylko dla tamtego serwera.
  6. Uruchom stary primary jako replikę. Zapisy, które przyjął po rozejściu, przepadły: w naszym teście 100 wierszy wstawionych na starym primary po failoverze zniknęło, a zostały te z nowego. Odzyskasz je tylko z kopii katalogu z kroku 1.
  7. W Patroni ustaw use_pg_rewind: true w konfiguracji dynamicznej (patronictl edit-config), a Patroni sam wywoła pg_rewind, gdy dawny lider dołącza jako replika. Opcja remove_data_directory_on_rewind_failure w sekcji postgresql pozwala mu przy nieudanym przewinięciu usunąć katalog danych i zbudować replikę od nowa.

Jak sprawdzić, że zadziałało

Na dawnym primary, teraz replice, sprawdź tryb pracy i oś czasu, z której odbiera WAL:

shop=# SELECT pg_is_in_recovery();
 pg_is_in_recovery
-------------------
 t
(1 row)

shop=# SELECT status, received_tli FROM pg_stat_wal_receiver;
  status   | received_tli
-----------+--------------
 streaming |            2
(1 row)

received_tli = 2 oznacza odbiór WAL z nowej osi czasu. W logu pojawia się started streaming WAL from primary at ... on timeline 2, a na nowym primary SELECT application_name, state FROM pg_stat_replication; pokazuje tę replikę ze stanem streaming.

Powiązane problemy

Wróć do listy: 112 najczęstszych pytań i problemów z PostgreSQL

Najczęściej zadawane pytania

Czym jest oś czasu (timeline) w PostgreSQL?
To numer generacji klastra, podnoszony przy każdym awansie repliki. Jest pierwszym członem nazwy pliku WAL, a plik .history zapisuje, w którym miejscu WAL nowa linia odgałęziła się od poprzedniej.
Dlaczego wal_log_hints trzeba włączyć przed awarią?
pg_rewind ustala zmienione bloki z WAL starego primary, a zmiany bitów podpowiedzi trafiają do WAL tylko przy wal_log_hints = on lub sumach kontrolnych stron. Parametr ma kontekst postmaster i wymaga restartu, więc ustaw go zawczasu na wszystkich węzłach.
Co się dzieje z danymi zapisanymi na starym primary po failoverze?
pg_rewind cofa katalog danych do punktu rozejścia, więc te zapisy przepadają. W naszym teście zniknęło 100 wierszy dopisanych na starym primary po awansie repliki. Jeśli mogły być ważne, skopiuj katalog danych starego primary, zanim uruchomisz pg_rewind.
Co zrobić, gdy pg_rewind nie znajduje pliku w pg_wal?
Uruchom pg_rewind z opcją --restore-target-wal (-c). Pobierze wtedy brakujące segmenty z archiwum przez restore_command ustawiony na starym primary. Bez restore_command dostaniesz błąd restore_command is not set in the target cluster.
Czy Patroni sam uruchamia pg_rewind?
Tak, gdy w konfiguracji dynamicznej ustawisz use_pg_rewind: true. Warunki są te same co przy ręcznym użyciu, czyli wal_log_hints = on albo sumy kontrolne stron. Przy nieudanym przewinięciu opcja remove_data_directory_on_rewind_failure pozwala odbudować replikę.

Komentarze (0)

Musisz być zalogowany by móc dodać komentarz. Zaloguj się przez Google

Brak komentarzy...