Blog JSystems - uwalniamy wiedzę!

Szukaj


Z tego artykułu dowiesz się:

  • czym są failover, failback, switchover i w jakim celu się je stosuje,
  • jak promować replikę do trybu zapis-odczyt,
  • jak zamienić funkcjami replikę fizyczną i serwer primary,
  • jak synchronizować repliki i serwer primary za pomocą pg_rewind.


[host 2: serwer replika]


Failover, czyli awaryjne przerzucenie odpowiedzialności za wszystkie zadania wykonywane przez klaster primary na inną replikowaną instancję, oraz otwarcie jej do zapisu w postgresie w środowiskach produkcyjnych o najwyższych wymogach dostępności. Odpowiada za to zazwyczaj jakieś zewnętrzne narzędzie jak Patroni czy REPMGR albo autorskie, niestandardowe skrypty czy automatyzacje.


Przejęcie roli primary w postgresie możemy wykonać na kilka sposobów. Za pomocą narzędzia pg_ctl, funkcji pg_promote() lub pliku triggera zdefiniowanego w parametrze postgresql.conf "promote_trigger_file"


/usr/lib/postgresql/15/bin/pg_ctl -D /data_pg/ promote

lub


psql -c "select pg_promote()"

lub


touch /sciezka/do/pliku/triggera

Po wykonaniu jednego polecenia z powyższych replika zakończy odtwarzanie, podniesie znacznik linii czasu o 1 oraz otworzy bazę do zapisu.


Linie czasu (timeline) to mechanika wprowadzona w postgresie, która gwarantuje, że po każdym zakończeniu odtwarzania zmian, czy to po odzyskaniu bazy z kopii zapasowej, czy awansowanie repliki na instancję, primary spowoduje zmianę "generacji" klastra, przez co pliki WAL będą generowane z nowym przedrostkiem. Pierwsza wartość w nazwie pliku WAL to numer linii czasu, dzięki czemu, jeżeli przez problemy sieciowe stara instancja primary była odizolowana od reszty klastra i aplikacji przed rozwiązaniem problemu wygenerowała nowe pliki WAL, nie będzie konfliktu z logami WAL wygenerowanymi przez świeżo awansowaną na primary replikę. Stara instancja primary będzie nadal generowała logi transakcyjne w poprzedniej linii czasu, a nowa już w podniesionej. Każde podniesienie linii czasu powoduje również stworzenie pliku historii dla danej linii czasu, który zawiera między innymi informację, w którym miejscu linie czasu się rozdzieliły. Dzięki temu unikniemy problemu zduplikowanych logów transakcyjnych i będziemy mogli jednocześnie archiwizować pliki WAL dla obu klastrów, starego primary i nowego. A korzystając z pełnej kopii zapasowej wykonanej dla starej instancji primary oraz plików WAL, będziemy mogli odtworzyć dowolny z klastrów primary do dowolnego momentu w czasie. Dzięki parametrowi postgresa "recovery_target_timeline", którym możemy wskazać, w której linii czasu powinien wykonać odzyskiwanie, domyślną wartością jest "latest", czyli podczas odtwarzania zmian postgres podąża za wszystkimi zmianami linii czasu, jeżeli posiada odpowiednią liczbę plików WAL w archiwum lub katalogu pg_wal.


[host 1: dawny serwer master]


Failback, czyli powrót do oryginalnej konfiguracji, przed wystąpieniem sytuacji awaryjnej.

Musimy zacząć od podłączenia starej instancji primary do klastra jako replika i zacząć odtwarzać wszystkie zmiany wykonane, kiedy była niedostępna. Czasami jeżeli awansowanie repliki na primary było kontrolowane, wystarczy stworzyć plik standby.signal w PGDATA, zadbać o odpowiednią konfigurację w parametrach primary_conninfo, restore_command i recovery_target_timeline = 'latest' oraz wystartować starą instancję główną jako replikę.

Ale jeżeli doszło do nieplanowanego zatrzymania primary, doszło do awarii sieci lub jakiejkolwiek sytuacji, której pierwotna primary mogła zapisać jakieś dane nieprzesłane na nowo awansowaną instancję główną, może być wymagane użycie pg_rewind, czyli narzędzia do synchronizacji dwóch katalogów PGDATA odmiennej generacji. Aby móc go użyć, musimy posiadać użytkownika z odpowiednimi uprawnieniami oraz dodać na nowym serwerze primary wpisy pg_hba.conf umożliwiające połączenie do bazy "postgres" i pozwalające na inicjalizację replikacji.


Przykładowe polecenie dla pg_rewind w trybie "dry run". Nie wykonujmy na razie poniższego pg_rewinda, praktyczna demonstracja funkcjonalności przedstawiona będzie trochę później.


