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...