sql >> Databáze >  >> RDS >> PostgreSQL

PostgreSQL Logical Replication Gotchas

PostgreSQL 10 přišel s vítaným přidáním logické replikace Vlastnosti. To poskytuje flexibilnější a snazší prostředky pro replikaci vašich tabulek než běžný mechanismus streamování replikace. Má však určitá omezení, která vám mohou nebo nemusí bránit v jeho použití pro replikaci. Čtěte dále a dozvíte se více.

Co je to vlastně logická replikace?

Streamování replikace

Před verzí 10 bylo jediným způsobem, jak replikovat data umístěná na serveru, replikovat změny na úrovni WAL. Během svého provozu se PostgreSQL server (primární ) generuje sekvenci souborů WAL. Základní myšlenkou je přenést tyto soubory na jiný PostgreSQL server (pohotovostní ), který vezme do těchto souborů a „přehraje“ je, aby znovu vytvořil stejné změny, které se odehrávají na primárním serveru. Pohotovostní server zůstává v režimu pouze pro čtení, který se nazývárežim obnovy a jakékoli změny na pohotovostním serveru nejsou povoleno (to znamená, že jsou povoleny pouze transakce pouze pro čtení).

Proces odesílání souborů WAL z primárního do pohotovostního režimu se nazývá logshipping a lze to provést ručně (skripty pro synchronizaci změn z primárního $PGDATA/pg_wal adresář do sekundárních) nebo prostřednictvím replikace streamování .Různé funkce jako replikační sloty , zpětná vazba v pohotovostním režimu a failover byly časem přidány, aby se zlepšila spolehlivost a užitečnost streamingreplication.

Jednou velkou „funkcí“ streamování replikace je to, že je to všechno nebo nic. Všechny změny všech objektů ze všech databází na primární musí být odeslány do pohotovostního režimu a pohotovostní režim musí importovat každou změnu. Není možné selektivně replikovat část vaší databáze.

Logická replikace

Logická replikace , přidaný ve verzi 10, umožňuje právě to – replikovat pouze sadu tabulek na jiné servery. Nejlépe se to vysvětluje na příkladu. Vezměme si databázi nazvanou src na serveru a vytvořte v něm tabulku:

src=> CREATE TABLE t (col1 int, col2 int);
CREATE TABLE
src=> INSERT INTO t VALUES (1,10), (2,20), (3,30);
INSERT 0 3

Chystáme se také vytvořit publikaci v této databázi (všimněte si, že k tomu musíte mít oprávnění superuživatele):

src=# CREATE PUBLICATION mypub FOR ALL TABLES;
CREATE PUBLICATION

Nyní pojďme k databázi dst na jiném serveru a vytvořte podobnou tabulku:

dst=# CREATE TABLE t (col1 int, col2 int, col3 text NOT NULL DEFAULT 'foo');
CREATE TABLE

A nyní nastavujeme předplatné zde se připojí k publikaci na zdroji a začnou načítat změny. (Všimněte si, že musíte mít uživatelerepuser na zdrojovém serveru s replikačními právy a přístupem pro čtení k tabulkám.)

dst=# CREATE SUBSCRIPTION mysub CONNECTION 'user=repuser password=reppass host=127.0.0.1 port=5432 dbname=src' PUBLICATION mypub;
NOTICE:  created replication slot "mysub" on publisher
CREATE SUBSCRIPTION

Změny jsou synchronizovány a na cílové straně můžete vidět řádky:

dst=# SELECT * FROM t;
 col1 | col2 | col3
------+------+------
    1 |   10 | foo
    2 |   20 | foo
    3 |   30 | foo
(3 rows)

Cílová tabulka má navíc sloupec „col3“, kterého se replikace nedotýká. Změny se replikují „logicky“ – takže pokud je možné vložit řádek se samotnými t.col1 a t.col2, proces replikace se tak stane.

Ve srovnání se streamovanou replikací je funkce logické replikace perfektní pro replikaci, řekněme, jednoho schématu nebo sady tabulek v konkrétní databázi na jiný server.

Replikace změn schématu

Předpokládejme, že máte aplikaci Django se sadou tabulek žijících ve zdrojové databázi. Je snadné a efektivní nastavit logickou replikaci pro přenesení všech těchto tabulek na jiný server, kde můžete spouštět reporting, analýzy, dávkové úlohy, aplikace pro podporu vývojářů/zákazníků a podobně, aniž byste se dotkli „skutečných“ dat a aniž byste ovlivnili produkční aplikaci.

