Юрий Максименко
db_maker@aha.ru

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


*См. "BYTE/Россия", № 2'2001 и 4'2001.

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

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

Не будем останавливаться на наиболее распространенном подходе: менеджер в филиале вручную составляет документ, отражающий изменения, посылает его по факсу службе поддержки работы сайта, а там вносят изменения в сайт на основе этого документа. Это не решение - это выход из положения, абсолютно не оправдывающий себя при большом объеме изменений. В перспективе же это создание трудностей для последующей героической борьбы с ними.

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

Что же остается? Остается применение репликации.

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

Репликация данных - состояние проблемы

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

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

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

Концепцию, положенную в основу РЕПЫ, я считаю настолько удачной, что с любезного разрешения ее автора изложу узловые моменты.

Базовый алгоритм

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

Рассмотрим репликацию между двумя таблицами. Обозначим их условно S (источник) и D (приемник), хотя имя у них, естественно, одинаковое. Должны выполняться следующие требования: структура и имена таблиц S и D должны быть идентичны, и в обеих таблицах должно быть поле типа TIMESTAMP (в которое автоматически проставляется текущее время изменения записи) с одинаковым именем.

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

  • Шаг 1. Открываем таблицу S для просмотра и переходим на ее первую запись.
  • Шаг 2. Выполняем поиск в таблице D записи со значением ключевого поля, равного значению ключевого поля текущей записи в S.
  • Шаг 3. Если искомая запись не найдена, копируем в D текущую запись таблицы S и переходим к шагу 5.
  • Шаг 4. Если искомая запись найдена, сравниваем значения полей типа TIMESTAMP в обеих таблицах. Если значение этого поля в таблице S младше, обновляем данную запись из D, заменяя значение полей найденной на шаге 2 записи значениями соответствующих полей текущей записи в S.
  • Шаг 5. Переходим на следующую запись таблицы S.
  • Шаг 6. В случае EOF (константа, показывающая, что выполнен переход на несуществующую запись) - закончить выполнение программы.
  • Шаг 7. Переходим на шаг 2.

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

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

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

Вдумчивый читатель уже, вероятно, задался вопросом: если все так просто, то почему же это до сих пор не реализовано и недоступно пользователям? А потому, что все не так просто.

Репликация между различными СУБД

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

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

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

Проблема часовых поясов возникает, когда две базы данных работают в разных часовых поясах, а следовательно сравнение времени в полях TIMESTAMP не позволяет вычислить, какая запись изменялась раньше, какая - позже. Проблема здесь лишь в том, что возможность размещения серверов баз данных в разных часовых поясах часто упускается из виду. Стоит лишь осознать ее, и она решается созданием в программе репликации свойства "часовой пояс" для каждой из реплицируемых баз. Я бы рекомендовал использовать международную систему часовых поясов, в которой отсчет ведется от Гринвичского меридиана (GMT). Тогда перед сравнением значений полей TIMESTAMP нужно добавить к каждому значение часового пояса.

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

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

Удаленные записи. Если в таблице S удалены какие-то записи, то их надо удалить и в таблице D (чтобы они не "реинкарнировались" при репликации из D в S). Но прошу заметить: удалить надо не все записи таблицы D, отсутствующие в S (это может привести к драматическим последствиям), а только те, что были удалены из S. Но как программа репликации узнает, что эта запись была в S и удалена оттуда?

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

Имя поля Тип данных Описание
DeletedRecordID Счетчик Идентификационный номер записи
TableName Текстовый Имя таблицы, из которой удалена запись
RecordID Числовой ID удаленной записи
DeletedAt Дата/время Время удаления

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

Если SQL-сервер поддерживает использование триггеров, то проблема решается написанием триггера, срабатывающего при удалении записи. Но такой распространенный SQL-сервер, как MySQL, пока не поддерживает триггеров. Нет их и в ряде настольных СУБД, таких как Microsoft Access. Что же делать? В этом случае, увы, при каждом удалении записи программный код базы данных должен вставлять соответствующую запись в протокол удаления.

