Итак. В рабочем варианте №1 докатились до того, что репликация поехала, но потом заткнулась на изменении структуры БД. Тема интересная и актуальная. Нужна методика поддержки репликации в рабочем состоянии в условиях постоянного изменения структуры БД. Приступим.
На своей локальной машине сделал БД alexsf_test и схему alexsf. Плюс ко всему:
ALTER ROLE alexsf SUPERUSER;
CREATE LANGUAGE plpgsql;
CREATE LANGUAGE plperlu;
и получаю: ERROR: не получилось загрузить библиотеку "/usr/lib/postgresql/plperl.so": libperl.so: невозможно открыть разделяемый объектный файл: Нет такого файла или каталога
SQL state: 58P01
Бл... Как всегда без приключений никуда... Такая библиотека есть, вот она:
/usr/lib/perl5/5.10.0/i586-linux-thread-multi/CORE/libperl.so
Че за херня!!! Как всегда отделываемся костылем:
ln -s /usr/lib/perl5/5.10.0/i586-linux-thread-multi/CORE/libperl.so /usr/lib/libperl.so
Теперь наполняем схему объектами и данными:
CREATE TABLE fio(
pk integer NOT NULL,
f character varying(100),
i character varying(100),
o character varying(100),
CONSTRAINT pk_fio PRIMARY KEY (pk)
)
WITH (OIDS=FALSE);
ALTER TABLE fio OWNER TO alexsf;
CREATE TABLE car(
pk integer NOT NULL,
model character varying(100),
nomer character varying(100),
CONSTRAINT pk_car PRIMARY KEY (pk)
)
WITH (OIDS=FALSE);
ALTER TABLE car OWNER TO alexsf;
CREATE TABLE telefon(
pk integer NOT NULL,
model character varying(100),
"operator" character varying(100),
nomer character varying(100),
CONSTRAINT pk_telefon PRIMARY KEY (pk)
)
WITH (OIDS=FALSE);
ALTER TABLE telefon OWNER TO alexsf;
insert into alexsf.fio VALUES('0','Пупкин0','Серафим0','Опанасович0');
insert into alexsf.fio VALUES('1','Пупкин1','Серафим1','Опанасович1');
insert into alexsf.fio VALUES('2','Пупкин2','Серафим2','Опанасович2');
insert into alexsf.fio VALUES('3','Пупкин3','Серафим3','Опанасович3');
insert into alexsf.fio VALUES('4','Пупкин4','Серафим4','Опанасович4');
insert into alexsf.fio VALUES('5','Пупкин5','Серафим5','Опанасович5');
insert into alexsf.fio VALUES('6','Пупкин6','Серафим6','Опанасович6');
insert into alexsf.fio VALUES('7','Пупкин7','Серафим7','Опанасович7');
insert into alexsf.fio VALUES('8','Пупкин8','Серафим8','Опанасович8');
insert into alexsf.fio VALUES('9','Пупкин9','Серафим9','Опанасович9');
insert into alexsf.car VALUES('0','Жигули0','0');
insert into alexsf.car VALUES('1','Жигули1','1');
insert into alexsf.car VALUES('2','Жигули2','2');
insert into alexsf.car VALUES('3','Жигули3','3');
insert into alexsf.car VALUES('4','Жигули4','4');
insert into alexsf.car VALUES('5','Жигули5','5');
insert into alexsf.car VALUES('6','Жигули6','6');
insert into alexsf.car VALUES('7','Жигули7','7');
insert into alexsf.car VALUES('8','Жигули8','8');
insert into alexsf.car VALUES('9','Жигули9','9');
insert into alexsf.telefon VALUES('0','Nokia0','MTS0','000');
insert into alexsf.telefon VALUES('1','Nokia1','MTS1','001');
insert into alexsf.telefon VALUES('2','Nokia2','MTS2','002');
insert into alexsf.telefon VALUES('3','Nokia3','MTS3','003');
insert into alexsf.telefon VALUES('4','Nokia4','MTS4','004');
insert into alexsf.telefon VALUES('5','Nokia5','MTS5','005');
insert into alexsf.telefon VALUES('6','Nokia6','MTS6','006');
insert into alexsf.telefon VALUES('7','Nokia7','MTS7','007');
insert into alexsf.telefon VALUES('8','Nokia8','MTS8','008');
insert into alexsf.telefon VALUES('9','Nokia9','MTS9','009');
После этого на многострадальном сервере bucardo делаю БД alexsf и схему alexsf, и создаю там аналогичные объекты. Не забываем дать alexsf права SUPERUSER. Затем настраиваем репликацию:
INSERT INTO bucardo.db (name, dbhost, dbname, dbuser, dbpass)
VALUES ('alexsf_slave_db','10.77.11.200','alexsf','alexsf','alexsf');
INSERT INTO bucardo.db (name, dbhost, dbname, dbuser, dbpass)
VALUES ('alexsf_master_db','10.77.11.71','alexsf_test','alexsf','alexsf');
INSERT INTO bucardo.dbgroup(name) VALUES('alexsf_slave_group');
INSERT INTO bucardo.dbgroup(name) VALUES('alexsf_master_group');
INSERT INTO bucardo.dbmap(db, dbgroup) VALUES('alexsf_slave_db','alexsf_slave_group');
INSERT INTO bucardo.dbmap(db, dbgroup) VALUES('alexsf_master_db','alexsf_master_group');
INSERT INTO bucardo.goat (db,schemaname,tablename,pkey,pkeytype,analyze_after_copy) VALUES ('alexsf_slave_db','alexsf','car','pk','bigint','false');
INSERT INTO bucardo.goat (db,schemaname,tablename,pkey,pkeytype,analyze_after_copy) VALUES ('alexsf_slave_db','alexsf','fio','pk','bigint','false');
INSERT INTO bucardo.goat (db,schemaname,tablename,pkey,pkeytype,analyze_after_copy) VALUES ('alexsf_slave_db','alexsf','telefon','pk','bigint','false');
INSERT INTO bucardo.herd(name) VALUES('alexsf_herd');
INSERT INTO bucardo.herdmap (herd, goat)
SELECT 'alexsf_herd', id
FROM goat WHERE db = 'alexsf_slave_db';
INSERT INTO bucardo.sync (name,source,targetdb,synctype,stayalive,checktime,analyze_after_copy)
VALUES('alexsf_sync','alexsf_herd','alexsf_master_db','pushdelta','false','1 minutes','false');
Вроде все. Перед тем, как запустить репликацию, ставлю inactive на не тестовых БД в таблице bucardo.db. Ну и запускаю... Делаю пинки по таблицам в slave БД, и вижу, что репликация работает.
Теперь дело за малым, разобраться в интересующем нас вопросе, а именно в изменении структуры БД...
Меняем структуру нашей подчиненной БД:
alter table car add column probeg numeric;
alter table telefon drop column "operator";
alter table telefon rename column nomer to n;
Перезапускаем bucardo и получаем:
May 28 09:39:46 bucardik Bucardo[16849]: MCP Validating source table "alexsf.car" on alexsf_slave_db
May 28 09:39:47 bucardik Bucardo[16849]: MCP Comparing tables and columns on alexsf_master_db
May 28 09:39:47 bucardik Bucardo[16849]: MCP FATAL: Source database "alexsf_sync", table alexsf.car has more columns than target "alexsf_master_db"
May 28 09:39:47 bucardik Bucardo[16849]: MCP Validation of sync FAILED
Обращаем внимание на то, что несмотря на изменение двух таблиц, bucardo ругается только на одну. То есть он не делает валидацию ВСЕХ таблиц, а обрывает этот процесс на первой, не прошедшей валидацию!!! Воблинзараза!!!
Итак, повторяем изменение структуры на master базе, и перезапускаем bucardo.
И ХРЕНЕЕМ!!! Bucardo не выдает никаких ошибок!!! Просто говорит, что реплицировать нечего!!! Возникает вопрос, то ли лыжи не едут, толи реально так и надо??? Делаем пинки по таблицам и наблюдаем за реакцией... Нормальная реакция, все работает... То есть так и надо, с репликацией ничего не случилось...
Будем считать то, что мы сделали первым вариантом. А именно:
-- изменили структуру подчиненной БД.
-- изменили структуру главной БД.
-- в промежутке между первыми двумя пунктами данные в подчиненной БД не менялись.
-- количество объектов участников репликации не изменилось.
Вывод по первому варианту: да просто офигенный вариант!!! малой кровью решаем проблемы с изменением структуры БД и не нарушаем при этом работу репликации!!! Имеет 100% право на жизнь, будем использовать.
Теперь опишем второй вариант:
-- меняем структуру подчиненной БД.
-- пинаем в подчиненной БД данные со всех сторон, особенно в местах, где произошли изменения структуры.
-- меняем структуру главной БД
-- количество объектов участников репликации не меняем.
Поехали делать второй вариант:
alter table fio add column vozr numeric;
alter table fio drop column o;
update fio set vozr=100;
Bucardo начинает орать всягую лабудень. Перезапустим его на всякий случай. И видим: таблица fio не прошла валидацию... Меняем структуру в главной БД, но НЕ ТРОГАЕМ там данные!!! Перезапускаем bucardo. И зашибись, репликация поехала. все пинки среплицировались!!!
Вывод по 2-му варианту: ну нормально!!! 100% рабочий. Крови больше не стало по сравнению с 1-м. Бум использовать.
Описываем 3-й вариант:
-- меняется поле первичного ключа в подчиненной БД.
-- тусуются данные в поле первичного ключа.
-- аналогично меняется поле первичного ключа в главной БД.
-- количество объектов репликации не меняем.
Поехали:
alter table telefon rename column pk to pkey;
update telefon set pkey=pkey+1000;
И вот о чудо, ничего не получилось. При выполнении update получили ошибку:
ERROR: record "old" has no field "pk"
SQL state: 42703
Контекст:PL/pgSQL function "bucardo_add_delta_u_pk" line 3 at SQL statement
,что вполне закономерно, ведь созданные объекты bucardo в подчиненной базе вроде как подвязаны на имена первичных ключей.
Анализируем...
-- Все объекты bucardo в подчиненной БД были созданы автоматически при внесении данных в таблицу bucardo.sync главной БД. То есть процесс выполнялся удаленно с главной БД.
-- Логично предположить, что в главной БД все эти объекты, а также таблицы и ключи на которые они ссылаются, зарегистрированы в конфигурационных таблицах bucardo.
-- Логично предположить, что изменение объектов в подчиненной БД также должно проводится с главной БД для обеспечения согласованности объектов подчиненной БД и конфигурационной информации в главной БД bucardo.
-- Объекты включались в репликацию фактически тремя действиями: вставка в таблицу goat, вставка в таблицу herdmap, вставка в таблицу sync.
-- Вставка в таблицы goat и herdmap была пообектной и создала, насколько я понял, набор конфигурационной информации объектов репликации. Вставка в таблицу sync ссылалась на весь набор объектов участников, была проведена одной строкой и привела к созданию набора объектов в подчиненной БД, с которыми у нас теперь проблемы.
-- На таблице sync есть триггер validate_sync AFTER INSERT OR UPDATE, который, как я предполагаю, и выполняет работы по удаленному созданию объектов в подчиненной БД. Логично предположить, что в случае изменения конфигурационной информации в goat и herdmap достаточно будет сделать UPDATE на sync для проведения удаленной корректировки объектов в подчиненной БД.
Вроде проанализировали и придумали, что делать дальше. А делать следующее:
Вариант 1:
-- Остановить репликацию
-- Из goat и herdmap выкосить telefon
-- Провести копию изменений структуры подчиненной БД в главной БД
-- В goat и herdmap занести telefon
-- Сделать UPDATE sync
-- Попробовать сделать неотработавший UPDATE в подчиненной БД
-- Запустить репликацию.
Вариант2:
-- Остановить репликацию
-- Порихтовать таблицы goat и herdmap с учетом изменения PK в telefon.
-- Провести копию изменений структуры подчиненной БД в главной БД
-- Сделать UPDATE sync
-- Проверить, изменились ли объекты bucardo в подчиненной БД на таблице telefon.
-- Запустить репликацию.
Пробуем вариант 1. Останавливаем bucardo и:
DELETE FROM bucardo.goat
WHERE db='alexsf_slave_db'
AND schemaname = 'alexsf'
AND tablename = 'telefon';
DELETE FROM bucardo.herdmap
WHERE goat = (SELECT id FROM bucardo.goat WHERE db='alexsf_slave_db'
AND schemaname = 'alexsf' AND tablename = 'telefon');
alter table telefon rename column pk to pkey;
INSERT INTO bucardo.goat (db,schemaname,tablename,pkey,pkeytype,analyze_after_copy) VALUES ('alexsf_slave_db','alexsf','telefon','pkey','bigint','false');
INSERT INTO bucardo.herdmap (herd, goat)
SELECT 'alexsf_herd', id
FROM goat
WHERE db = 'alexsf_slave_db'
AND schemaname = 'alexsf'
AND tablename = 'telefon';
UPDATE bucardo.sync SET status = 'inactive' WHERE name = 'alexsf_sync';
UPDATE bucardo.sync SET status = 'active' WHERE name = 'alexsf_sync';
В подчиненной БД выполняем:
update telefon set pkey=pkey+1000;
... и получаем ту же ошибку... БЛ....!!!
Попробуем грохнуть триггера на telefon и опять обновить sync:
-- В подчиненной БД
drop trigger bucardo_add_delta_d on alexsf.telefon;
drop trigger bucardo_add_delta_i on alexsf.telefon;
drop trigger bucardo_add_delta_u on alexsf.telefon;
drop trigger bucardo_triggerkick_alexsf_sync on alexsf.telefon;
-- В главной БД
UPDATE bucardo.sync SET status = 'inactive' WHERE name = 'alexsf_sync';
UPDATE bucardo.sync SET status = 'active' WHERE name = 'alexsf_sync';
Смотрим, и видим, что на таблице telefon в удаленной БД триггера не создались :( Я буду плакать... Ну почему bucardo не реплицирует DDL????
Перечитал сейчас всю доку, благо ее там не так много(или хреново, что немного), и ничего по работе с отдельными goat не нашел...
Попробую сделать еще одну таблицу и включить ее в репликацию...
-- В подчиненной БД:
CREATE TABLE komp(
pklu4 integer NOT NULL,
cpu character varying(100),
hdd character varying(100),
ozu character varying(100),
CONSTRAINT pk_komp PRIMARY KEY (pklu4)
)
WITH (OIDS=FALSE);
ALTER TABLE telefon OWNER TO alexsf;
-- В БД bucardo в главной БД:
INSERT INTO bucardo.goat (db,schemaname,tablename,pkey,pkeytype,analyze_after_copy) VALUES ('alexsf_slave_db','alexsf','komp','pklu4','bigint','false');
INSERT INTO bucardo.herdmap (herd, goat)
SELECT 'alexsf_herd', id
FROM goat
WHERE db = 'alexsf_slave_db'
AND schemaname = 'alexsf'
AND tablename = 'komp';
UPDATE bucardo.sync SET status = 'active' WHERE name = 'alexsf_sync';
UPDATE bucardo.sync SET synctype = 'fullcopy' WHERE name = 'alexsf_sync';
UPDATE bucardo.sync SET synctype = 'pushdelta' WHERE name = 'alexsf_sync';
И вот на synctype = 'fullcopy' я получил ошибку:
NOTICE: Issuing rollback() for database handle being DESTROY'd without explicit disconnect() at line 29.
КОНТЕКСТ: SQL-команда: "SELECT validate_sync('alexsf_sync')"
ERROR: error from Perl trigger function: error from Perl function: No such table found for database alexsf_master_db: "alexsf.komp" at line 259. at line 30.
О-о-о-о!!!!!!!!!!!!! Эврика!!!!!!!!!! Нашел!!!!!!!!!!
Триггер на таблице sync на UPDATE реагирует не при каждом UPDATE, а только каких-то конкретных полей!!!! Например очень хорошо реагирует на поле synctype. По крайней мере на таблице telefon создались триггера. Новую таблицу komp я пока выкосил...
Так, нужно теперь мысли в кучу собрать и расписать принципы работы 3-го варианта(переименования поля с PK)... Попробуем для порядка опять поменять имя ключа и проверить:
-- На подчиненной БД и на главной БД:
alter table telefon rename column pkey to pklu4;
-- На главной БД:
update bucardo.goat set pkey = 'pklu4'
where db = 'alexsf_slave_db'
and schemaname = 'alexsf'
and tablename = 'telefon';
UPDATE bucardo.sync SET synctype = 'fullcopy' WHERE name = 'alexsf_sync';
UPDATE bucardo.sync SET synctype = 'pushdelta' WHERE name = 'alexsf_sync';
И видим, что триггера на талбице telefon в подчиненной БД не поменялись, но при этом были созданы bucardo.bucardo_add_delta_d_pklu4, bucardo.bucardo_add_delta_i_pklu4 bucardo.bucardo_add_delta_u_pklu4 в подчиненной БД... То есть не до конца отработало, частично, так сказать, по кривому, если точнее :( Я зол.
Попробуем удалить триггера с таблицы telefon и заново пихнуть sync:
-- На пордчиненной БД alexsf:
drop trigger bucardo_add_delta_d on alexsf.telefon;
drop trigger bucardo_add_delta_i on alexsf.telefon;
drop trigger bucardo_add_delta_u on alexsf.telefon;
drop trigger bucardo_triggerkick_alexsf_sync on alexsf.telefon;
О, вот так получилось!!!
Теперь проверим, что не потеряются данные репликации при пинках по таблице sync:
Для этого мы:
- выключим репликацию
- напихаем новых данных в таблицу
- поменяем поле первичного ключа
- проведем изменения в структуре bucardo. Сделаем согласованной схему(sync)...
- опять понапихаем данных
- запустим репликацию
- проверим, что оба запихивания среплицировались
Поехали(напомню, что на данный момент у нас рабочая схема репликации):
#на хосте bucardo
/etc/init.d/bucardo_stop
-- на подчиненной БД под alexsf
insert into alexsf.telefon VALUES('20006','Nokia6','MTS6');
insert into alexsf.telefon VALUES('20007','Nokia7','MTS7');
insert into alexsf.telefon VALUES('20008','Nokia8','MTS8');
insert into alexsf.telefon VALUES('20009','Nokia9','MTS9');
alter table telefon rename column pklu4 to primokey;
drop trigger bucardo_add_delta_d on alexsf.telefon;
drop trigger bucardo_add_delta_i on alexsf.telefon;
drop trigger bucardo_add_delta_u on alexsf.telefon;
drop trigger bucardo_triggerkick_alexsf_sync on alexsf.telefon;
-- на главной БД alexsf
alter table telefon rename column pklu4 to primokey;
-- на главной БД bucardo
UPDATE bucardo.goat SET pkey = 'primokey'
WHERE db = 'alexsf_slave_db'
AND schemaname = 'alexsf'
AND tablename = 'telefon';
UPDATE bucardo.sync SET synctype = 'fullcopy' WHERE name = 'alexsf_sync';
UPDATE bucardo.sync SET synctype = 'pushdelta' WHERE name = 'alexsf_sync';
Ну и радость, все работает, ничего не потерялось... Вроде все варианты рассмотрели... Теперь подитожить, и сформировать набор правил...
5 лет назад
2 комментария:
Привет.
При перезапуске bucardo перечитывает конфигурацию колонок всех ему подчиненных таблиц. Только есть такой момент -- если хотя бы признак NULL\NOT NULL не совпадает в колонках -- он пропускает всю таблицу :(
А вообще, выходит, достаточно делать перезапуски, данных он не растеряет.
Данные он растеряет в том случае, если ты goat удалишь и создашь заново...
Отправить комментарий