Blog JSystems - uwalniamy wiedzę!

Szukaj


Z tego artykułu dowiesz się:

  • jakie są najczęściej występujące problemy z replikacją,
  • dlaczego replikacja może przestać strumieniować zmiany i jak temu zaradzić,
  • kiedy może pojawić się konflikt z procesem odtwarzania i jak temu zaradzić,
  • co może powodować opóźnienia w replikacji i jak temu zaradzić.




1. Replikacja przestała strumieniować zmiany


Zazwyczaj postgres stara się przechowywać pliki WAL od checkpointa do checkpointa i za każdym razem sprawdza, czy posiada więcej WAL niż określa parametr min_wal_size oraz usuwa nadmiarowe logi, które zostały już zarchiwizowane, jeżeli archiwizacja jest włączona, oraz nie są wymagane do uzyskania spójnych danych w przypadku nieplanowanego zatrzymania bazy. Dlatego, jeżeli standby straciłoby połączenie z primary na odpowiednio długą chwilę, może się zdarzyć sytuacja, w której jeszcze nie przesłane na instancję standby pliki WAL zostały już usunięte.

W tym przypadku, aby przywrócić replikację, musimy jakoś odzyskać brakujące pliki WAL. Jeżeli mamy skonfigurowaną archiwizację, możemy na klastrze standby ustawić "restore_comand" i skopiować brakujące pliki WAL z archiwum. Postgres będzie na bieżąco sprawdzał, czy kolejny plik WAL jest dostępny na instancji primary. Jeżeli tak będzie, przerwie proces odzyskiwania z archiwum i ponownie rozpocznie strumieniowanie zmian z primary. Problem pojawia się, kiedy urządzenie przechowujące archiwalne logi jest dość wolne. Możemy wtedy mieć problem dogonić klaster główny, jeżeli przerwa nastąpiła w okresie zwiększonej aktywności w bazie, za czym idzie generacja większej liczby plików WAL.

Aby tej sytuacji zapobiec, postgres posiada funkcję nazwaną "replication slots" wprowadzoną w wersji 9.4. Sloty odpowiedzialne są za przechowywanie wszystkich plików WAL, które nie zostały "skonsumowane" przez wal receiver. Oznacza to, że w momencie utraty połączenia pomiędzy klastrami postgres nie będzie mógł usunąć żadnego pliku WAL, który nie został odebrany przez standby. Brzmi świetnie, ale ma też swoje minusy. Jeżeli odpowiednio szybko nie przywrócimy połączenia, możemy doprowadzić do sytuacji, w której zapełnimy dysk przechowujący pliki WAL, czym doprowadzimy do zatrzymania instancji głównej. Od wersji 13 mamy dostępną opcję "max_slot_wal_keep_size", która określa, jak bardzo slot może urosnąć, zanim postgres zacznie usuwać pliki WAL w nim zawarte.

Inną opcją na zapewnienie odpowiedniej liczby plików WAL do przywrócenia replikacji, już nie polecaną, jest ustawienie parametru "wal_keep_segments" w wersjach 12 i starszych oraz "wal_keep_size" od wersji 13. Określa ona, ile plików WAL postgres musi za wszelką cenę przechowywać na potrzeby odtwarzania przez instancję standby. Wadą tego rozwiązania jest to, że przechowujemy zawsze X plików określone w parametrze, czy ich potrzebujemy czy nie i mimo to nie mamy gwarancji, że będzie ich wystarczająco po ponownym wystartowaniu repliki do wznowienia replikacji.

2.

ERROR: canceling statement due to conflict with recovery

Detail: User query might have needed to see row versions that must be removed


Czasami zdarza się, że zapytania na replice, szczególnie te dłuższe, są przerywane z powyższym błędem. Oznacza to, że wiersze, które dana transakcja próbowała przeczytać zostały usunięte na instancji głównej oraz proces vacuum zdążył je już wyczyścić, a replika odtworzyła te zmiany, usuwając wersje wierszy ciągle wymagane przez zapytanie aktywne na replice.



