
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:
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_conflictswartość "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)
Brak komentarzy...