PostgreSQL
 sql >> база данни >  >> RDS >> PostgreSQL

Логическа репликация на PostgreSQL

PostgreSQL 10 дойде с добре дошло допълнение на логическата репликация отличителен белег. Това осигурява по-гъвкаво и по-лесно средство за репликиране на вашите таблици от обикновения механизъм за стрийминг репликация. Въпреки това, той има някои ограничения, които могат или не могат да ви попречат да го използвате за репликация. Прочетете, за да научите повече.

Какво е все пак логическата репликация?

Поточно репликация

Преди v10 единственият начин за репликиране на данни, намиращи се в сървър, беше да репликирате промените на ниво WAL. По време на работа, PostgreSQL сървър (основният ) генерира поредица от WAL файлове. Основната идея е тези файлове да се прехвърлят на друг PostgreSQL сървър (готовност ), който взема тези файлове и ги „възпроизвежда“, за да пресъздаде същите промени, случващи се на основния сървър. Сървърът в режим на готовност остава в режим само за четене, нареченрежим на възстановяване , и всички промени в сървъра в режим на готовност не разрешено (тоест, разрешени са само транзакции само за четене).

Процесът на изпращане на WAL файловете от основния към режим на готовност се нарича logshipping , и може да се направи ръчно (скриптове за rsync промени от $PGDATA/pg_wal на основния директория към вторичен) или чрез поточно репликация .Различни функции като слотове за репликация , отзив в режим на готовност и отказ при отказ бяха добавени с течение на времето за подобряване на надеждността и полезността на стрийминг репликацията.

Една голяма „характеристика“ на стрийминг репликацията е, че е всичко или нищо. Всички промени във всички обекти от всички бази данни на първичната трябва да бъдат изпратени в режим на готовност, а резервният трябва да импортира всяка промяна. Не е възможно селективно да репликирате част от вашата база данни.

Логическа репликация

Логическа репликация , добавен във v10, прави възможно да се направи точно това – репликиране само на набор от таблици на други сървъри. Най-добре се обяснява с пример. Да вземем база данни, наречена src в сървър и създайте таблица в него:

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

Също така ще създадем публикация в тази база данни (обърнете внимание, че трябва да имате права на суперпотребител, за да направите това):

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

Сега да преминем към база данни dst на друг сървър и създайте подобна таблица:

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

И сега настройваме абонамент тук, който ще се свърже с публикацията в източника и ще започне да изтегля промените. (Обърнете внимание, че трябва да имате userrepuser на изходния сървър с привилегии за репликация и достъп за четене до таблиците.)

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

Промените се синхронизират и можете да видите редовете от страната на местоназначението:

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

Таблицата на местоназначението има допълнителна колона “col3”, която не се докосва от нейната пликация. Промените се репликират „логически“ – така че, стига да е възможно да се вмъкне ред само с t.col1 и t.col2, процесът на репликация ще се извърши.

В сравнение с поточно репликацията, функцията за логическа репликация е перфектна за репликиране, да речем, на единична схема или набор от таблици в конкретна база данни на друг сървър.

Репликация на промени в схемата

Да приемем, че имате приложение Django с набор от таблици, живеещи в изходната база данни. Лесно и ефективно е да настроите логическа репликация, за да прехвърлите всички тези таблици в друг сървър, където можете да изпълнявате отчети, анализи, пакетни задания, приложения за поддръжка на разработчици/клиенти и други подобни, без да докосвате „реалните“ данни и без да засягате производственото приложение.

Вероятно най-голямото ограничение на логическата репликация в момента е, че тя не реплицира промените на схемата – всяка DDL команда, изпълнена в изходната база данни, не причинява подобна промяна в базата данни местоназначение, за разлика от стрийминг репликацията. Например, ако направим това в изходната база данни:

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

това се регистрира в целевия регистрационен файл:

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

и репликацията спира. Колоната трябва да се добави „ръчно“ на местоназначението, в който момент репликацията се възобновява:

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)

Това означава, че ако вашето Django приложение е добавило нова функция, която се нуждае от нови колони или таблици, и вие трябва да стартирате django-admin migrate в изходната база данни, настройката за репликация прекъсва.

Заобиколно решение

Най-добрият ви залог за отстраняване на този проблем е да поставите на пауза абонамента на дестинацията, първо да мигрирате дестинацията, след това източника и след това да възобновите абонамента. Можете да поставите на пауза и да възобновите абонаментите по следния начин:

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

