Blog JSystems - uwalniamy wiedzę!

Szukaj


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?

















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 <nazwaslota>, 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');

Komentarze (0)

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

Brak komentarzy...