Streaming replication i hot-standby to fundament wysokiej dostepnosci PostgreSQL - zanim wejdzie sie w Patroni czy pgBouncer, warto zrozumiec ten mechanizm od podstaw. Wyjasniamy go na przykladzie.
W jaki sposób skonfigurować replikę powtarzającą każdą zmianę na serwerze źródłowym? W jaki sposób replika może przejąć rolę serwera primary?
| Ikona pliku PDF do pobrania Cheatsheet | Ikona notatek Cheatsheet edytowalny | Ikona ćwiczenia Ćwiczenie | Ikona wskazówki Rozwiązanie ćwiczenia |
Z tego artykułu dowiesz się:
- czym jest i jak działa replikacja strumieniowa,- do czego nam potrzebna replikacja strumieniowa i kiedy ją stosować,- jak przygotować klaster PostgreSQL do replikacji,- jak określać maksymalną ilość replik które mogą się podłączyć do klastra PostgreSQL,- jak ustawić odpowiednie reguły w pg_hba.conf by umożliwić replikację,- jak stworzyć zdalną replikę klastra i spowodować by powtarzała zmiany wykonywane na serwerze primary,- jak weryfikować stan i opóźnienia w replikacji po stronie serwera primary,- jak weryfikować stan i opóźnienia w replikacji po stronie - repliki,- w jaki sposób skonfigurować plik .pgpass byśmy nie byli proszeni o hasło przy każdym tworzeniu repliki,- jak optymalizować szybkość commitów w środowisku replikowanym,- jak zadbać o to, by w przypadku rozłączenia się repliki i podłączenia ponownie replika nadgoniła zmiany na serwerze primary,- jak tworzyć, listować i kasować fizyczne sloty replikacyjne.
[ Do realizacji przykładów z tego rozdziału potrzebujemy hostów 1 i 2. Należy je przygotować według tej instrukcji ]
W replikacji strumieniowej, hot standby, wszystkie wpisy WAL zostają najpierw zapisane w pamięci "WAL buffers", następnie wywoływany jest fsync, który utrwala je lokalnie na dysku w plikach WAL. Pliki WAL zawierają informacje o wszystkich zmianach zachodzących w bazie danych oraz podzielone są na 16 MB segmenty. Następnie postgres za pomocą procesu "walsender" wysyła te wpisy do klastra standby, która za pomocą procesu "wal receiver" odbiera je i zapisuje do plików WAL na serwerze standby i natychmiast odtwarza zmiany z odebranego wpisu, dzięki czemu dane są dostępne do odczytu niemal w tym samym momencie, najczęściej w ciągu kilku milisekund na klastrze standby w przypadku replikacji asynchronicznej lub w tym samym momencie w przypadku replikacji synchronicznej.
Sposób, w jaki dane są przesyłane i odtwarzane, sprawia, że instancja primary i wszystkie repliki są dokładnymi kopiami, 1 do 1. Mają dokładnie taki sam rozmiar i w każdym bloku danych przechowywane są dokładnie te same wiersze.
Domyślnie replikacja strumieniowa działa w trybie asynchronicznym, ale możemy to w bardzo łatwy sposób zmienić, przyjrzymy się temu nieco później.
Jednym z ważniejszych parametrów dla replikacji strumieniowej, szczególnie tej synchronicznej, ale nie tylko, jest "synchronous_commit". Określa on, kiedy transakcja zwróci informację o udanym commicie do klienta. Za jego pomocą możemy "sterować" szybkością commitów oraz trwałością danych, im niższa trwałość, tym większa przepustowość transakcji na sekundę.
Poziomy synchronous_commit:
- off / 0(zero) / false / no - wyłącza commit synchroniczny. Dane nadal będą w pełni spójne, transakcja przekaże informację zwrotną o udanym commicie w momencie zapisania wpisu WAL do buforów WAL w pamięci. Następnie zależnie od wal_writer_delay wpisy będą zrzucane kolejno do plików WAL. Wadą tego rozwiązania jest fakt, że w momencie wypadku utracone zostaną wszystkie transakcje, które były zacommitowane tylko w pamięci, czyli w przypadku crasha serwera możemy utracić do wal_writer_delay (domyślnie 200ms) transakcji.
- local - zwraca informację o zakończeniu transakcji po dodaniu wpisu WAL do buforów oraz zapisaniu do pliku WAL na dysku.
- remote_write - transakcja czeka na informację zwrotną z instancji standby o zapisaniu wpisu WAL na dysku, write_lsn.
- on / 1 / true / yes - jeżeli "synchronous_standby_names" jest puste, ustawienie parametru na ON gwarantują taką samą trwałość danych jak: local, remote_write, on, remote_apply. W replikacji synchronicznej - transakcja czeka na informację o zakończonym flushu danych z wpisu WAL, flush_lsn.
- remote_apply - transakcja na primary czeka na informację zwrotną ze standby o odtworzeniu transakcji na replice, replay_lsn.
Parametr synchronous_commit możemy ustawić na poziomie transakcji, sesji, dla użytkownika lub całej instancji. Ustawienie go na "off" jest przydatne w sytuacji, kiedy planujemy ładowanie dużej liczby danych lub w trakcie migracji danych między systemami. Wyłączony commit synchroniczny może bardzo obniżyć generowane obciążenie na serwerze oraz przyspieszyć całą operację, ponieważ fsync jest wywoływany tylko raz na 200ms, przy domyślnych ustawieniach, a nie dla każdego wpisu z osobna.
W replikacji strumieniowej parametrem "synchronous_standby_names" określamy wymagania co do replikacji synchronicznej. Które serwery powinny być replikowane synchroniczne, jakie oczekiwania - mamy - względem replik. Które i ile z nich będzie odtwarzało synchronicznie transakcje z instancją primary i kiedy możemy zwrócić informację o zakończeniu transakcji z repliki do primary. Domyślnie parametr jest pusty, co oznacza, że replikacja będzie działała jako asynchroniczna.
Parametr przyjmuje wartości:
- first X (application_name1,application_name2,...) - pierwsze X serwerów z listy musi przesłać informację zwrotną do primary, zanim transakcja zostanie zacommitowana,
- any X (application_name1,application_name2,...) - dowolne X serwerów z listy musi przesłać informację zwrotną do primary, zanim transakcja zostanie zacommitowana,
- application_name1,application_name2,... - wszystkie serwery z listy muszą przesłać informację zwrotną do primary, zanim transakcja zostanie zacommitowana.
Wartości "application_nameX" to nazwy replik, które możemy znaleźć w widoku pg_stat_replication w kolumnie application_name, a które możemy ustawić na replice, aktualizując parametr "primary_conninfo".
Przykładowo: primary_conninfo = 'application_name=pg2 user=replicator password=tajnehaslo host=192.168.33.10 port=5432'.
Chcąc korzystać z replikacji synchronicznej, musimy zagwarantować bardzo szybkie połączenie z jak najniższymi opóźnieniami pomiędzy serwerami postgresa. Najlepiej jeżeli oba serwery byłyby w tej samej serwerowni, ponieważ każde opóźnienie w odtwarzaniu na replice nie pozwoli zakończyć transakcji na instancji głównej. Konfigurując replikację synchroniczną, musimy liczyć się z tym, że każda transakcja będzie trwała minimalnie dłużej o "roundtrip time", czyli czas potrzebny na wysłanie pakietu z primary na replikę i z powrotem, ponieważ każda pojedyncza transakcja będzie musiała być zatwierdzona przez oba serwery przed poinformowaniem aplikacji o sukcesie lub błędzie w wykonywaniu transakcji.
[host 1: serwer master]
Przed stworzeniem repliki strumieniowej na instancji głównej musimy upewnić się, że mamy ustawione wszystkie potrzebne parametry i stworzyć użytkownika z prawem replikacji oraz dodatkowymi uprawnieniami wymaganymi przez pg_rewind, czyli narzędzie służące do synchronizacji katalogów PGDATA w celu ponownego podłączenia do replikacji. Na przykład po wykonaniu awansowaniu repliki na instancję primary możemy wykonać pg_rewind w celu synchronizacji katalogów z danymi i ponownego podłączenia starego serwera primary jako replika do nowego serwera głównego. Jako użytkownik systemowy postgres edytujemy plik /data_pg/postgresql.conf i zmieniamy poniższe parametry:
vi /data_pg/postgresql.conf
listen_addresses = '*'
wal_level = replica
wal_log_hints = on
max_wal_senders = 5
max_replication_slots = 5
logging_collector = on
Ustawione powyżej parametry to:
- listen_addresses - określa, na których adresach sieciowych postgres będzie nasłuchiwał. Przy ustawieniu * nasłuchuje na wszystkich interfejsach sieciowych dostępnych na serwerze,
- wal_level - poziom logowania w plikach wal. Możliwe poziomy to: minimal, replica, logical. Dla replikacji musimy ustawić przynajmniej replica. Minimal pozwala tylko na wykonanie odzyskania bazy do spójnego stanu w przypadku nieplanowanego restartu klastra, a logical dodaje dodatkowe informacje potrzebne przy replikacji logicznej,
- max_wal_senders - maksymalna liczba procesów WAL sender dla instancji,
- max_replication_slots - limit liczby slotów replikacyjnych w instancji,
- logging_collector - włączenie logowania dla procesu postgresa do pliku,
Ostatnim parametrem jest wal_log_hints. Odpowiada on za zapisywanie pełnych bloków danych do plików WAL - po pierwszej modyfikacji każdego bloku po checkpoincie. Włączając to ustawienie, możemy też sprawdzić, o ile więcej plików WAL postgres wygeneruje po włączeniu sum kontrolnych. Do jego zmiany musimy zrestartować postgresa. Jest to jedno z wymagań pg_rewind. Następnie z poziomu użytkownika systemowego postgres logujemy się do psql. Tworzymy użytkownika replicator, który powinien mieć uprawnienia superusera lub możliwość czytania plików w katalogu z danymi z poziomu postgresa. Ze względów bezpieczeństwa polecam nadać tylko wymagane uprawnienia, lista potrzebnych funkcji poniżej:
# restart klastra
# dla Ubuntu
postgres@ubuntu:~$ /usr/lib/postgresql/15/bin/pg_ctl -D /data_pg restart
# dla CentOS
[postgres@centos ~]$ /usr/pgsql-15/bin/pg_ctl -D /data_pg restart
postgres@ubuntu:~$ psql
create user replicator with replication password 'tajnehaslo';
GRANT EXECUTE ON function pg_catalog.pg_ls_dir(text, boolean, boolean) TO replicator;
GRANT EXECUTE ON function pg_catalog.pg_stat_file(text, boolean) TO replicator;
GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text) TO replicator;
GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text, bigint, bigint, boolean) TO replicator;
Następnie musimy dodać wpis do /data_pg/pg_hba.conf umożliwiający połączenie dla replikacji oraz przeładować konfigurację.
vi /data_pg/pg_hba.conf
host - replication - replicator - <X.X.X.X IP adres serwera standby>/32 - scram-sha-256
psql -c "select pg_reload_conf()"
[Host 2: serwer slave]
Teraz możemy rozpocząć stawianie nowej repliki na jednym z hostów za pomocą narzędzia pg_basebackup. Parametry to kolejno: -h adres serwera, -U - użytkownik, -C stwórz slot replikacyjny, -S nazwa slota replikacyjnego, -R dopisz konfigurację umożliwiającą strumieniowanie, -D docelowy katalog z danymi, -X strumieniowanie wpisów WAL, -P wyświetlanie postępu prac nad kopią. Wykonujemy to z poziomu użytkownika systemowego postgres:
pg_basebackup -h <X.X.X.X IP adres serwera primary> -U replicator -C -S pg2 -R -D /data_pg -X stream -P
Zostaniemy zapytani o hasło użytkownika "replicator". Jeżeli wszystko było wykonane według przykładów, to hasłem tutaj będzie "tajnehaslo". Jeżeli nie chcielibyśmy podawać hasła przy tworzeniu repliki, możemy
stworzyć sobie plik .pgpass:
postgers@pg2:~$ cat <<EOF > ~postgres/.pgpass
<X.X.X.X>:5432:*:replicator:tajnehaslo
EOF
Aby ułatwić sobie identyfikację replik, dodajemy w pliku /data_pg/postgresql.auto.conf - "application_name=pg2" w parametrze "primary_conninfo", np.
primary_conninfo = 'application_name=pg2 user=replicator password=tajnehaslo channel_binding=prefer host=192.168.33.10 port=5432 sslmode=prefer sslcompression=0 sslsni=1 ssl_min_protocol_version=TLSv1.2 gssencmode=prefer krbsrvname=postgres target_session_attrs=any'
primary_slot_name = 'pg2'
Application name,może przyjmować dowolną wartość. - Najważniejsze, żeby ułatwiała identyfikację serwera, na którym dana replika działa.
Na koniec uruchamiamy serwer slave:
pg_ctl -D /data_pg/ start
[Host 1: serwer master]
Status replikacji możemy sprawdzić na instancji primary za pomocą widoku pg_stat_replication.
Wyświetla on informacje o id procesu walsender dla każdej repliki, użytkownika, - który jest wykorzystywany do replikacji, nazwę aplikacji, - którą ustawiliśmy w "primary_conninfo" na replice, serwer, na który dane są replikowane oraz - informacje o stanie replikacji.
Kolumna state może przyjmować wartości "streaming", jeżeli aktualnie przesyła wpisy WAL do procesu walreceiver, catchup, jeżeli standby aktualnie odtwarza logi transakcyjne z archiwum, startup w momencie startu replikacji, backup jeżeli wykonujemy kopię klastra za pomocą pg_basebackup, oraz stopping, kiedy proces jest zatrzymywany.
Kolejnymi wartościami są sent_lsn, write_lsn, flush_lsn, replay_lsn oraz write_lag, flush_lag, replay_lag. Sent_lsn to ostatnia lokacja wpisu w pliku WAL wysłanego na replikę, write_lsn to ostatni wpis zapisany w pliku WAL na dysku repliki, flush_lsn to ostatnia lokacja w pliku WAL, - która została przeczytana i zapisana w plikach danych. Ale - to, że została tam zapisana, nie oznacza, iż dane są dostępne do odczytu. Ten następuje po "odtworzeniu" wpisu oraz udostępnieniu do odczytu, czyli wartość replay_lsn.
# \x - widok rozszerzony - extended view. Sprawia, że każdy wiersz jest
# wyświetlany jako osobny wpis, nazwy kolumn po lewej, wartość po prawej
postgres=# \x
postgres=# select * from pg_stat_replication where application_name ='pg2';
-[ RECORD 1 ]----+------------------------------
pid | 6315
usesysid | 16384
usename | replicator
application_name | pg2
client_addr | 192.168.56.12
client_hostname - |
client_port | 42572
backend_start | 2023-08-14 20:08:19.624098+00
backend_xmin |
state | streaming
sent_lsn | 0/12000000
write_lsn | 0/12000000
flush_lsn | 0/12000000
replay_lsn | 0/12000000
write_lag |
flush_lag |
replay_lag |
sync_priority | 0
sync_state | async
reply_time | 2023-08-14 20:53:01.8204+00
Stworzona w ten sposób replikacja - to replikacja asynchroniczna. Fakt ten możemy potwierdzić w widoku pg_stat_replication, w kolumnie sync_state.
Chcąc przełączyć replikację na synchroniczną, musimy zaktualizować parametr "synchronous_standby_names", dodając do niego nazwę repliki lub listę replik, które chcemy używać jako repliki synchroniczne.
Przykładowo z poziomu konsoli psql:
alter system set synchronous_standby_names to 'pg2';
select pg_reload_conf();
postgres=# select * from pg_stat_replication where application_name ='pg2';
-[ RECORD 1 ]----+------------------------------
pid | 6315
usesysid | 16384
usename | replicator
application_name | pg2
client_addr | 192.168.56.12
client_hostname - |
client_port | 42572
backend_start | 2023-08-14 20:08:19.624098+00
backend_xmin |
state | streaming
sent_lsn | 0/12000000
write_lsn | 0/12000000
flush_lsn | 0/12000000
replay_lsn | 0/12000000
write_lag |
flush_lag |
replay_lag |
sync_priority | 1
sync_state | sync
reply_time | 2023-08-14 20:53:11.852902+00
Tym sposobem zmieniliśmy typ replikacji z asynchronicznej na synchroniczną. Poziom synchronizacji w replikacji synchronicznej określamy parametrem synchronous_commit, który opisany był wcześniej. W ramach powtórki wybrać możemy opcje: remote_write - transakcja czeka na informację zwrotną z instancji standby o zapisaniu wpisu WAL na dysku, write_lsn, on - transakcja czeka na informację o zakończonym flushu danych z wpisu WAL, czyli dane są utrwalone na dysku, ale jeszcze niekoniecznie widoczne dla aplikacji, flush_lsn, lub remote_apply - transakcja na primary czeka na informację zwrotną ze standby o odtworzeniu transakcji na replice, czyli dane mogą być odczytane na replice, replay_lsn.
[Host 2: serwer slave]
Status replikacji możemy też sprawdzić na - replice w widoku pg_stat_wal_receiver. Możemy w nim znaleźć informacje podobne do pg_stat_replication na instancji primary, a dodatkowo informację o czasie ostatniej wymiany wiadomości, przykładowo sprawdzenie przez replikę, czy primary jest dostępny, hot standby feedback - wiadomości o aktywnych transakcjach na replice czy status replikacji z serwera standby.
postgres=# select * from pg_stat_wal_receiver;
pid | 4841
status | streaming
receive_start_lsn | 0/32000000
receive_start_tli | 2
written_lsn | 0/320000D8
flushed_lsn | 0/320000D8
received_tli | 2
last_msg_send_time | 2023-07-11 20:19:17.897255+00
last_msg_receipt_time | 2023-07-11 20:19:17.972795+00
latest_end_lsn | 0/320000D8
latest_end_time | 2023-07-11 20:19:17.972795+00
slot_name | pg2
sender_host | 192.168.33.10
sender_port | 5432
conninfo | user=replicator password=******** channel_binding=prefer dbname=replication host=192.168.33.10 port=5432 application_name=pg3 fallback_application_name=walreceiver sslmode=prefer sslcompression=0 sslsni=1 ssl_min_protocol_version=TLSv1.2 gssencmode=prefer krbsrvname=postgres target_session_attrs=any
Podczas wywoływania polecenia pg_basebackup użyliśmy przełączników -C -S , aby stworzyć slot replikacyjny, który zagwarantuje nam, że wszystkie pliki WAL wymagane przez replikację były przechowywane przez instancję primary do czasu wykorzystania ich przez replikę.
[host 1: serwer master]
Wypisać wszystkie istniejące sloty możemy poleceniem:
postgres=# select * from pg_replication_slots;
Stworzenie ręczne nowego fizycznego slota replikacyjnego odbywa się za pomocą polecenia:
postgres=# select pg_create_physical_replication_slot('replika_pg2');
Warto raz na jakiś czas sprawdzić, czy wszystkie sloty są aktywne i konsumują pliki WAL, ponieważ nieaktywne sloty replikacyjne mogą prowadzić do przetrzymywania plików WAL w nieskończoność i spowodować przepełnienie dysku, a w efekcie zatrzymanie postgresa. Niepotrzebne sloty replikacyjne możemy usunąć poleceniem:
postgres=# select pg_drop_replication_slot('replika_pg2');
Szkolenie Administracja, replikacja i tuning baz danych PostgreSQL →
To szkolenie może być dofinansowane dla Ciebie z KFS lub BUR.
★★★★★Średnia ocena naszych szkoleń w Google: 5/5


Top comments (0)