postgres logica replication ddl - ghdrako/doc_snipets GitHub Wiki
- wiki https://wiki.postgresql.org/wiki/Logical_replication_of_DDLs
- pgsql-hackers thread - Support logical replication of DDLs
- pgsql-hackers thread - Deparsing utility commands
- pgsql-hackers thread - Support logical replication of global object commands
- pgsql-hackers thread - Initial schema sync for logical replication
- PG documentation for Logical Replication
- PG documentation for CREATE PUBLICATION
inicjalnie
pg_dump --schema-only
replicate_ddl_command()
SELECT pglogical.replicate_ddl_command(
'ALTER TABLE users ADD COLUMN last_seen_at timestamptz'
);
Co się działo:
- DDL wykonywał się lokalnie
- pglogical wysyłał tekst polecenia
- subscriber wykonywał ten sam DDL
Gdzie dziala:
- dodanie kolumny
- proste CREATE TABLE
- proste indeksy
Znane problemy:
- złożone ALTER-y
- zmiany typów
- indeksy CONCURRENTLY
- DDL zależne od danych
- race conditions
event trigger + tabela zmian
DDL → event trigger → ddl_queue → logical replication → apply worker → EXECUTE
CREATE TABLE ddl_queue (
id bigserial primary key,
ddl text,
executed boolean default false,
created_at timestamptz default now()
);
CREATE OR REPLACE FUNCTION capture_ddl()
RETURNS event_trigger
LANGUAGE plpgsql
AS $$
DECLARE
obj record;
BEGIN
FOR obj IN SELECT * FROM pg_event_trigger_ddl_commands()
LOOP
INSERT INTO ddl_queue(ddl)
VALUES (obj.command_tag || ' ' || obj.object_identity);
END LOOP;
END;
$$;
CREATE EVENT TRIGGER ddl_capture
ON ddl_command_end
EXECUTE FUNCTION capture_ddl();
Na subscriberze
CREATE OR REPLACE FUNCTION apply_ddl_queue()
RETURNS void
LANGUAGE plpgsql
AS $$
DECLARE
r RECORD;
BEGIN
FOR r IN
SELECT id, ddl
FROM ddl_queue
WHERE executed = false
ORDER BY id
FOR UPDATE
LOOP
BEGIN
-- wykonanie DDL
EXECUTE r.ddl;
-- oznaczenie jako wykonane
UPDATE ddl_queue
SET executed = true,
executed_at = now()
WHERE id = r.id;
EXCEPTION WHEN others THEN
-- logowanie i retry w przyszłości
RAISE WARNING 'Failed to execute DDL id %: %, will retry', r.id, SQLERRM;
-- nie przerywamy pętli, zostanie powtórzone przy następnym run
END;
END LOOP;
END;
$$;
CREATE OR REPLACE FUNCTION ddl_queue_trigger()
RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
-- wywołanie worker’a dla nowego wiersza
PERFORM apply_ddl_queue();
RETURN NEW;
END;
$$;
CREATE TRIGGER ddl_queue_after_insert
AFTER INSERT ON ddl_queue
FOR EACH ROW
EXECUTE FUNCTION ddl_queue_trigger();
Problemy:
- Replikacja nie jest synchroniczna
- Logical replication ma lag.
- Trigger odpali się dopiero gdy wiersz dotrze do subscriber.
- Jeśli DML na tabeli pojawi się przed DDL, może dojść do błędu. Insert juz z nowa kolumna dla ktorej jeszcze altr nie dotarl
- Race conditions
- Trigger odpala się w kontekście insertu
- Jeśli kilka DDL wpada w krótkim czasie:
- worker może próbować wykonać je równolegle
- może dojść do deadlock lub error (column exists)
- Blokady i long-running DDL
- ALTER TABLE na dużej tabeli może trwać długo
- Trigger blokuje insert do ddl_queue, aż worker skończy
- Może spowalniać replikację lub nawet powodować timeout
- Idempotency
- Każdy insert wywołuje worker
- Jeśli DDL już było wykonane (np. retry), trzeba obsłużyć błąd w workerze
- Dlatego zawsze stosuj IF NOT EXISTS / try-catch w workerze
- Wielkość kolejki
- Trigger odpala się przy każdym wierszu
- Przy dużym napływie DDL może to spowodować dużo concurrency i logów
- Często lepiej batchować w pętli w workerze niż wywoływać przy każdej insercji