Разделы:
 О себе   Работы   Диплом   Статьи   Ссылки   Поиск 
 
Профессиональная
деятельность
 
 
О себе
 
  Моя биография на русском языке. Здесь описаны важнейшие события моей жизни, а, кроме того, мои планы на будущее.  
 
Диплом
 
  Описание моей дипломной работы, ее цели, структуры, средств и результатов, мною достигнутых в процессе исследования.  
 
Коротко  
 
 
  Грищенко В.И.
Магистр ДонНТУ
Студент группы ПО-98а

Тема работы:
«Отображение структур данных предприятия на веб-каталоги открытого и закрытого доступа»
 
 
 
 

СИНХРОНИЗАЦИЯ БД ЧАСТЬ 1

Источник: http://iamhere.inso.ru/5
Текст был обработан Грищенко В.И.

Начало

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

В свое время эта задача встала и передо мной. Некоторые СУБД имеют встроенные средства синхронизации (этот процесс еще иногда называют репликацией, а иногда эти понятия различают). Но во-первых, мне нужно было синхронизировать базы под управлением InterBase, а во-вторых, даже те средства сторонних производителей, которые для этой СУБД существуют, меня не устраивали. Мне был необходим такой механизм синхронизации, который не предусматривает постоянного наблюдения администраторами на всех БД - программа писалась в расчете на маленькие организации. InterBase для этого подходил идеально - легкий, надежный (уж точно надежнее локальных БД!), практически не требующий администрирования при не слишком сильной загрузке. Да где же взять такой репликатор? Да еще чтобы был простой как пробка - запустил - отработало. Да еще чтобы в программу вставить работу! Да еще чтобы мог синхронизировать базы под Win и под Unix!

Два дня поисков подходящих средств в Internet ни к чему не привели. Много рекламы, мало информации, бешеные цены и, что уже просто надоело, "очень удобные средства для администратора с помощью которых очень легко исправлять коллизии". Ну нет у меня администратора, который мог бы следить за каждой синхронизацией (проходящей пару раз в день) и исправлять коллизии! Что же делать?

Ладно, попытка - не пытка, я решил поискать что-нибудь по алгоритмам, используемым при репликации баз данных.
НИЧЕГО.
Абсолютно ничего!
Если я плохо искал и кто-то мне покажет интересные в этом плане и свободно доступные материалы, я буду рад! Но по-видимому это khow-how фирм-производителей или до сих пор не разработано каких-то общеизвестных алгоритмов на эту тему. Лезу в Дейта. Ничего. Так, рассуждения на тему распределенных БД, но того, что мне нужно, нет.

Так или иначе, но, помолясь, я решил обмозговать это все сами посмотреть, что получится. Обмозговал. Помучился. Написал. Исправил. Исправил. Еще раз исправил. Заработало. Сглючило. Исправил - перестало глючить. Пока работает. Мне нравится. Работает! Проблемы, конечно, есть. И некоторые весьма приличные. Но... но все таки оно работает!

Так что я решил рассказать, кому интересно, то что я думал и что делал.

Варианты, терминология

Все термины самопальные, так что не обижайтесь, если что-то покажется глупым. :)

Синхронизация - процесс при котором базы данных приводятся в идентичное состояние. Если кто-то придумает определение получше я его выслушаю.

Сначала расклассифицируем синхронизацию по направленности и времени проведения.

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

Если синхронизация должна проводиться немедленно после внесения изменений в БД, то такую синхронизацию назовем синхронизацией в реальном времени. А если изменения из одних баз должны вноситься в другие базы ПОЗЖЕ, по команде/событию/etc - то это будет отложенная синхронизация.

Разделим теперь отложенную синхронизацию еще на два подкласса. Если для синхронизации необходимо соединение с обоими базами данных иона проводится путем анализа расхождений в этих базах, то назовем ее синхронизацией по текущему состоянию. А если синхронизация проводится по журналу - списку вносимых изменений в базы - то так и назовем - синхронизацией по журналу. Обе эти разновидности встречаются в коммерческих разработках. Забегая вперед, скажу, что я реализовывал синхронизацию по текущему состоянию.

Далее. Если при проведении синхронизации изменения могут выявляться и производиться отдельными полями записей, то это синхронизация по полям. Если же "единицей" проведения синхронизации является запись - то это синхронизация по записям.

