Pull to refresh

Comments 11

Сам сделал уже 2 синхронизитора для Firebird, поэтому тема интересна.

Насчет допустимости DDL в Execute Statement не знал, спасибо за такой хак! Правда есть опасения, что разработчики не зря закрыли эту возможность, как бы чего не поломалось.

Не понял такой момент: вот в БД нижнего уровня (БД1) произошли изменения, сработал триггер, заполнил очередь изменений. Затем данные уплыли в родительскую базу. Из нее попали в БД2. Там же тоже триггер сработает и эти же данные отскоком снова пойдут в БД1? Как пресекается это пинг-понг?

И еще, как насчет производительности? На триггерах получается висит достаточно тяжелый код, на клиентских РМ не возникают тормоза?
Ухты, тема даже кому-то интересна :)

Касаемо DDL в ES — возможность довольно-таки небезопасна и неоднозначна, согласен. Для того, чтобы не огрести кучу проблем, процедуру rpl_install, конечно же, следует запускать только в монопольном режиме, выгнав предварительно из базы всех пользователей.

Пинг-понг пресекается на уровне контекстной переменной. Каждый реплицирующий тригер вначале делает проверку:
if ((select rdb$get_context('USER_SESSION', 'replicating_now') from rdb$database)is not null) then exit;
а питоновский клиент вначале своей работы делает
select rdb$set_context('USER_SESSION','replicating_now','true') from rdb$database;
Таким образом для существующих программ-клиентов вообще ничего не меняется и при этом получается премлимый результат.

Касаемо тяжелости тригерного кода — он, если можно так выразится, только выглядит тяжелым. В том смысле, что там куча текста и условий, и чем больше полей в таблице, тем объемнее код тригера. Но при относительно редких вставках/обновлениях, которые генерирует оператор (скажем, 1-2 операции в секунду) разницы в скорости не ощущается.

Хочу добавить, что со времени написания статьи код немного изменился — в SQL-части были убраны некоторые глюки (например, обновление строк с символом апострофа не реплицировалось; еще что-то по мелочи).
Да, про «пинг-понг» уже понял из кода. Правда с таким подходом есть еще минус — иерархическую синхронизацию будет сделать затруднительно. Т.е. чтобы центральная БД была дочерней для еще более главной.

Кстати, видимо такой синхронизатор не сможет переварить блобовские поля с бинарными данными, если такие встретятся. Данные из полей же прямым текстом хранятся?

А как решается проблема уникальных полей записей? Т.е. чтобы в разных БД не создали разные записи с одинаковым ID, которые вызовут коллизию при синхронизации? Предварительной установкой генераторов в уникальные для каждой БД диапазоны?

Да, вы правы — иерархическая не получится. И, как бы ни странно это не звучало, так и планировалось. Главная идея была как раз в максимальной децентрализации. Фактически, главная база от подчиненной отличается только тем, что в главной (временно) хранятся скрипты из подчиненных. И в любой момент любая из баз данных может стать «главной». Ну, ладно, не в любой — в момент, только когда данные между базами синхронизированы.
Как показала (моя) практика, наличие главного сервера является слабым звеном когда по какой-то причине его надо понизить в звании, а главным назначить другой сервер. У меня такое было дважды, когда в результате развития фирмы бывший филиал вдруг становился центром, а центр, соответственно, филиалом. Тоесть, сервер вроде как надо оставить на месте, но много каких функций перенести в новый центр.

С блобами — да, задача емеет место быть. И более того, даже будет решаться. На момент написания скриптов синхронизация блобов была неактуальна. Сейчас планируется сохранять копию блоба в отдельной таблице с двумя полями — UUID и тело блоба. А при синхронизации блоб сохранять с именем файла равным полученному UUID рядом с файлом скрипта и, соответственно, передавать и обрабатывать. Заглушка для работы с блобами в питоновском скрипте уже есть, она и будет использована.

