Blog JSystems - uwalniamy wiedzę!

Szukaj

PostgreSQL

Po failoverze replikacja logiczna przestaje działać - slot logiczny został na starym primary

W skrócie

  • Problem: po przełączeniu primary subskrypcja przestaje odbierać zmiany, a w logu subskrybenta co kilka sekund pojawia się błąd replication slot does not exist.
  • Dlaczego: slot replikacji logicznej istnieje tylko na primary i nie jest przenoszony ani przez WAL, ani przez pg_basebackup, więc awansowana replika go nie ma.
  • Rozwiązanie: w PostgreSQL 16 i starszych odbudowujemy subskrypcję z kopią danych albo przed planowanym switchoverem przenosimy slot, a od PostgreSQL 17 włączamy synchronizację slotów na replikę.

Ten problem dotyczy układu, w którym źródłem replikacji logicznej jest klaster wysokiej dostępności: primary z repliką fizyczną, a do tego subskrybent, na przykład baza raportowa. Failover fizyczny, czyli awaryjne przejęcie roli primary przez replikę, przechodzi gładko, aplikacja pisze już do nowego primary, a baza raportowa po cichu przestaje dostawać zmiany. Odtworzyliśmy to na PostgreSQL 16. Pokażemy, dlaczego sam nowy slot nie wystarcza i jak wznowić replikację bez luki w danych, osobno dla wersji 16 i 17.

Jak to wygląda w praktyce

Po przełączeniu adresu subskrypcji na nowy primary (ALTER SUBSCRIPTION sub_orders CONNECTION '...') proces apply startuje co kilka sekund i od razu kończy pracę. Log subskrybenta:

LOG:  logical replication apply worker for subscription "sub_orders" has started
ERROR:  could not start WAL streaming: ERROR:  replication slot "sub_orders" does not exist
LOG:  background worker "logical replication worker" (PID 1029446) exited with exit code 1

W pg_stat_subscription kolumna pid jest pusta, a SELECT slot_name, slot_type FROM pg_replication_slots; na nowym primary nie zwraca żadnego wiersza, choć na starym primary slot sub_orders typu logical był aktywny.

Dlaczego tak się dzieje

Subskrypcja odbiera zmiany przez slot replikacji logicznej, czyli znacznik na serwerze źródłowym, który pamięta, do którego miejsca w WAL (dzienniku zmian) subskrybent potwierdził odbiór, i pilnuje, żeby potrzebne pliki WAL nie zniknęły. Slot jest zapisany w katalogu pg_replslot primary. Nie trafia do WAL, więc replika fizyczna go nie dostaje, a pg_basebackup kopiuje pg_replslot jako pusty katalog. Po awansie replika nie ma slotu. PostgreSQL 16 pozwala już utworzyć slot logiczny na replice (lekcja, przygotowana na starszej wersji, pokazuje jeszcze błąd logical decoding cannot be used while in recovery), ale taki slot nie śledzi pozycji slotu z primary.

Utworzenie slotu na nowym primary wygląda na naprawę, lecz nowy slot zaczyna od chwili utworzenia. W naszym teście wiersz wstawiony na nowym primary między failoverem a utworzeniem slotu (id = 1002) nigdy nie dotarł do subskrybenta, a późniejszy (id = 1003) dotarł. Replikacja działała dalej, z luką, której nic nie zgłosiło.