Это вроде бы все понятно интуитивно, но хочется строгости и не хочется размолвок из-за различий в терминологии.

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

Отсекаем лишнее

Итак, я упомянул несколько вариантов синхронизации и теперь от "лишних" следует избавиться. :) Мы не будем рассматривать синхронизацию в реальном времени это все-таки довольно экзотическая задача. Мы рассмотрим одностороннюю синхронизацию, но очень коротко - это довольно простая задача, ее можно решить без коллизий (по крайней мере, мне так кажется, хотя я могу и ошибаться). Ясно, что односторонняя синхронизация легко реализуется любым методом двухсторонней (по журналу или по текущему состоянию), но без множества проблем, присущих двусторонней синхронизации. И конечно, основная наша цель - это двусторонняя отложенная синхронизация.

Идентификация записей

Для того, чтобы правильно проводить синхронизацию данных любым способом, очевидно, необходима однозначная идентификация записей во всей системе (по крайней мере в рамках одной таблицы). Если для кого-то это покажется неочевидным, скажите, я напишу подробнее.

Для унификации желательно, чтобы в каждой таблице, включенной в процесс синхронизации, был суррогатный первичный ключ (ID) - обычно INTEGER. Впрочем, чаще всего так и есть. Как же мы можем обеспечить уникальную идентификацию записей.

  1. Можно ввести в каждую таблицу дополнительное поле - номер БД, в которой эта запись была создана впервые (DBID). При этом, очевидно, ID уже не будет являться первичным ключом, вместо этого первичным ключом будет пара (DBID, ID). Следует заметить, что из-за этого данное решение не слишком привлекательно
  2. Можно сделать первичным ключом строку специального формата, например XXXX-YYYY-ZZZZZZZZ, где XXXX - это идентификатор базы данных, где запись была создана впервые, YYYY - идентификатор таблицы, ZZZZZZZZ - идентификатор записи внутри конкретной таблицы конкретной БД. Такое решение является хорошо масштабируемым, позволяет "запихать" в ID много дополнительной информации, но у него тоже есть минусы. Во-первых, некоторая избыточность информации. Во-вторых, более сложный механизм генерации таких ID. И еще, неплохо было бы ограничить возможные значения ID данным форматом. Это тоже заботы.
  3. На мой взгляд, для InterBase лучшим вариантом является следующий - ID для всех таблиц генерируется обычным триггером, выбирающим значения из генератора. При этом начальное значение генератора различно для разных БД, за счет чего обеспечится уникальность ID по всем БД. Данный подход характерен для InterBase. Применение его для других СУБД может быть ограничено невозможностью задать начальное значение для счетчика автоинкрементных полей.

Ясно, что я выбрал третий вариант. :) Остается еще один вопрос - тип данных для ID. Ясно, что есть для ID использовать INT64 (NUMERIC(18)), то вопрос с доступным диапазоном номеров не встает - диапазон огромен. Но к сожалению, есть некоторые проблемы, мешающие это сделать. Дело в том, что до сих пор в Delphi поддержка полей Int64 несколько хромает. Это связано как с недоработками компонент доступа к данным (IBX, FIBPlus), так и с изначальным отсутствием в классе TField поддержки полей Int64.

В то же время, если мы внимательно посмотрим на диапазон INTEGER, то обнаружим, что при

  • использовании только положительных значений
  • использовании единственного генератора на все таблицы БД
  • ежедневном создании в системе 10000 записей

нам хватит диапазона INTEGER на 27 лет работы системы из 21 БД. При этом начальные значения генераторов разных БД будут определяться как DBID * 100000000. Для большинства систем это вполне нормальные показатели.

Коллизии

В этом разделе мы рассмотрим возможные варианты изменений в базе данных и отметим, в каких случаях вероятно возникновение коллизий. Разделим все вносимые изменения на два класса - изменения, вносимые в одиночные независимые таблицы и вносимые в таблицы, связанные отношениями FOREIGN KEY.

Кроме того, коллизии могут быть вызваны за счет работы триггеров и ограничений прикладного характера.