Aby temu zapobiec, możemy skorzystać z parametru "hot_standby_feedback=on". Spowoduje on, że replika będzie przekazywała informację o najstarszej aktywnej transakcji do instancji głównej z częstotliwością zdefiniowaną w wal_receiver_status_interval, dzięki czemu primary nie usunie wierszy, które wciąż mogą być wymagane przez rozpoczęte transakcje na standby.

Jednak parametr ten nie jest uniwersalnym rozwiązaniem dla każdej repliki mającej jakieś problemy. Jeżeli na instancji głównej bardzo często zmieniamy bądź usuwamy dane, a na replice wykonujemy dużo długich selectów, hot_standby_feedback może zacząć powodować puchnięcie tabel i indeksów, tzw. bloat oraz powodować lagi na replice.


#PRIMARY

postgres=# select application_name,client_addr,backend_xmin,state,sent_lsn,write_lsn,flush_lsn,replay_lsn,sync_state from pg_stat_replication;

application_name | client_addr | backend_xmin | state | sent_lsn | write_lsn | flush_lsn | replay_lsn | sync_state

------------------+---------------+--------------+-----------+------------+------------+------------+------------+------------

pg3 | 192.168.56.13 | <PUSTE> | streaming | 2/8E000110 | 2/8E000110 | 2/8E000110 | 2/8E000110 | async

(1 row)



#REPLIKA

postgres@ubuntu:~$ psql

psql (15.3 (Ubuntu 15.3-1.pgdg22.04+1))

Type "help" for help.



postgres=# ALTER SYSTEM SET hot_standby_feedback = on;

ALTER SYSTEM

postgres=# \q

postgres@ubuntu:~$ /usr/lib/postgresql/15/bin/pg_ctl -D /data_pg/ restart



#PRIMARY

postgres=# select application_name,client_addr,backend_xmin,state,sent_lsn,write_lsn,flush_lsn,replay_lsn,sync_state from pg_stat_replication;

application_name | client_addr | backend_xmin | state | sent_lsn | write_lsn | flush_lsn | replay_lsn | sync_state

------------------+---------------+--------------+-----------+------------+------------+------------+------------+------------

pg3 | 192.168.56.13 | 27954316 | streaming | 2/8E000110 | 2/8E000110 | 2/8E000110 | 2/8E000110 | async

(1 row)





3. Lagi

Najczęstsze przyczyny:



  • długie transakcje na instancji standby mogą powodować lagi, jeżeli parametr hot_standby_feedback jest włączony. Ze względu na to, że odtwarzanie zmian odbywa się poprzez jeden proces, jeżeli jeden obiekt na standby jest zablokowany i nie może zostać przepisany, cała replikacja czeka na odblokowanie obiektu, aby kontynuować. Można temu nieco zaradzić poprzez ustawienie parametru max_standby_streaming_delay, który powoduje anulowanie transakcj,i które blokują proces odzyskiwania przez X sekund. Nie oznacza to jednak, że kiedy ustawimy go na 30 sekund, nigdy nie będziemy mieli laga większego niż 30s. Jeżeli jeden proces zablokuje proces recovery na 30s, a po 25 sekundach rozpocznie się następny blokujący recovery, pierwszy zostanie zabity po 30, a drugi będzie czekał kolejne 25 sekund do 30 w sumie zanim zostanie zabity, co da nam w sumie 55 sekund laga,

  • mniej wydajny serwer na replice,

  • zbyt wolne połączenie sieciowe pomiędzy serwerami,

  • błędy logicznej replikacji, jeżeli zapomnimy o aplikowaniu zmian DDL na obu serwerach replikacja zatrzyma się do czasu poprawienia obiektów, aby ich definicje były zgodne na obu serwerach,

  • również dla logicznej replikacji, kiedy wykonujemy transakcje zmieniające ogromne liczby danych w jednej transakcji i mamy złe ustawienia parametrów timeoutów wal_sender_timeout oraz wal_receiver_timeout, przez co worker replikacji kończy pracę nie robiąc żadnego postępu,

  • w bardzo rzadkich przypadkach, zbyt duży shared_buffers na replice może powodować lagi na replice, jeżeli wykonujemy bardzo dużo operacji drop table, truncate oraz vacuum na tabelach, gdzie usunęliśmy znaczne liczby danych. Są to operacje, które w momencie odtwarzania na replice wywołują funkcje, które skanują cały shared buffer w poszukiwaniu wszystkich bloków danych należących do danego obiektu, aby je z niego usunąć, co w przypadku ogromnych shared_buffers, może być problematyczne, jeżeli co kilka minut/sekund każemy mu skanować 1TB RAMu, a replikacja nie może kontynuować dopóki funkcja się nie zakończy i wszystkie bloki danych nie zostaną z nich usunięte.


