Blog JSystems - uwalniamy wiedzę!

Szukaj

PostgreSQL

Standby nie nadąża za primary - jak zmierzyć i zmniejszyć lag

W skrócie

  • Serwer zapasowy (standby) pokazuje dane starsze niż primary - raporty czytane z repliki zwracają nieaktualne wyniki, a przy awarii primary grozi nam utrata świeżych transakcji.
  • Standby dostaje strumień WAL od primary i musi go u siebie odtworzyć; opóźnienie (lag) bierze się z wolnej sieci, przeciążonego dysku repliki albo z tego, że pojedynczy proces odtwarzający nie wyrabia.
  • Mierzymy lag zapytaniem na primary, znajdujemy wąskie gardło (sieć, zapis, konflikt z długimi zapytaniami) i dobieramy lek: szybszy transfer, hot_standby_feedback, więcej mocy na odtwarzanie albo replikę synchroniczną.

Replika strumieniowa powinna deptać primary po piętach. Gdy zaczyna zostawać w tyle, trzeba najpierw dobrze zmierzyć opóźnienie, a dopiero potem je zbijać. Pokazujemy jedno i drugie.

Jak to wygląda w praktyce

Kierujemy zapytania raportowe na standby, żeby odciążyć primary. Po jakimś czasie użytkownicy zgłaszają, że raport nie widzi zamówienia sprzed dwóch minut, choć na produkcji już jest. Albo monitoring pokazuje, że opóźnienie repliki rośnie w godzinach szczytu do kilkudziesięciu sekund, a nocą przy dużym imporcie skacze do kilku minut. W skrajnym przypadku standby po prostu przestaje nadążać na stałe i dystans tylko się powiększa. To realny problem nie tylko dla świeżości raportów - jeśli teraz padnie primary, przełączymy się na replikę, która nie zdążyła zastosować ostatnich transakcji, i te dane przepadną.

Dlaczego tak się dzieje

W replikacji strumieniowej primary wysyła standby ciągły strumień rekordów WAL. Na replice dzieje się to w dwóch etapach: proces walreceiver odbiera i zapisuje WAL na dysk, a proces startup te rekordy odtwarza w plikach danych. Lag może powstać na każdym z tych etapów. Najczęstsze przyczyny to: za wąskie albo obciążone łącze między serwerami, wolniejszy dysk na replice niż na primary (odtwarzanie to głównie zapisy losowe), pojedynczy wątek odtwarzający, który nie wyrabia przy nawale zmian, oraz konflikty odtwarzania - gdy na standby trwa długie zapytanie, a napływający WAL chce zmienić właśnie czytane wiersze, PostgreSQL musi wstrzymać odtwarzanie albo przerwać zapytanie. Warto rozróżnić trzy rodzaje opóźnienia: write (ile WAL dotarło), flush (ile trwale zapisano) i replay (ile faktycznie zastosowano) - dla świeżości danych liczy się replay.

Jak to rozwiązać krok po kroku

  1. Najpierw mierzymy. Na primary sprawdzamy stan każdej repliki: SELECT client_addr, state, sent_lsn, replay_lsn, pg_wal_lsn_diff(sent_lsn, replay_lsn) AS bajty_zaleglosci, write_lag, flush_lag, replay_lag FROM pg_stat_replication;. Kolumny *_lag to gotowe opóźnienia w jednostkach czasu.
  2. Na samym standby czas opóźnienia policzymy tak: SELECT now() - pg_last_xact_replay_timestamp() AS opoznienie_replay;. To najuczciwsza miara tego, jak stare dane widzi replika.
  3. Ustalamy wąskie gardło. Jeśli sent_lsn prawie równa się replay_lsn, ale opóźnienie i tak rośnie, wina leży po stronie sieci lub nadążania primary z wysyłką. Jeśli WAL dociera (write blisko), a replay zostaje - hamuje odtwarzanie na replice (wolny dysk lub konflikty).
  4. Przy wolnej sieci włączamy kompresję WAL na primary (wal_compression = on) i sprawdzamy przepustowość łącza; przy replikacji przez WAN rozważamy replikację kaskadową lub bliższą lokalizację.
  5. Przy konfliktach z długimi zapytaniami na standby włączamy hot_standby_feedback = on - replika informuje primary, jakie wiersze są jej potrzebne, dzięki czemu vacuum ich nie usuwa i odtwarzanie nie musi się zatrzymywać. Ostrożnie: to może zwiększać bloat na primary.
  6. Przy wolnym odtwarzaniu dajemy replice szybszy dysk i podnosimy max_standby_streaming_delay, jeśli świadomie godzimy się na chwilowo starsze dane w zamian za nieprzerywanie zapytań. Rozważamy też prefetch WAL (recovery_prefetch = on w nowszych wersjach), który przyspiesza odtwarzanie.
  7. Jeśli absolutnie nie możemy tracić transakcji przy awarii, przełączamy wybraną replikę w tryb synchroniczny: na primary ustawiamy synchronous_standby_names. Wtedy commit czeka na potwierdzenie od standby - lag danych znika, ale rośnie czas zatwierdzania.