Изменения в одиночных таблицах

  1. В таблицу добавляется новая запись

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

    Ну, может еще стоит отметить, что если сразу после синхронизации эти записи были удалены из исходной БД, то при следующей синхронизации они НЕ должны восстановиться. :) Впрочем, это скорее, относится к следующему подразделу:

    Причиной коллизии могут стать ограничения типа UNIQUE. Поясняю. Пусть есть 2 БД: A и B. На таблицу (Table1) наложено ограничение UNIQUE(Field1). Пусть в начальный момент времени в обоих базах НЕТ записей, у которых Field1 = Value1. Добавим такую запись сначала в A, а затем в B. Ошибок не возникает - ограничение UNIQUE не нарушено. Теперь пытаемся синхронизировать эти БД. По логике вещей, мы должны создать в каждой БД по 1й записи. Но не можем, так как это вызовет нарушение ограничения. Конечно, при условии того, что данные были введены верно и модель построена правильно, это должны быть ОДИНАКОВЫЕ (по предметным полям) записи, они должны описывать одну сущность. Но нам от этого не легче - коллизия возникла и ее надо разрешать. Разрешить ее можно удалением записи из одной базы и повторным проведением синхронизации.

    Эта является замечательным примером того, что коллизия может возникнуть безотносительно конкретной реализации механизма синхронизации. Она возникает принципиально из-за распределенности системы и отложенности синхронизации.

    Еще стоит упомянуть "мягкий" вариант этой коллизии (без возникновения ошибок БД), но тоже очень неприятный. Пусть есть 2 учереждения, между которыми синхронизируется таблица жителей района. Ограничение на уникальность ФИО естественно не введено, т.к. могут попасться люди с одинаковым ФИО. А паспортные данные по тем или иным причинам не хранятся вовсе. Иванов Иван Иванович приходит в учереждение №1, оператор ищет его в БД, не находит и поэтому добавляет его в БД. Синхронизация баз проводится ночью. А этот паразит Иван Иванович в этот же день идет в другое учереждение и там, не найдя его в БД, его заносят еще раз. Теперь внимание! В предыдущем случае при синхронизации выдалась бы ошибка и администратор (если таковой имеется) что-то бы придумал. Теперь же ошибки нет, т.к. нет ограничения на уникальность, эти записи получат разные первичные ключи и в итоге в каждой БД будет ровно по 2 Иванова Ивана Ивановича. Хотя оба оператора тщательнейшим образом проверяли его отсутствие при вводе! К чему это может привести при выполнении соответствующих выборок и генерации отчетов - можете придумать сами. Например, выяснится, что по соответствующему адресу стало прописано на 1 человека больше...

    В общем, подобные "логические" коллизии практически неотслеживаемы. И это очень печально.

  2. Из таблицы удаляется запись

    После проведения синхронизации эта запись должна удалиться, если она там есть, из других БД. Опять-таки, возможны "логические" коллизии - допустим, триггер проверяет наличие хотя бы одной записи, у которой Field1 = Value1.Сначала в обоих БД было по две такие записи. В обоих базах удалим по одной записи. При этом ошибок не будет, так как остается вторая. Но если мы удалили в разных базах разные записи, то после синхронизации возникнет ошибка, так как в итоге в базах не окажется ни одной записи Field1 = Value1. В случае такого рода ограничений, опять-таки, по-видимому, без администратора, хорошо знакомого со структурой БД и способного исправить такие коллизии ничего не выйдет :(.

  3. В таблице изменяются некоторые поля записи

    После проведения синхронизации в других базах эта запись должна измениться аналогичным образом.

    Первая и очень вероятная коллизия заключается в одновременном изменении записи в разных БД. Эта коллизия имеет два варианта - если изменяются одни и те же поля и если изменяются разные поля.

    В первом случае, очевидно, один вариант изменения неверен - ведь описываемая сущность имеет вполне конкретное значение данного параметра. Приняв за правду более позднее изменение или более "главную" БД, мы легко можем разрешить вопрос.

    А второй случай, при синхронизации на уровне записей может вызвать серьезнейшую проблему - потерю введенной информации. Привожу пример. Вышеупомянутый Иванов И.И. опять побежал в вышеупомянутые учереждения. В первое он побежал и сказал там, то он теперь инвалид второй группы, что оператор и занес в БД. Прибежав в тот же день во второе учереждение он сказал, что у него родилась тройня (или просто второе учереждение - это родильный дом и там все сами узнали, а Иванов бегал только в одно учереждение). Там изменили поле "кол-во детей". Внимание! Если синхронизация производится целыми записями, то в зависимости от времени изменений или от "главности" баз мы потеряем ту или иную введенную информацию, если только умный администратор не увидит подробнейшее описание случившегося и не исправит эту ситуацию руками. Я полагаю, это ясно.

    Так что мы видим, что синхронизация на уровне записей не только приводит к перекачке неизмененных полей, но и может служить источником очень неприятных ошибок. В то же время, к сожалению, для реализации синхронизации н уровне полей необходимо гораздо больше служебной информации и гораздо более сложные структуры данных. К тому же, реализация синхронизации по полям может упереться в возможности СУБД - необходимо определять список измененных полей и зачастую это тоже нетривиальная задача.

Изменения в связанных таблицах

Будем рассматривать коллизии при синхронизации таблиц TableA и TableB. TableB имеет внешний ключ (FOREIGN KEY), ссылающийся на TableA. Тогда записи таблицы A будут родительскими, а соответствующие записи таблицы B - дочерними.

  1. Создается новая родительская запись или новая дочерняя запись к существующей родительской

    Никаких особенностей нет, дополнительных проблем из-за связанности таблиц не возникает.

  2. Создается новая родительская запись и дочерние к ней

    И родительская и дочерние записи должны появиться в других БД. Здесь становится важным порядок создания записей в результирующей БД - сначала должна создаваться родительская запись, затем дочерние. Впрочем, в тех СУБД, в которых ограничения ссылочной целостности проверяются только при подтверждении транзакции, это не обязательно. Но даже в них нельзя посоздавать все новые родительские записи, подтвердить транзакцию, а затем посоздавать новые дочерние записи.

  3. Удаляется дочерняя запись

    Никаких особенностей нет, дополнительных проблем из-за связанности таблиц не возникает.

  4. Удаляется родительская запись и дочерние

    В результирующей БД должны удалиться сначала дочерние записи, затем родительская.

  5. В базе А создается дочерняя запись, в базе В удаляется родительская
  6. В базе А дочерняя запись передается от родительской записи 1 к родительской записи 2, в базе В удаляется родительская запись 2
  7. ----

Циклы FOREIGN KEY

TODO

Влияние триггеров

TODO

Синхронизация по журналу - общие принципы

Это наиболее часто применяемый метод. Все действия, производимые в БД (создания, удаления и изменения записей) сохраняются в некотором журнале - обычно это отдельная таблица или набор таблиц в самой БД.

Заполнение журнала производится триггерами, которые записывают в журнал информацию, достаточную для повторного выполнения произведенного действия. Понятно, что в случае удаления записи достаточно сохранить идентификатор таблицы и значение первичного ключа удаленной записи, а для изменения или создания записи нужно также сохранять значения полей. Обычно также сохраняется отметка времени, когда было произведено изменение.

Для синхронизации двух БД на каждой из них повторяют все действия , произведенные в другой БД, извлекая их по очереди из журнала. Отсюда следует одно из преимуществ синхронизации по журналу - нет необходимости в установлении соединения с БД - журнал, выведенный в отдельный файл можно передать по "floppynet" :) Необходимо только тщательно отмечать, какие записи журнала еще не передавались. Эта проблема усложняется при наличии нескольких синхронизируемых БД - нужно запоминать, какая последняя запись журнала была передана в каждую из БД.