/usr/lib/postgresql/15/bin/pg_rewind -n -c -R -D /data_pg/ --source-server='host=<X.X.X.X adres nowego primary> port=5432 user=replicator password=tajnehaslo application_name=pg1 dbname=postgres'

Powyższe polecenie z powodu użycia przełącznika "-n" wykona tzw. "dry run", sprawdzi, czy wszystko jest ustawione jak należy, czy użytkownik ma odpowiednie uprawnienia, czy wszystkie pliki WAL są dostępne. Jeżeli polecenie się powiedzie, możemy usunąć "-n" i wykonać je jeszcze raz.


Przykład polecenia pg_rewind:


/usr/lib/postgresql/15/bin/pg_rewind -c -R -D /data_pg/ --source-server='host=<X.X.X.X adres nowego primary> port=5432 user=replicator password=tajnehaslo application_name=pg1 dbname=postgres'

Po wykonaniu pg_rewind zweryfikujmy, czy został stworzony plik standby.signal.

Później możemy zmodyfikować primary_conninfo, aby dodać parametr application_name, ustawić odpowiednio wartość dla "primary_slot_name" oraz na aktualnej primary stworzyć nowy fizyczny slot replikacyjny.


Przykład tworzenia fizycznego slotu replikacyjnego:


postgres=# select pg_create_physical_replication_slot('replika_pg1');

Możemy wystartować starą instancję główną, która teraz powinna łączyć się jako replika do nowego primary.

Znając wszystkie kroki, możemy wykonać failback poprzez switchover, czyli kontrolowaną zamianę ról pomiędzy serwerami. Zatrzymać aktualny primary, awansować replikę i otworzyć do zapisu, upewnić się, że slot replikacyjny istnieje, na zatrzymanej instancji stworzyć "standby.signal" oraz wystartować klaster.


Wykonanie kontrolowanego failovera, krok po kroku.


[host 1: oryginalny serwer master]


Stworzenie użytkownika na aktualnym serwerze obsługującym zapis (master / primary):


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;

Zatrzymanie postgresa na klastrze primary "pg1".


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

[host 2: oryginalny serwer slave]


Promote instancji standby "pg2" oraz stworzenie slotu replikacyjnego dla starego primary. Nazwę slota replikacyjnego powinniśmy wybrać w sposób pozwalający nam na łatwą identyfikację, dla jakiej usługi dany slot został stworzony.


postgres=# select pg_promote();
postgres=# select pg_create_physical_replication_slot('pg1');

Sprawdzić wpisy w pg_hba.conf na pg2 czy użytkownik replicator ma możliwość podłączenia się do bazy postgres i jako replikacja. Dodajemy je, jeżeli nie istnieją, podając wewnętrzny adres IP serwera pg1 , czyli naszego dawnego mastera:


host    replication       replicator      192.168.0.26/32      scram-sha-256
host postgres replicator 192.168.0.26/32 scram-sha-256

Po edycji pg_hba.conf pamiętajmy o przeładowaniu konfiguracji na pg2.


psql -c "select pg_reload_conf()"

[host 1: oryginalny serwer master]


Synchronizacja katalogu z danymi ze starej instancji primary (pg1) z nową (pg2) za pomocą pg_rewind. Poniższe wykonujemy jako użytkownik systemowy postgres na dawnym serwerze primary - czyli pg1:


/usr/lib/postgresql/15/bin/pg_rewind -c -R -D /data_pg/ --source-server='host=<X.X.X.X adres nowego primary> port=5432 user=replicator password=tajnehaslo application_name=pg1 dbname=postgres'

Sprawdzenie w postgresql.auto.conf, czy konfiguracja recovery (restore_command) dla replikacji wal-shipping i ustawnienia do replikacji strumieniowej (primary_conninfo, primary_slot_name oraz restore_command) są poprawne na pg1. Jeżeli któryś z nich nie istnieje, należy go dodać.

Parametr primary_conninfo powinien odnosić się w connection stringu do odpowiedniego hosta (host=x.x.x.x) oraz zawierać application_name z nazwą dla instancji, która pozwoli nam na jej szybką identyfikację, np. "pg1", a w primary_slot_name (który dopisujemy, jeśli go nie ma) powinniśmy mieć nazwę slota, który wcześniej stworzyliśmy na pg2, czyli "pg1".

Przykładowo:


restore_command = 'pgbackrest --stanza=test --pg1-path=/data_pg --archive-get %f "%p"'
primary_conninfo = 'host=10.1.0.11 application_name=pg1 ...'
primary_slot_name = 'pg1'

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

Ostatnim krokiem będzie weryfikacja, czy replikacja działa poprawnie, za pomocą odpowiednich zapytań, z widoku pg_stat_replication na primary (pg2) lub pg_stat_wal_receiver na replica (pg1).


select * from pg_stat_replication;
select * from pg_stat_wal_receiver;

Komentarze (0)

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

Brak komentarzy...