Jak sprawdzić, że zadziałało

Po zmianach obserwujemy w czasie replay_lag z pg_stat_replication oraz now() - pg_last_xact_replay_timestamp() na standby - powinny zejść do wartości poniżej sekundy i przestać rosnąć w szczycie. Prosty test: na primary wykonujemy SELECT pg_current_wal_lsn();, zapisujemy wynik, a na replice odpytujemy SELECT pg_last_wal_replay_lsn(); - obie wartości powinny się prawie natychmiast zrównać. Dla repliki synchronicznej kolumna sync_state w pg_stat_replication pokaże sync, a opóźnienie replay będzie praktycznie zerowe. Warto podpiąć te metryki pod stały monitoring, bo lag potrafi wrócić przy zmianie profilu obciążenia.

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

Szkolenie Administracja, replikacja i tuning baz danych PostgreSQL

Sprawdź szkolenie: Administracja, replikacja i tuning baz danych PostgreSQL

To szkolenie może być dofinansowane z KFS lub BUR.

★★★★★Średnia ocena naszych szkoleń w Google: 5/5

Szkolenie Zaawansowana administracja PostgreSQL - HA, DR, monitoring, skalowanie

Sprawdź szkolenie: Zaawansowana administracja PostgreSQL (HA, DR, monitoring, skalowanie)

To szkolenie może być dofinansowane z KFS lub BUR.

★★★★★Średnia ocena naszych szkoleń w Google: 5/5

Najczęściej zadawane pytania

Jak najprościej zmierzyć, o ile replika jest opóźniona względem primary?
Na samym standby wykonaj SELECT now() - pg_last_xact_replay_timestamp(). Wynik to czas, o jaki dane na replice są starsze od produkcji. Na primary dodatkowe metryki daje pg_stat_replication z kolumnami write_lag, flush_lag i replay_lag, gdzie replay_lag mówi o realnej świeżości danych.
Czym różnią się opóźnienia write, flush i replay?
Write to ile WAL dotarło do repliki, flush to ile trwale zapisano na jej dysku, a replay to ile faktycznie zastosowano w plikach danych. Dla świeżości odczytów i bezpieczeństwa przy failoverze liczy się replay, bo dopiero zastosowane zmiany są widoczne w zapytaniach na standby.
Kiedy warto włączyć hot_standby_feedback?
Gdy na standby odtwarzanie zatrzymuje się przez konflikty z długimi zapytaniami raportowymi. Hot_standby_feedback sprawia, że replika informuje primary, jakich wierszy potrzebuje, więc vacuum ich nie usuwa i odtwarzanie nie musi się wstrzymywać. Trzeba jednak uważać, bo może to zwiększać bloat na primary.
Jak całkowicie wyeliminować utratę transakcji przy awarii primary?
Przełącz wybraną replikę w tryb synchroniczny, ustawiając na primary synchronous_standby_names. Wtedy commit czeka na potwierdzenie zapisu przez standby, więc lag danych znika. Kosztem jest dłuższy czas zatwierdzania transakcji, dlatego stosuje się to tam, gdzie zero utraty danych jest ważniejsze niż maksymalna przepustowość.

Komentarze (0)

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

Brak komentarzy...