Понятно, что после успешного применения журнала к другой БД журнал можно урезать. Но тем не менее, журнал занимает много места - в лучшем случае дублирует новые и измененые записи.

Если одна и та же запись изменялась несколько раз подряд, то можно либо хранить все промежуточные изменения, либо "склеить" эти изменения в одно. Это снизит размер журнала, но может привести к лишним коллизиям.

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

Следует также упомянуть, что на базе журнала можно построить службу отката состояния БД практически на любой момент (если только соответствующие данные не были удалены из журнала).

Синхронизация по журналу реализуется достаточно просто, но очевидно, что коллизии при этом неизбежны. Для иллюстрации этого предлагаю вам следующую аналогию. Представьте себе, что у вас есть 2 комнаты, в которых расположены ящики. У вас есть робот, который можно запрограммировать на перемещение ящиков. В начальный момент ящики в комнате расположены одинаково. Вы подаете роботу команды и он перемещает ящики. Ваш напарник подает команды другому роботу во второй комнате. После этого вы приказываете роботам поменяться местами и повторить все действия, начиная с начального момента. Очевидно, что это может привести к неприятностям - в первой комнате робот передвигал ящик №5, но во второй комнате его не оказалось на месте, в первой он подвинул ящик №54 в угол, но во второй угол оказался занят.