Szukając przyczyny lagów replikacji, warto ustawić parametr

log_recovery_conflict_waits = on
, co pozwoli nam znaleźć proces blokujący replikację, jeżeli replikacja jest blokowana przez więcej niż
deadlock_timeout
, który domyślnie ustawiony jest na 1 sekundę. Możemy również sprawdzić, jakie konflikty występowały dla każdej z baz w widoku
pg_stat_database_conflicts


select * from pg_stat_database_conflicts ;

Spróbujmy zasymulować jeden z możliwych konfliktów. Na potrzeby demonstracji stworzymy tabelę "lag" i dodamy jakieś dane.


postgres=# create table lag (x int, y int);

CREATE TABLE





postgres=# insert into lag values (generate_series(1,1000000), 1);

INSERT 0 1000000

Na replice włączę logowanie konfliktów z recovery.


postgres=# alter system set log_recovery_conflict_waits to on;

ALTER SYSTEM

postgres=# select pg_reload_conf();

pg_reload_conf

----------------

t

(1 row)

Na instancji primary wykonam UPDATE, który zacznie aktualizację wszystkich wierszy w tabeli.


postgres=# update lag SET y = x + 34;

Ponownie na replice rozpocznę transakcję i długie zapytanie na zmienianej tabeli, a w osobnym oknie otworzymy log, aby śledzić nowe wpisy.


postgres=# begin;

BEGIN

postgres=*# select * from lag a, lag b;

Na priamary, po zakończeniu update, a po wystartowaniu transakcji, wykonujemy jeszcze jedną aktualizację.


postgres=# update lag SET y = x + 134;

Po chwili w logu powinien pojawić się wpis o procesie blokującym proces odtwarzania, oraz w widoku

pg_stat_database_conflicts
wartość "confl_bufferpin" powinna podnieść się o jeden.


2022-11-09 10:47:41.337 UTC [6599] LOG:  recovery still waiting after 1000.802 ms: recovery conflict on buffer pin

2022-11-09 10:47:41.337 UTC [6599] CONTEXT: WAL redo at 11/D4795108 for Heap2/PRUNE: latestRemovedXid 839 nredirected 0 ndead 82; blkref #0: rel 1663/13697/40961, blk 132743 FPW

2022-11-09 10:48:10.269 UTC [6692] postgres@postgres ERROR: canceling statement due to conflict with recovery

2022-11-09 10:48:10.269 UTC [6692] postgres@postgres DETAIL: User was holding shared buffer pin for too long.

2022-11-09 10:48:10.269 UTC [6692] postgres@postgres STATEMENT: select * from lag a, lag b;

2022-11-09 10:48:10.274 UTC [6599] LOG: recovery finished waiting after 29939.820 ms: recovery conflict on buffer pin


2022-12-09 10:48:10.274 UTC [6599] CONTEXT: WAL redo at 11/D4795108 for Heap2/PRUNE: latestRemovedXid 839 nredirected 0 ndead 82; blkref #0: rel 1663/13697/40961, blk 132743 FPW

Komentarze (0)

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

Brak komentarzy...