-- resume replication
ALTER SUBSCRIPTION mysub ENABLE;

Ако се добавят нови таблици и публикацията ви не е „ЗА ВСИЧКИ ТАБЛИЦИ“, ще трябва да ги добавите към публикацията ръчно:

ALTER PUBLICATION mypub ADD TABLE newly_added_table;

Също така ще трябва да „опресните“ абонамента от страната на местоназначението, за да кажете на Postgres да започне да синхронизира новите таблици:

dst=# ALTER SUBSCRIPTION mysub REFRESH PUBLICATION;
ALTER SUBSCRIPTION

Поредици

Помислете за тази таблица в източника, имаща последователност:

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)

Последователността s_a_seq беше създаден, за да подкрепи a колона от serial type. Това генерира автоматично нарастващите стойности за s.a . Сега нека репликираме това в dst и вмъкнете друг ред:

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)

Опа, какво се случи току-що? Дестинацията се опита да стартира последователността от нулата и генерира стойност 1 за a . Това е така, защото логическата репликация не репликира стойностите за последователности, тъй като следващата стойност на тази последователност не се съхранява в самата таблица.

Заобиколно решение

Ако мислите за това логично, не можете да промените една и съща стойност на „автоматично нарастване“ от две места без двупосочна синхронизация. Ако наистина имате нужда от нарастващо число във всеки ред на таблица и трябва да вмъкнете в тази таблица от множество сървъри, можете:

  • използвайте външен източник за номера, като ZooKeeper или etcd,
  • използвайте диапазони, които не се припокриват – например първият сървър генерира и вмъква числа в диапазона от 1 до 1 милион, вторият в диапазона от 1 милион до 2 милиона и т.н.

Таблици без уникални редове

Нека опитаме да създадем таблица без първичен ключ и да я репликираме:

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

И редовете вече са на дестинацията:

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

Сега нека опитаме да изтрием втория ред в източника:

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.

Това се случва, защото дестинацията няма да може да идентифицира уникално реда, който трябва да бъде изтрит (или актуализиран) без първичен ключ.

Заобиколно решение

Разбира се, можете да промените схемата, за да включите първичен ключ. В случай, че не искате да направите това, вие ALTER TABLE и задайте „идентификация на репликата“ на пълния ред или уникален индекс. Например:

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

Изтриването вече е успешно, както и репликацията също:

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

Ако вашата таблица наистина няма начин за уникално идентифициране на редове, тогава вие сте затънали. Вижте раздела REPLICA IDENTITY на ALTERTABLE за повече информация.

Различно разделени дестинации

Не би ли било хубаво да има източник, който е разделен по един начин, а дестинация по различен начин? Например, при източника можем да запазим дялове за всеки месец, а в местоназначението за всяка година. Предполага се, че дестинацията е по-голяма машина и трябва да съхраняваме исторически данни, но рядко се нуждаем от тези данни.

Нека създадем таблица с месечни дялове в източника:

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

И опитайте да създадете таблица с годишни дялове на местоназначението:

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 се оплаква, че се нуждае от таблицата на дяловете за януари 2019 г., която нямаме намерение да създаваме на дестинацията.

Това се случва, защото логическата репликация работи не на ниво основна таблица, а на ниво дъщерна таблица. Няма реално решение за това – ако използвате повторно дялове, йерархията на дяловете трябва да е една и съща от двете страни на настройката на алогичната репликация.

Големи обекти

Големите обекти не могат да бъдат репликирани с помощта на логическа репликация. Това вероятно не е голяма работа в днешно време, тъй като съхраняването на големи предмети не е обичайна съвременна практика. Също така е по-лесно да съхранявате препратка към голям обект на някакво външно, излишно хранилище (като NFS, S3 и т.н.) и да репликирате тази препратка, вместо да съхранявате и репликирате самия обект.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Как да декодирате журналите за грешки на PostgreSQL

  2. psql:не можа да се свърже със сървъра:Връзката е отказана Грешка при свързване към отдалечена база данни

  3. Как мога да променя съществуващата колона като идентичност в PostgreSQL 11.1

  4. Как да мигрираме съществуваща Postgres таблица към разделена таблица възможно най-прозрачно?

  5. Симулирате CREATE DATABASE, АКО НЕ СЪЩЕСТВУВА за PostgreSQL?