А теперь обсудим действительно серьезную проблему - это различные правила хранения текстовых данных и полей типа Дата/Время.

К примеру, требуется ввести в базу данных название фирмы McDonald's. Читатель, знакомый с SQL, уже, разумеется, понял суть проблемы. Апостроф, содержащийся в названии, имеет в SQL специальное назначение, что чревато аварийным завершением работы программы. Иначе говоря, такая инструкция SQL, как SET firm = 'McDonald's', обречена - интерпретатор SQL решит, что первый апостроф заключил текстовую константу, и не поймет, зачем здесь второй. Что самое прискорбное - единого правила составления выражения для данного случая нет. Вот варианты формирования этой инструкции:

Для MySQL 	SET firm='McDonald''s'
Для Microsoft Access 	SET firm='[McDonald's]'

Заметим здесь, что апостроф - далеко не единственный "проблемный" символ.

Та же проблема встает и с вводом полей даты: представление константы типа Дата/Время в запросе SQL также зависит от вида SQL-сервера (СУБД).

Есть просто забавные примеры проблем с некоторыми другими типами данных. Например, Microsoft Access может поставить перед разработчиком пилатовский вопрос ("Что есть истина?"): если запросить с помощью DAO значение логического поля, то вместо -1 может быть возвращено значение "Истина".

Алексей Куленцов предложил изящное решение: использовать так называемые драйверы баз данных. Базе данных ставится в соответствие метод, представляющий собой по сути формализованное на языке программирования правило записи текстовых полей и полей типа Дата/Время в запросе SQL. Это, разумеется, требует знания основ объектно-ориентированного программирования.

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

Требования к базам данных

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

  1. Все таблицы должны содержать поле TIMESTAMP с одинаковым именем
  2. Должны соблюдаться соглашения об именах таблиц и ключевых полей, чтобы по названию таблицы можно было вычислить название ключевого поля. Например, в таблице Orders ключевое поле будет называться OrderID, в таблице Users - UserID и т.д.
  3. Должен присутствовать протокол удаления и обеспечиваться его заполнение.

Средство разработки программы

Идеальным вариантом я считаю написание программы репликации на C++. Но увы! Это легче сказать, чем сделать, так как потребует весьма больших трудозатрат высококвалифицированных программистов.

Первая версия РЕПЫ была реализована на Microsoft Access 97. Причины такого решения были следующими.

  • Microsoft Access - это одновременно среда разработки приложений и СУБД, что позволяет хранить в одном файле все служебные данные программы репликации - протоколы, последовательность таблиц при репликации для каждой пары баз данных и другую ценную информацию.
  • Microsoft Access поддерживает доступ к данным через ODBC.
  • Microsoft Access поддерживает использование DAO, что позволяет широко применять объекты доступа к данным. Особенно ценен в нашем случае объект Connection, позволяющий работать с базой данных безотносительно к их физической природе.
  • Microsoft Access может отправлять запросы непосредственно на сервер базы данных.
  • Язык Visual Basic for Application, работающий под управлением Microsoft Access, имеет ряд черт объектно-ориентированного программирования, в целом достаточных для решения задач репликации.

Выбор Microsoft Access как платформы разработки программы репликации имеет и свои ограничения. В первую очередь на компьютере, на котором выполняется репликация, должна быть установлена программа Microsoft Access (увы, не создающая exe-файлы) и драйверы баз данных (на Microsoft Access крайне проблематично, если вообще возможно, реализовать установку ODBC-драйверов баз данных). Также отметим одну очень неприятную деталь: DAO не возвращает ошибок выполнения запросов, если выражение запроса задано корректно. Это означает, что программа может по какой-то причине не вставить записи, предназначенные для вставки, но "не заметит" этого и пойдет дальше. Описание решения этой проблемы выходит за рамки данной статьи, но все же не забывайте о ней.