При этом следует отметить, что удачное приложение журнала 2 в БД 1 еще не означает, что без ошибок отработает журнал 1 в БД 2.

Сведем рассмотренные нами преимущества и недостатки в единый список.

Преимущества

  • Нет необходимости в установке соединения
  • Можно вернуть БД на любой момент в прошлом
  • Сравнительная простота реализации
  • Высокая скорость синхронизации - пропорциональна кол-ву изменений за цикл

Недостатки

  • Большие объемы хранимых данных - пропорциональны кол-ву изменений за цикл
  • Коллизии практически неизбежны

Синхронизация по текущему состоянию - общие принципы

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

Итак, синхронизация по текущему состоянию всегда выполняется с установлением соединения, так что применить этот метод без наличия какого-либо канала связи между серверами нельзя.

Процесс, производящий синхронизацию, устанавливает соединение с обоими БД (если баз больше двух, то синхронизация выполняется попарно - например, "цепочкой" или "звездой" ) и пробегает по всем синхронизируемым таблицам в поисках различий. Здесь мы видим первый существенный недостаток метода - скорость синхронизации пропорциональна полному количеству записей в БД. Напомню, что для синхронизации по журналу скорость была пропорциональна кол-ву измененных данных.

Когда различия находятся, то управляющий процесс эти различия убирает, приводя обе БД к идентичному виду. При этом приходится принимать решение - какая из БД содержит более верные данные по текущей обрабатываемой записи - следует ли удалить запись из базы 1 или наоборот, создать такую же в базе Б, где она пока отсутствует.

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

Если вспомнить аналогию с роботами, ящиками и комнатами, то в данном случае робот будет заглядывать в обе комнаты и, если увидит, что ящик №15 стоит в разных местах, то, приняв какое-то из расположений за верное, постарается в другой комнате поставить его в то же место. При этом, если его место занято, то возможен дополнительный анализ - а каким ящиком занято? а почему не занято в первой комнате?

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

Для проведения синхронизации по состоянию обычно для каждой записи БД вводятся одно или несколько дополнительных полей, по которым управляющий процесс сможет определить, какая из двух записей содержит более актуальные данные. Размер дополнительных данных опять-таки получается пропорциональным общему количеству данных в БД, но сравнить это с журнальной синхронизацией нельзя, так как размер дополнительных полей достаточно мал и не увеличивается при многократных изменениях данных, а записи в журнале чаще всего целиком дублируют записи таблиц и накапливаются при последовательных изменениях.

Сведем рассмотренные нами преимущества и недостатки в единый список.

Преимущества

  • Хорошие возможности по исправлению коллизий

Недостатки

  • Низкая скорость синхронизации - пропорциональна кол-ву всех записей
  • Необходимо соединение с обоими БД
  • Для всех записей вводятся дополнительные поля - сравнить с журнальной синхронизацией толком нельзя

Синхронизация независимых наборов таблиц

Итак, пусть у нас есть несколько баз данных - A, B, C,... При этом изменение данных происходит (без ограничения общности) только в БД A. Еще следует отметить, что если в базах данных имеются независимые наборы таблиц, то в одном таком наборе данные могут изменяться только в БД А, а в другом - только в базе B (например). Такая структура тоже может считаться системой с односторонней синхронизацией, просто проводимой раздельно по нескольким наборам таблиц.

Поясню примером. У фирмы есть Интернет-магазин. Одна база данных находится в офисе фирмы, вторая - на Web-сервере. Пусть есть несколько таблиц, описывающих товары Интернет-магазина и таблицы гостевой книги и форума. Эти наборы не зависят друг от друга и точки изменения данных - разные. Описания товаров заполняются в офисе и изменения должны передаваться на БД Web-сайта. А информация от клиентов - форум и гостевые сообщения создаются на сайте и идут в обратном направлении.

В такой схеме коллизии практически исключены - потоки обновлений базы не пересекаются. Реализовывать такую схему можно практически любым способом без особых проблем.

Проблемы начинаются тогда, когда в Интернет-магазин вводится функция online-покупки товара. При этом заказы покупателей являются зависимыми от таблиц товаров. И данные для этого набора могут изменяться в двух БД одновременно.

Продолжение следует...

Во второй части мы перейдем собственно к моему решению - реализации двусторонней синхронизации по текущему состоянию БД.


 
         Вебмастер: zenon@ukrtop.com