Уникальность записей — да, именно так как вы написали. Пробовал три варианта — перевод баз данных на первичные ключи в формате UUID; перевод на составные ключи в формате ИД базы + первичный ИД ну и разброс по диапазонах.
Последний подход оказался максимально безболезненным для работающей базы данных. Хотя при написании системы с нуля я бы остановился на связке ИД базы + суррогат для таблицы.
С блобами можно сделать иначе: обратимо преобразовывать таким образом, чтобы данные стали валидной строкой. Например, каждый байт записать двумя байтами в шестнадцатиричном формате, таким образом блоб будет закодирован символами 0..F. Но, наверное придется писать UDF для этого.

У меня вся система устроена несколько иначе, нет явной очереди изменений. Каждая запись каждой таблицы снабжена счетчиком изменений, заполняемым из генератора триггером на вставку/обновление. Синхронизатор запоминает на каком значении генератора он закончил в прошлый раз и на следующем такте выбирает записи, со значением счетчика больше последнего засинхронизированного.

Таким образом, экономим на вставках в очередь. Но очередь все равно нужна для синхронизации удалений.
Вы имеете в виду что-то типа base64? Ну, не знаю. Получим более ресурсоемкий процесс формирования пакета для синхронизации. А если использовать UDF, то еще и дополнительные зависимости от сторонних решений, которых я пытался избежать. Хотя при этом общая логика и питоновская часть останется практически без изменений.

Скажите, а как вы обрабатываете вариант, когда одна и та же запись изменяется между сеансами синхронизации несколько раз, но при этом необходимо сохранить историю изменений? Например, есть таблица заказов с полем «состояние заказа». Например, 1 — принято оператором, 2 — обработано менеджером, 3 — поставлено в очередь на выполнение, 4 — переадресовано вышестоящему отделу/поставщику, 5 -выполнено. Что-то типа этого. И, например, заказ обработан достаточно быстро, чтобы в синхронизацию попал сразу последний вариант записи. Каким образом сохраняется информация о предыдущих состояниях заказа?
И еще вопрос — у вас счетчик изменений один для каждой таблицы? Или каждая запись имеет свой собственный? И как вы обрабатываете такое большое количество счетчиков?
>>вариант, когда одна и та же запись изменяется между сеансами синхронизации несколько раз

А никак, какой смысл? Если требуется хранить историю изменений чего-либо (состояния), то логичнее ее сохранять в базе в отдельной таблице а не в логе синхронизатора. Т.е. для каждого изменения добавляется запись типа (ID документа, дата, действие, etc...). При синхронизации эти данные корректно засинхронизируются.

Да, счетчик сейчас один. По расчетам 64-битного генератора хватит на сотни лет.

Но есть мысль сделать в каждой таблице свой. Таким образом, факт изменения генератора будет служить сигналом к синхронизации этой таблице. Сейчас же на каждом такте фактически приходится делать выборку по каждой синхронизируемой таблице… WHERE CNG > :CNG что несмотря на индексы по счетчику съедает какие-то ресурсы.

Количество счетчиков значения не имеет, т.к. уже есть служебная таблица в которой хранится последнее синхронизированное значение счетчика для каждой таблицы. БД сейчас не очень сложная, около 80 таблиц.
Я кажется понял проблему с историей изменений. Если на поле «состояние» навешана некая триггерно-процедурная логика, то да, наверное синхронизатор может данные покалечить. Правда в моей БД таких завязок нет, но в будущем теоретически такое возможно. Буду иметь ввиду
Да, имелась в виду именно потенциальная возможности получить неприятности в случае несрабатывания тригеров на изменение поля состояния.
Я был неточен в своем посте — отвлекали постоянно.
Еще вопрос на закуску:

Зачем разделители конца строки в генерируемых запросах/триггерах? Для красоты? :)
Ага :)
На этапе отладки всего этого тригеро-процедурного добра пробелы и концы строк в автоматически генерируемых тригерах существенно сократили количество выпитого кофе.
Sign up to leave a comment.

Articles