Jak to rozwiązać krok po kroku

  1. Potwierdź przypadek na subskrybencie: SELECT subname, subenabled, subslotname FROM pg_subscription; i SELECT subname, pid, received_lsn FROM pg_stat_subscription;. Pusty pid i błąd o brakującym slocie w logu to ten scenariusz.
  2. Po nieplanowanym failoverze w PostgreSQL 16 i starszych odbuduj subskrypcję z kopią danych. Samo DROP SUBSCRIPTION się nie uda (could not drop replication slot "sub_orders" on publisher), więc najpierw odłącz slot: ALTER SUBSCRIPTION sub_orders DISABLE; i ALTER SUBSCRIPTION sub_orders SET (slot_name = NONE);, potem DROP SUBSCRIPTION sub_orders; i TRUNCATE orders; dla tabel z publikacji.
  3. Utwórz subskrypcję na nowy primary: CREATE SUBSCRIPTION sub_orders CONNECTION 'host=NOWY_PRIMARY dbname=shop user=...' PUBLICATION pub_orders;. Domyślne copy_data = true skopiuje tabele od nowa, a slot powstanie na nowym primary.
  4. Przed planowanym switchoverem w PostgreSQL 16 możesz przenieść slot techniką z lekcji. Dokumentacja PostgreSQL jej nie opisuje, więc stosuj ją tylko w oknie serwisowym. Obie instancje muszą mieć wal_level = logical (kontekst postmaster, restart), a replika także hot_standby_feedback = on i fizyczny slot w primary_slot_name. Bez nich primary może usunąć wiersze katalogu systemowego potrzebne skopiowanemu slotowi: w naszym teście VACUUM katalogu na primary unieważnił slot na replice, a jej log pokazał invalidating obsolete replication slot "sub_orders". Tuż przed przełączeniem skopiuj katalog $PGDATA/pg_replslot/sub_orders z primary na replikę i zrestartuj replikę, bo sloty są wczytywane przy starcie. Na replice SELECT slot_name, wal_status, conflicting FROM pg_replication_slots; musi pokazać w kolumnach wal_status i conflicting wartości reserved i f. Potem zatrzymaj primary, awansuj replikę i zmień CONNECTION subskrypcji. U nas wiersze sprzed i po przełączeniu dotarły bez strat. Skopiowany slot od razu zatrzymuje WAL na replice, więc nie trzymaj go na zapas.
  5. Od PostgreSQL 17 korzystaj z synchronizacji slotów (failover slots). Subskrypcję twórz z WITH (failover = true). Na replice dokumentacja wymaga sync_replication_slots = on, hot_standby_feedback = on, fizycznego slotu w primary_slot_name i dbname w primary_conninfo, a na primary zdecydowanie zaleca wpisanie tego slotu do synchronized_standby_slots, żeby subskrybent nie wyprzedził repliki. Przed przełączeniem sprawdź na replice SELECT slot_name, (synced AND NOT temporary AND invalidation_reason IS NULL) AS failover_ready FROM pg_replication_slots;, w kolumnie failover_ready ma być t. Po awansie nadal zmieniasz CONNECTION subskrypcji na nowy primary. W wersji 16 tego mechanizmu nie ma.
  6. W klastrze Patroni możesz zdefiniować slot jako stały: sekcja slots w konfiguracji dynamicznej z type: logical, database i plugin. Patroni kopiuje go na repliki (z ich restartem), przesuwa jego pozycję co loop_wait sekund i sam włącza hot_standby_feedback. Działa to od PostgreSQL 11. Dokumentacja Patroni uprzedza, że po failoverze część zmian może dotrzeć do subskrybenta drugi raz.

Jak sprawdzić, że zadziałało

Po odbudowie subskrypcji porównaj dane na subskrybencie z nowym primary i sprawdź stan synchronizacji tabel:

reporting=# SELECT count(*), max(id) FROM orders;
 count | max
-------+------
  1003 | 1003
(1 row)

reporting=# SELECT srrelid::regclass, srsubstate FROM pg_subscription_rel;
 srrelid | srsubstate
---------+------------
 orders  | r
(1 row)

Stan r (ready) oznacza zakończoną kopię początkową i zwykłą replikację. Na nowym primary SELECT slot_name, active, confirmed_flush_lsn FROM pg_replication_slots; pokazuje slot z active = t, a confirmed_flush_lsn rośnie po każdej nowej zmianie. Błędy o brakującym slocie przestają trafiać do logu subskrybenta.

Powiązane problemy

Wróć do listy: 112 najczęstszych pytań i problemów z PostgreSQL

Najczęściej zadawane pytania

Dlaczego nie wystarczy utworzyć nowego slotu na nowym primary?
Nowy slot zaczyna od chwili utworzenia, więc zmiany zapisane między failoverem a jego utworzeniem nie trafią do subskrybenta. W naszym teście wiersz z tego okna nie dotarł, a replikacja działała dalej bez żadnego błędu.
Czy pg_basebackup kopiuje sloty replikacji?
Nie. Katalog pg_replslot trafia do kopii jako pusty, a informacje o slotach nie są zapisywane w WAL. Dlatego replika zbudowana przez pg_basebackup nie ma slotów primary i po awansie subskrypcja nie ma się do czego podłączyć.
Od której wersji PostgreSQL sloty logiczne przechodzą na replikę automatycznie?
Od PostgreSQL 17. Subskrypcja musi mieć opcję failover = true, a replika sync_replication_slots = on, hot_standby_feedback = on, primary_slot_name i dbname w primary_conninfo. Dokumentacja zaleca też synchronized_standby_slots na primary. W PostgreSQL 16 slot trzeba przenieść albo odbudować subskrypcję.
Dlaczego DROP SUBSCRIPTION kończy się błędem po failoverze?
DROP SUBSCRIPTION próbuje usunąć slot na serwerze źródłowym, a nowy primary go nie ma. Wyłącz subskrypcję, odłącz ją od slotu przez ALTER SUBSCRIPTION ... SET (slot_name = NONE) i dopiero wtedy ją usuń.
Czy Patroni chroni sloty logiczne przy failoverze?
Tak, jeśli zdefiniujesz je jako stałe w sekcji slots konfiguracji dynamicznej. Patroni kopiuje je na repliki i przesuwa ich pozycję co loop_wait sekund, ale część zmian może po failoverze dotrzeć do subskrybenta drugi raz.

Komentarze (0)

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

Brak komentarzy...