Pravděpodobně největším omezením logické replikace je v současnosti to, že nereplikuje změny schématu – žádný příkaz DDL provedený ve zdrojové databázi nezpůsobí podobnou změnu v cílové databázi, na rozdíl od streamingreplication. Pokud to například provedeme ve zdrojové databázi:

src=# ALTER TABLE t ADD newcol int;
ALTER TABLE
src=# INSERT INTO t VALUES (-1, -10, -100);
INSERT 0 1

toto se zaznamená do cílového souboru protokolu:

ERROR:  logical replication target relation "public.t" is missing some replicated columns

a replikace se zastaví. Sloupec musí být přidán „ručně“ na místo určení, kdy se replikace obnoví:

dst=# SELECT * FROM t;
 col1 | col2 | col3
------+------+------
    1 |   10 | foo
    2 |   20 | foo
    3 |   30 | foo
(3 rows)

dst=# ALTER TABLE t ADD newcol int;
ALTER TABLE
dst=# SELECT * FROM t;
 col1 | col2 | col3 | newcol
------+------+------+--------
    1 |   10 | foo  |
    2 |   20 | foo  |
    3 |   30 | foo  |
   -1 |  -10 | foo  |   -100
(4 rows)

To znamená, že pokud vaše aplikace Django přidala novou funkci, která potřebuje nové sloupce nebo tabulky, a musíte spustit django-admin migrate ve zdrojové databázi se nastavení replikace přeruší.

Řešení

Nejlepším řešením, jak tento problém vyřešit, by bylo pozastavit předplatné na cílovém místě, nejprve migrovat cíl, poté zdroj a poté předplatné obnovit. Odběry můžete pozastavit a obnovit takto:

-- pause replication (destination side)
ALTER SUBSCRIPTION mysub DISABLE;

-- resume replication
ALTER SUBSCRIPTION mysub ENABLE;

Pokud jsou přidány nové tabulky a vaše publikace není „PRO VŠECHNY TABULKY“, budete je muset přidat do publikace ručně:

ALTER PUBLICATION mypub ADD TABLE newly_added_table;

Budete také muset „obnovit“ předplatné na cílové straně, abyste mohli Postgresu sdělit, že má začít synchronizovat nové tabulky:

dst=# ALTER SUBSCRIPTION mysub REFRESH PUBLICATION;
ALTER SUBSCRIPTION

Sekvence

Zvažte tuto tabulku ve zdroji, která má sekvenci:

src=# CREATE TABLE s (a serial PRIMARY KEY, b text);
CREATE TABLE
src=# INSERT INTO s (b) VALUES ('foo'), ('bar'), ('baz');
INSERT 0 3
src=# SELECT * FROM s;
 a |  b
---+-----
 1 | foo
 2 | bar
 3 | baz
(3 rows)

src=# SELECT currval('s_a_seq'), nextval('s_a_seq');
 currval | nextval
---------+---------
       3 |       4
(1 row)

Sekvence s_a_seq byl vytvořen na podporu a sloupec serial type.To vygeneruje hodnoty automatického zvýšení pro s.a . Nyní to zopakujme do dst a vložte další řádek:

dst=# SELECT * FROM s;
 a |  b
---+-----
 1 | foo
 2 | bar
 3 | baz
(3 rows)

dst=# INSERT INTO s (b) VALUES ('foobaz');
ERROR:  duplicate key value violates unique constraint "s_pkey"
DETAIL:  Key (a)=(1) already exists.
dst=#  SELECT currval('s_a_seq'), nextval('s_a_seq');
 currval | nextval
---------+---------
       1 |       2
(1 row)

Jejda, co se právě stalo? Cíl se pokusil spustit sekvenci od začátku a vygeneroval hodnotu 1 pro a . Je to proto, že logická replikace nereplikuje hodnoty pro sekvence, protože další hodnota sekvence není uložena v tabulce samotné.

Řešení

Pokud o tom přemýšlíte logicky, nemůžete upravit stejnou hodnotu „autoincrement“ ze dvou míst bez obousměrné synchronizace. Pokud opravdu potřebujete zvýšit číslo v každém řádku tabulky a potřebujete vložit do této tabulky z více serverů, můžete:

  • pro číslo použijte externí zdroj, jako je ZooKeeper nebo etcd,
  • použijte nepřekrývající se rozsahy – například první server generuje a vkládá čísla v rozsahu 1 až 1 milion, druhý v rozsahu 1 milion až 2 miliony atd.

Tabulky bez jedinečných řádků

Zkusme vytvořit tabulku bez primárního klíče a zopakovat ji:

src=# CREATE TABLE nopk (foo text);
CREATE TABLE
src=# INSERT INTO nopk VALUES ('new york');
INSERT 0 1
src=# INSERT INTO nopk VALUES ('boston');
INSERT 0 1