Хотелось бы остановиться на одной весьма ценной опции, которая, на мой взгляд, необходима программе репликации и которую как раз обеспечивает Microsoft Access. Представим себе, что непосредственный контакт программы репликации с базой данных - источником (по локальной сети или через Интернет) невозможен. Это не просто частая - это обычная ситуация, так как подведение к офису достаточно быстрого канала связи пока достаточно дорого и хлопотно.

Как же быть в этом случае? Посылать по электронной почте обновления в формате *.mdb. Работать это может следующим образом: создается mdb-файл, воспроизводящий таблицы базы данных, и в него реплицируются только записи, у которых значение поля TIMESTAMP младше последней даты в соответствующих протоколах репликации.

Репликация в управлении разработкой сайта

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

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

Picture
Схема движения информации в фирме, включающей головной офис и ряд удаленных филиалов.

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

Рассмотрим стрелку 2. Из корпоративной базы данных на сайт идут обновления информации об услугах. В обратном направлении идет информация о заявках на туристическое обслуживание, поступивших на сайт.

Какую же информацию вводит сотрудник (стрелка 3)? Прежде всего это различные объявления, новости компании, графические изображения, если необходимо. Помимо этого, в базе данных сайта вполне может содержаться информация, отсутствующая в корпоративной базе данных (например, расписания культурных мероприятий и т.п.).

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

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

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

Но вот движение по стрелке 3 создает гораздо больше проблем, так как для сотрудника, обновляющего сайт, придется сделать либо специальные странички для заполнения, либо создать ему локальную базу данных и опять-таки использовать репликацию. В каком же случае локальная база данных (например, на Microsoft Access) предпочтительнее служебных страничек? Только тогда, когда требуется достаточно развитый интерфейс, что в случае поддержки сайтов - сравнительная редкость.

Вместо заключения

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

Проблема в начале разработки, естественно, ясна руководителю, причем настолько, что он не вдается в тонкости ее реализации: "В рабочем порядке". Начался процесс разработки страниц и базы данных. И ясная было проблема начинает все больше тонуть в тумане. Не подчиненный руководителю проекта разработчик корпоративной базы данных изменил структуру своих таблиц - и скрипт обмена, "заточенный" под старую структуру, стал генератором ошибок. Руководство потребовало отразить на сайте ранее неоговоренную информацию ("хозяин - барин"), и опять надо все переписывать…

И тут в ход идет неотразимая уловка. Звучит она примерно так: "Систему обмена данными имеет смысл разрабатывать тогда, когда данных много, а пока обойдемся ручным обновлением". Тем самым проблема не решается, а переносится "на потом". Но тут есть одно "но". А именно - когда объем данных действительно станет большим, проблема может оказаться неразрешимой. Хотя бы потому, что программисты, выполнив работу и получив деньги, уже заняты в другом проекте, а новые настаивают на полной переработке всего проекта (не секрет, что трудозатраты на изучение чужого программного кода эквивалентны написанию своего).

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

Чтобы таких трагикомических ситуаций не возникало, необходимо на этапе программирования разработать систему обновления информации в базе данных сайта. В письменном виде. Почему это так существенно? В классическом труде "Мифический человеко-месяц, или Как создаются программные системы" Фредерик Брукс писал: "Только когда пишешь, становятся видны пропуски и проступают несогласованности. В процессе записывания возникает необходимость принятия сотен мини-решений, и их наличие отличает четкую и ясную картину от расплывчатой".

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

Есть и еще одно важное основание составить письменную схему. Покажем это на примере. Представьте, что в проекте схемы обмена данными записано: "Суточный отчет выводится на экран, распечатывается и посылается в головной офис по факсу. В головном офисе поступившие изменения вносятся с клавиатуры в базы данных головного офиса. После чего сотрудники головного офиса по телефону сообщают в филиал, какие туры считать оплаченными…" Абсурд? Но оглянитесь вокруг!