В статье рассматривается внутреннее устройство репликатора, реализованного в рамках системы «Экстра», предназначенной для учета и анализа аварийности, ведения паспортизации, проведения расчетов и построения регламента на нефтедобывающих предприятиях. Предложен нестандартный подхода к проблеме идентификации записей в пределах распределенной базы данных.
Под репликацией обычно понимается приведение нескольких баз данных с одинаковой структурой в одно и то же непротиворечивое (не вызывающее нарушений целостности) состояние, сопровождающееся взаимным внесением изменений. Решение данной задачи при наличии постоянного канала между серверами баз данных сводится к неоднократно описанному в специальной литературе механизму двухфазной фиксации (2PC — two-phase commit) [1]. Данный механизм состоит в проведении каждой транзакции сначала на сервере, обрабатывающем подключение, и инициировавшем данную транзакцию, а затем на каждом заинтересованном сервере. Если ни на первом, ни на втором этапе ни у одного из серверов не было «возражений» по результатам операции, то транзакция считается зафиксированной.
Если постоянного соединения между серверами нет, возможно несколько вариантов организации этого процесса: 1) данные реплицируются путем физического переноса базы с одного сервера на другой (реплицирующий сервер), проводится репликация и перенос базы обратно; 2) между серверами устанавливается временное, как правило, низкоскоростное соединение для выполнения реплицирования; 3) реплицирование производится без контакта как такового. В последнем случае нет очевидного способа обеспечения целостности данных: в пределах распределенной базы данных возможна ситуация, когда данные, внесенные в базу A, при переносе в базу B нарушают ее целостность. Здесь нужно запретить внесение данных в базу B и отменить произведенные изменения в базе A, что, в свою очередь, может вызвать нарушение целостности базы A, и так до бесконечности. Разумеется, если постановка задачи допускает разделение областей влияния на уровне таблиц (например, в данную базу вносятся данные только о физических, а в ту — только о юридических лицах) или на уровне строк таблиц (изменение реквизитов клиента возможно только в филиале, в котором зарегистрирован клиент), этой проблемы можно избежать. К сожалению, в ряде случаев разделить данные таким образом не представляется возможным.
Разберем более подробно второй вариант, не делая, как правило, различия между реплицированием в пределах одного сервера и между разными серверами, поскольку для описываемой технологии физическое расположение базы данных безразлично. Различия наступают только на этапе оптимизации при учете стоимости соединения: там, где возможно, следует использовать более высокоскоростное и надежное соединение.
Для удобства каждое отношение будем указывать в формате <название базы>.<название отношения>. Эквивалентными отношениями назовем отношения, находящиеся в разных базах данных, но содержащие одинаковое множество атрибутов. Эквивалентными кортежами будем называть кортежи, находящиеся в эквивалентных отношениях и описывающие одинаковые понятия предметной области.
В процессе реплицирования возникает проблема идентификации кортежей: является ли некая запись, содержащаяся в отношении A.FOO измененной записью из отношения B.FOO, или эта запись внесена позднее вместо удаленной? Если да, то какой именно записи из B.FOO она соответствует? Сществует два способа идентификации записей в пределах отношения.
- Естественные ключи (ЕК). Первичные ключи (ПК) содержат значения, взятые из предметной области: номер банковского счета, номер социального страхования, Ф.И.О. и т.п. Что характерно, обеспечение уникальности первичных ключей ложится на пользователей баз данных, а ситуация, когда значение ПК отсутствует (неизвестно), или по каким-то причинам дублируется, попросту не рассматривается.
- Суррогатные ключи (СК). В качестве ПК используются значения, не имеющие смысла в предметной области. Значение создается в пределах базы данных, и его уникальность обеспечивается самим сервером.
В первом случае решение очевидно. Поскольку естественные ключи уникальны в пределах предметной области, они уникальны и в пределах пространства баз данных. Таким образом, если в A.FOO и B.FOO внесены записи, идентифицируемые значением «Иванов», эти записи будут эквивалентны, даже в том случае, если они внесены разными операторами, одновременно зарегистрировавшими клиента с данной фамилией в разных базах. Тот факт, что записи относятся к разным людям, не будет иметь какого-либо значения: при репликации оба приводятся «к общему знаменателю».
Для второго случая нет какого-либо очевидного способа задания эквивалентности кортежей. Естественные ключи имеют ограниченное применение в областях, где уникальность их значений гарантируется административно. Работа с ними связана с перегрузкой индексов сервера, что увеличивает стоимость операций соединения и имеет смысл только в тех случаях, когда можно выделить ЕК (при отображении в базе объектов реального мира). По этим причинам был выбран вариант, использующий механизм суррогатных ключей [3].
В дальнейшем мы будем ориентироваться на конкретное решение, реализованное в рамках реальной системы. Используемые в нем механизмы не являются системно зависимыми и могут быть перенесены на большинство других систем. В частности, в качестве сервера баз данных использована система Firebird (firebird.sourceforge.net), в которой есть штатный механизм для задания значений СК. В случае если такого механизма нет то, как правило, при проектировании базы аналогичный механизм создается разработчиком.
Узлом репликации назовем базу данных, участвующую в процессе реплицирования, и специально подготовленную для этого. По возможности, в процессе подготовки (инсталляции) системы репликатор должен затрагивать минимально возможное число компонентов базы данных. В идеале, структура базы данных при работе в распределенной базе не должна претерпевать каких-либо изменений относительно аналогичной нераспределенной.
Центральным узлом будем называть узел, непосредственно проводящий репликацию. Хотя изложенные идеи можно применять с некоторыми оговорками и в случае произвольной репликации (когда любой узел может реплицировать данные с любым другим), для простоты изложения опишем вариант, когда репликация производится между узлами через некий узел-посредник. В качестве примера возьмем случай репликации между тремя узлами A, B и C. В момент установки системы центральным узлом выбран B. Таким образом, станут возможными репликации A-B и B-C, но репликация A-C будет некорректной.
Реплицируемым отношением считается отношение, имеющееся во всех узлах репликации, кортежи которого разделяются между узлами.
Главное внимание при реплицировании между узлами уделяется изменениям в базах данных. Решение, основанное на поэлементном сравнении кортежей эквивалентных отношений, в случае физической удаленности узлов связано с передачей объемов информации, сравнимых с размером самой базы данных. Другой вариант, состоящий в хранении факта и характера изменения, представляется более предпочтительным.
Характер изменений определяется типом операции, произведенной над отношением. На начальном этапе проектирования системы были выбраны операции добавления, модификации и удаления.
Борьба с первичным ключом
Как идентифицировать кортеж в распределенной базе данных, используя механизм суррогатных ключей? Стандартное решение [1] связано с введением уникальной нумерации узлов реплицирования в момент запуска системы, и работой с составными ПК. В каждое отношение добавляется вторая часть первичного ключа — идентификатор базы, создавшей кортеж, что само по себе неудобно. Некоторые авторы [2] предлагают разделить множества значений, из которых задается ПК, между узлами репликации — в пределах базы A используются значения от 1 до 1000000; в базе B — от 1,000,001 до 2,000,000; и т.д. Данное решение, хотя и является допустимым, во-первых, накладывает требования на количество записей в пределах базы (при добавлении в базу A 1,000,001-ой записи произойдет конфликт), а во-вторых, относится скорее к разряду полумер, снимающих симптомы, но не решающих проблему.
Предлагаемое нами решение проблемы идентификации кортежей состоит во внедрении в над-структуру репликации, физически реализуемую в виде набора таблиц, функционального отображения множества первичных ключей любого отношения, любого узла к эквивалентным отношениям любого другого узла. Технически это реализуется при помощи хранения на центральном узле репликации полной информации об отображении пространства ПК любого узла репликации на пространство ПК центрального узла.
В дальнейшем будем идентифицировать запись как <узел>.<таблица>.<значение ПК>, подразумевая, что имя поля, содержащего ПК, известно.
Рассмотрим пример. В момент запуска системы таблица FOO не содержит ни одной строки. В распределенной базе данных участвуют два узла: A и B. На узле A в таблицу BAR вносится строка вида:
1 Иванов
где 1 — первичный ключ, «Иванов» — значение.
На узле B вносится строка
1 Петров
после чего производится реплицирование, в момент которого история изменений узла A содержит запись вида:
Таблица Операция ПК BAR insert 1
В свою очередь, история узла B выглядит следующим образом:
Таблица Операция ПК BAR insert 1
При этом, несмотря на то, что записи о добавлении выглядят одинаково, таблицы содержат неэквивалентные кортежи.
Допустим, для начала проводится репликация от A к B. Первая (и пока единственная) запись в журнале изменений ссылается на запись с ПК равным 1 таблицы BAR. Поскольку у репликатора нет никакой информации об отображении A.BAR.1 в B.BAR, данное отображение создается — стандартным способом (для FireBird это генератор) получается новое уникальное значение для B.BAR. Допустим, получено значение 2. Тогда будет создано отображение: A.BAR.1 — B.BAR.2. Соответственно, в процессе переноса записи в узел B значение ПК будет заменено на 2, а B.BAR будет иметь вид:
1 Петров
2 Иванов
В свою очередь, при проведении репликации от B к А запись из B.BAR будет внесена как:
1 Петров
Соответственно, будет создано отображение B.BAR.1 — A.BAR.2. Если теперь в A.BAR запись будет изменена:
update BAR set NAME=?Крючков? where (ID=2)
то при переносе в B.BAR за счет использования, созданного отображения запрос будет оттранслирован в
update BAR set NAME=?Крючков? where (ID=1)
Описанная методика в определенном смысле расширяет реляционную базу данных в новое измерение за счет внесения функциональной зависимости между кортежами эквивалентных отношений. Подобное изменение реляционной модели нельзя признать революционным, оно, тем не менее, вводит новое понятие, позволяющее расширить математическую базу теории реляционных баз данных (по крайней мере, ни в одном из известных нам источников такой метод не описан).
Рассмотрим теперь случай, когда распределенная база распространена на более чем два узла репликации. Где тогда целесообразно хранить список отображений? Наиболее последовательным представляется решение, в котором физическое хранение отображений осуществляется на центральном узле репликации, участвующем во всех операциях по реплицированию. Как в описанной ситуации операция update кортежа A.BAR.2 будет распространена в некоторый новый узел репликации C, изначально существовавшей в пределах распределенной базы данных?
Допустим, операции реплицирования между узлами A и C еще не проводилось. Для того, чтобы все внесенные изменения отобразились в узел C, в истории изменений отношения A.BAR должны содержаться три операции:
- добавление A.BAR.1;
- добавление A.BAR.2 (репликация от B);
- изменение A.BAR.2.
В результате последовательного воспроизведения операций на узле C будут созданы отображения A.BAR.1 — C.BAR.1, A.BAR.2 — C.BAR.2. Полный список отображений теперь выглядит как:
- A.BAR.1 - B.BAR.2
- B.BAR.1 - A.BAR.2
- A.BAR.1 - C.BAR.1
- A.BAR.2 - C.BAR.2
Отображения являются симметричными (A.BAR.1 — B.BAR.2 может трактоваться и как B.BAR.2 — A.BAR.1) и транзитивными (из B.BAR.2 — A.BAR.1 и A.BAR.1 — C.BAR1 следует B.BAR.2 — C.BAR.1).
Предположим, что после описанной последовательности действий мы производим повторную репликацию A-B. Следует ли вносить в базу узла B все изменения, содержащиеся в журнале узла A? Разумеется, нет, поскольку новых изменений с момента предыдущего сеанса реплицирования не происходило.
Узлом-источником называется узел репликации, из которого в данный момент производится реплицирование в узел-получатель.
Сеансом реплицирования будем называть процесс перенесения изменений от одного узла к другому, характеризующийся узлом-источником, узлом-получателем, датой начала и подмножеством журнала изменений узла-источника, обработанных в процессе перенесения изменений.
В момент окончания репликации в список сеансов реплицирования центрального узла вносится соответствующая запись. Следующим сеансом будут обрабатываться только те записи, которые появились в журнале изменений за время, прошедшее со времени предыдущего сеанса репликации между заданной парой узлов. Таким образом, в процессе реплицирования обеспечивается внесение изменений из узла-источника в узел-получатель точно в том же порядке, в котором они происходили в источнике.
Следующая сложность, с которой мы столкнемся — рефлексивное распространение изменений. Последний сеанс реплицирования, проходивший в направлении A — B, затронул первые три из трех существующих записей в журнале изменений A.BAR. В процессе реплицирования для операции изменения A.BAR.2 была произведена соответствующая операция в B.BAR, о чем, разумеется, была сделана запись в журнале изменений B.BAR, который приведен в вид:
BAR insert 1 BAR insert 2 (появилась после реплицирования от A) BAR update 1 (появилась после реплицирования от A)
Допустим, теперь мы проводим реплицирование в обратном направлении: от B к A. Нужно ли переносить второе и третье изменение обратно в базу A? Нет, они там уже имеются. Поэтому каждое изменение в журнале изменений нужно снабжать комментарием о том, кто является инициатором этого изменения. Формат журнала изменений принимает вид:
- название отношения;
- операция;
- идентификатор записи в текущем узле реплицирования;
- идентификатор узла инициировавшего операцию.
При обработке журнала такого формата изменения, пришедшие от некоторого узла, возвращаться не будут.
Что дальше?
При практическом применении данной методики возникает еще ряд вопросов.
Выполнять весь сеанс в пределах одной транзакции по ряду причин нежелательно (и, скорее всего, некорректно, так как невозможность фиксации пакета изменений может быть вызвана всего-навсего невозможностью проведения какой-то одной операции из этого пакета). С другой стороны, проводя фиксацию в произвольный момент, скажем, после обработки сотни операций из журнала изменений, мы рискуем раздробить изменения, проводимые в исходной базе в пределах одной транзакции на две. При работе на низкоскоростных каналах с низкой надежностью проведение длительных операций без фиксации может вызвать необходимость повторного проведения всей серии изменений, если произойдет разрыв соединения.
Вероятно, наиболее правильным решением будет ввести дополнительный тип операции, информация о котором записывается в журнал изменений — операцию фиксации. Таким образом, при воспроизведении операций становится ясно, в какой момент можно проводить фиксацию без опасности раздробить транзакцию. Недостатком этого подхода можно считать необходимость внесения изменений во все клиентские приложения, которые производят правки в базе данных. Если запись о выполнении добавлений, изменений или удалений в журнал изменений можно автоматизировать триггерами, то запись о фиксации должна производиться из клиентской программы.
К примеру, отношение FOO имеет ограничение, условие которого зависит от данных из отношения BAR. Предположим, оригинальные изменения происходят в следующем порядке: изменяются данные в BAR (например, добавили договор для нового клиента); становятся возможными изменения в FOO, так как в BAR произошли изменения (теперь можно внести данные по операциям на счете этого клиента); происходят изменения в BAR, делающие невозможными дальнейшие изменения в FOO (договор аннулирован, операции больше не проводятся). При воспроизведении этих операций на другом узле репликации становится важным знать не только сам факт проведения изменений в BAR на шаге 1, но и характер изменений (в данном примере это состояние договора с клиентом). Если содержимое всех полей оригинальной записи на каждой операции, информация о которой вносится в журнал операций, протоколировалось, то такой проблемы попросту бы не возникло, однако хранение лишних данных далеко не во всех случаях оправданно.
Хранить содержимое всех изменений вряд ли целесообразно, но вот записывать данные об изменениях полей, на которые наложены ограничения, вполне возможно. На практике, в рамках разрабатываемой нами системы использовались ограничения на три типа полей: varchar (символьный с переменной длиной), integer, float (см. рис. 1).
Заключение
С репликацией по-прежнему связаны ряд неприятных вопросов, на которые нет и не может быть однозначного ответа. Не претендуя на революционность своих идей, авторы надеются, что их статья поможет разработчикам оценить возможности и ловушки различных подходов к практической реализации репликаторов.
Cистема «Экстра» предназначена для проведения паспортизации трубопроводов и анализа текущего состояния трубопроводного фонда на базе реальных, диагностических и расчетных величин, а также анализа коррозионной опасности, возникающей при транспорте обводненных газожидкостных смесей.
Система объединяет работу всех служб, связанных с эксплуатацией трубопроводов. База данных представлена 770 характеристиками трубопроводов, размещенными в 94 таблицах. «Экстра» использует в общей сложности 15 различных методик расчетов, связанных с эксплуатацией и гидравлическими расчетами закольцованных сетей трубопроводов, а также включает геоинформационную систему собственной разработки.
Административная структура нефтедобывающих предприятий, как правило, состоит из следующих звеньев: Цех — Управление — Производственное объединение. Если два последних уровня обычно объединены сетью, то связь между Цехами, географически удаленными от Управления на расстояния до 100 км в подавляющем большинстве случаев представлена каналом с низкой пропускной способностью, недостаточной для клиент-серверных приложений, работающих с большими выборками. Необходимость построения единой информационной системы, когда основной объем данных о работе трубопроводов должен вводиться в Цехах, определяет актуальность задачи репликации. Существующие решения, к примеру, репликатор компании Synectics (www.synectics.co.za/borland/interbase/ibreplicator/) или СУБД Firebird (http://firebird.sourceforge.net/), не имеющая штатного механизма реплицирования, по ряду причин не подходят. Поэтому был разработан собственный механизм, не требующий серьезных изменений в структуре базы данных и встраиваемый в уже сложившуюся структуру. В качестве средства разработки репликатора использовалась связка Perl — DBI — DBD::Interbase.
Литература
- К. Дейт. Введение в системы баз данных, 6-е изд. Диалектика, 1998
- Vince Duggan. Replicant Technologies, http://www.synectics.co.za/
borland/interbase/ibreplicator/
papers/4128.html - Анатолий Тенцер. Естественные ключи против искусственных ключей, http://www.akzhan.midi.ru/
devcorner/articles/
NaturalKeysVersusAtrificialKeysByTentser.html
Андрей Луковенко (aluck@bashnet.ru) — инженер Института проблем транспорта энергоресурсов (г. Уфа). Айрат Фаритов (monicor@bashnet.ru) — заведующий отделом ИПТЭР.