A řádky jsou nyní také v cíli:

dst=# SELECT * FROM nopk;
   foo
----------
 new york
 boston
(2 rows)

Nyní zkusme smazat druhý řádek u zdroje:

src=# DELETE FROM nopk WHERE foo='boston';
ERROR:  cannot delete from table "nopk" because it does not have a replica identity and publishes deletes
HINT:  To enable deleting from the table, set REPLICA IDENTITY using ALTER TABLE.

K tomu dochází, protože cíl nebude schopen jednoznačně identifikovat řádek, který je třeba odstranit (nebo aktualizovat), bez primárního klíče.

Řešení

Schéma můžete samozřejmě změnit tak, aby zahrnovalo primární klíč. V případě, že to nechcete udělat, ALTER TABLE a nastavte „identifikaci repliky“ na celý řádek nebo jedinečný index. Například:

src=# ALTER TABLE nopk REPLICA IDENTITY FULL;
ALTER TABLE
src=# DELETE FROM nopk WHERE foo='boston';
DELETE 1

Odstranění je nyní úspěšné a replikace také:

dst=# SELECT * FROM nopk;
   foo
----------
 new york
(1 row)

Pokud vaše tabulka opravdu nemá žádný způsob, jak jednoznačně identifikovat řádky, pak jste na tom špatně. Další informace naleznete v části REPLICA IDENTITY v ALTERTABLE.

Různě rozdělené destinace

Nebylo by hezké mít zdroj, který je rozdělen jedním způsobem a cíl jiným způsobem? Například u zdroje můžeme udržovat oddíly pro každý měsíc a v cíli pro každý rok. Cíl je pravděpodobně větší stroj a my potřebujeme uchovávat historická data, ale potřebujeme je jen zřídka.

Vytvořme měsíční rozdělenou tabulku u zdroje:

src=# CREATE TABLE measurement (
src(#     logdate         date not null,
src(#     peaktemp        int
src(# ) PARTITION BY RANGE (logdate);
CREATE TABLE
src=#
src=# CREATE TABLE measurement_y2019m01 PARTITION OF measurement
src-# FOR VALUES FROM ('2019-01-01') TO ('2019-02-01');
CREATE TABLE
src=#
src=# CREATE TABLE measurement_y2019m02 PARTITION OF measurement
src-# FOR VALUES FROM ('2019-02-01') TO ('2019-03-01');
CREATE TABLE
src=#
src=# GRANT SELECT ON measurement, measurement_y2019m01, measurement_y2019m02 TO repuser;
GRANT

A zkuste vytvořit roční tabulku rozdělenou na oddíly v cíli:

dst=# CREATE TABLE measurement (
dst(#     logdate         date not null,
dst(#     peaktemp        int
dst(# ) PARTITION BY RANGE (logdate);
CREATE TABLE
dst=#
dst=# CREATE TABLE measurement_y2018 PARTITION OF measurement
dst-# FOR VALUES FROM ('2018-01-01') TO ('2019-01-01');
CREATE TABLE
dst=#
dst=# CREATE TABLE measurement_y2019 PARTITION OF measurement
dst-# FOR VALUES FROM ('2019-01-01') TO ('2020-01-01');
CREATE TABLE
dst=#
dst=# ALTER SUBSCRIPTION mysub REFRESH PUBLICATION;
ERROR:  relation "public.measurement_y2019m01" does not exist
dst=#

Postgres si stěžuje, že potřebuje tabulku oddílů pro leden 2019, kterou nemáme v úmyslu vytvořit v cíli.

K tomu dochází, protože logická replikace nefunguje na úrovni základní tabulky, ale na úrovni podřízené tabulky. Neexistuje pro to žádné skutečné řešení – pokud používáte oddíly znovu, hierarchie oddílů musí být na obou stranách nastavení alogické replikace stejná.

Velké objekty

Velké objekty nelze replikovat pomocí logické replikace. V dnešní době to pravděpodobně není velký problém, protože skladování velkých předmětů není běžnou moderní praxí. Je také snazší uložit odkaz na velký objekt na nějaké externí, redundantní úložiště (jako NFS, S3 atd.) a replikovat tento odkaz místo ukládání a replikace samotného objektu.


  1. 6 důvodů, proč může Microsoft Access pomoci vaší firmě

  2. SET SQLBLANKLINES:Jak povolit prázdné řádky v SQLcl &SQL*Plus

  3. Vytvořte vztah v SQL Server 2017

  4. Recenze knihy :Benjamin Nevarez:Query Tuning &Optimization