Алгоритм
асинхронной репликации
Автор:
Марат Губаев (Marat Gubaiev)
Дата
создания ‑ 17.11.2006. Дата редактирования ‑ 03.11.2007.
Частичная
или полная публикация возможна только при наличии ссылки на http://repl2.narod.ru .
Контакты:
gubayev@yandex.ru
В статье
рассматривается алгоритм асинхронной репликации собственного изобретения.
Алгоритм предназначен для построения РБД с произвольной
топологией.
Работоспособность
алгоритма демонстрируется с помощью прототипа (см. DOWNLOADS).
БЛАГОДАРНОСТИ
д.т.н., доценту,
проф. кафедры 603 ХАИ Туркину Игорю Борисовичу за постановку задачи и мудрое
руководство дипломным проектом;
Тимуру Губаеву
за редактирование, поиск источников и поддержку;
Кириллу Толстолужскому за ценные замечания.
ОГЛАВЛЕНИЕ
СПИСОК УСЛОВНЫХ ОБОЗНАЧЕНИЙ И
СОКРАЩЕНИЙ
1 Зачем ещё один алгоритм
репликации?
3.1 Составной ключ,
запакованный в 64-х разрядное число
3.2 Использование счётчика
поколений для регистрации изменений
3.3 Служебные таблицы для
регистрации изменений
3.4 Двухэтапная установка
поколений
3.4.3 Преимущества
двухэтапной установки поколений.
3.5 Типы транзакций.
Состояния триггеров.
4.1 Разрешение
конфликтов обновления
4.2 Очистка
таблиц регистрации
5.1 Описание
алгоритма репликации
5.2 Листинг
метода TInteractionDM.Receive (реализация алгоритма репликации)
БД – база данных.
Асинхронная
репликация (далее просто «репликация») – синхронизация изменений в двух БД.
РБД –
распределённая БД. В статье термин «РБД» понимается не строго (12 условий Дейта
для РБД не выполняются). Под РБД понимается множество БД имеющих одинаковую
структуру таблиц и передающих друг другу данные в процессе репликации.
ЛБД – БД в
составе РБД. Может получать данные от других ЛБД и передавать данные другим ЛБД
в составе РБД.
Сервер – ЛБД,
передающая данные.
Клиент – ЛБД,
принимающая данные.
Ситуации, когда
при разработке БД желательно или необходимо использовать механизм репликации
встречаются достаточно часто. Соответственно встроенная возможность репликации
присутствует во многих СУБД. Так же существует множество репликаторов сторонних
производителей, в т.ч. с открытым кодом (см. ссылки).
Несмотря на такое
многообразие, иногда для конкретного проекта существующие решения не подходят.
В таком случае найти описание алгоритма достаточно трудно. Следовательно,
потребность в подробно описанном алгоритме репликации больше, чем в очередном
репликаторе.
Репликаторов
много, а описаний алгоритмов мало.
Исходя из
вышеизложенного, задачу статьи следует сформулировать следующим образом:
разработать максимально простой алгоритм репликации, разработать прототип,
демонстрирующий работоспособность алгоритма.
В данной статье
рассматривается РБД со следующими характеристиками.
1. Произвольная топология: ЛБД могут быть
связаны произвольным образом (в т.ч. циклически).
2. Запись изменяется и удаляется только в той
ЛБД, в которой была создана (схема ведущий-ведомый).
3. Возможно одновременное внесение изменений,
приём данных от нескольких серверов и считывание данных несколькими клиентами,
так же одновременная репликация двух ЛБД друг с другом и одновременное
выполнение служебных транзакций (установка поколений, очистка логов).
4. Структура таблиц ограничена: первичный
ключ должен быть 64х разрядным целым числом.
Прототип
реализован на Delphi7+FireBird1.0. Основная логика реализована с помощью
триггеров и хранимых процедур FireBird. В Delphi вынесен только собственно
алгоритм репликации, т.к. он предполагает взаимодействие 2х ЛБД.
ЛБД прототипа
состоит из 2х таблиц, связанных отношением главная-подчинённая
Есть несколько
решений, обеспечивающих уникальность первичного ключа. Популярным решением
являются методы, основанные на преобразовании ключей (одна и та же запись имеет
разный первичный ключ в разных ЛБД). Однако преобразование усложняет алгоритм и
требует дополнительного времени.
В данном
алгоритме используется вычисление первичного
ключа про формуле. Для хранения первичного ключа используется 64-разрядное
число (NUMERIC(18,0)). Старшие 4 байта – ID ЛБД, младшие 4 байта ‑ ID записи. Фактически это – составной ключ, записанный в одно поле. Таким
образом, первичные ключи в синхронизируемых таблицах должны быть одного типа ‑
NUMERIC(18,0).
Обычное
«человеческое» решение – использовать для регистрации изменений временные метки
(time stamp). Но у временных меток есть множество недостатков:
- очень трудно установить одинаковое время
на всех узлах РБД;
- системное время зависит от человеческого
фактора: его можно выставить как угодно;
- разнообразные системы времени: UTC,
поясное время и т.д.;
- разнообразные способы представления
времени в компьютере: в каждой OS, СУБД, языке программирования может быть своё
представление времени, с разной точностью и ограничениями;
Из всего этого
следует, что time stamp лучше по возможности не использовать. Вместо time stamp
будем применять аналог логического таймера (logical clock)[entropy6.pdf],
который я назвал счётчиком поколений. В [entropy6.pdf]
значение logical clock увеличивается после каждого события (в данном случае
событие ‑ изменение), таким образом, можно восстановить очерёдность изменений
в одной ЛБД, но нельзя восстановить очерёдность изменений в РБД, т.к. нельзя
сравнивать значения logical clock в разных ЛБД. Счётчик поколений отличается от
logical clock тем, что поколения увеличиваются в общем случае после нескольких
изменений. Тогда эти изменения принадлежат одному поколению.
Для регистрации
изменений в алгоритме используется служебные таблицы, т.н. таблицы регистрации
(логи) которые обладают следующими свойствами.
1. Каждой таблице с данными XXXX
соответствует одна таблица регистрации LOG_XXXX.
2. Каждой записи в таблице регистрации
соответствует одна запись в таблице данных, если запись вставлялась или
изменялась;
3. Записи в таблице регистрации не
соответствует ни одной записи в таблице данных, если запись удалена.
ID записи в
таблице данных является внешним ключом, который ссылается на соответствующую запись в таблице
регистрации.
Таблица регистрации LOG_XXXX имеет следующий
вид (XXXX – соответствующая таблица данных):
Назначение |
Физическое имя |
Примечание |
ID записи |
LOG_GID |
|
Поколение вставки |
LOG_INS_GEN |
|
Поколение изменения/удаления |
LOG_UPD_GEN |
|
Признак удалённости |
LOG_IS_DELETED |
Если нет подчинённой записи в таблице данных, значит, признак удалённости равен True(1), иначе – False(0) |
ID ЛБД-владельца записи (ЛБД, в которой запись была создана) |
LOG_DB_ID |
Вычисляется по ID записи LOG_GID. |
Поколения записи
устанавливаются в два этапа.
1-й этап. При вставке LOG_INS_GEN и
LOG_UPD_GEN устанавливаются в NULL. При изменении или удалении записи
LOG_UPD_GEN устанавливается в NULL. Эта запись становится невидимой для
клиентов.
|
LOG_INS_GEN |
LOG_UPD_GEN |
Insert |
NULL |
NULL |
Update |
|
NULL |
Delete |
|
NULL |
2-й этап. Периодически запускается
транзакция, в которой счётчик поколений увеличивается на 1. Новое значение
счётчика заносится в LOG_INS_GEN и LOG_UPD_GEN, если эти поля были
установлены в NULL. Клиенты получают эту запись во время репликации. Важно, что
бы поколения не выставлялись более чем в одной транзакции одновременно.
Двухэтапная установка поколений позволяет избежать «скрытия» данных при
репликации.
Рассмотрим следующий пример (см. порядок
репликации)
1. Транзакция T1 изменяет запись R1.
Устанавливает поколение записи g.
2. Транзакция T2 изменяет запись R2.
Устанавливает поколение записи g+1.
3. T2 подтверждается.
4. Клиент узнаёт об изменениях в записи R2.
Запоминает, что поколение на сервере равно g+1.
5. T1 подтверждается.
Клиент не узнает об изменениях в записи R1, т.к.
считается, что если клиент знает об изменениях в поколении g+1, то он знает и
об изменениях в поколении g (с time stamp была бы аналогичная проблема).
При двухэтапной
установке поколений тот же случай будет выглядеть следующим образом.
1. Транзакция T1 изменяет запись R1.
Устанавливает поколение записи в NULL.
2. Транзакция T2 изменяет запись R2.
Устанавливает поколение записи в NULL.
3. T2 подтверждается.
4. Запись R2 скрыта для клиентов.
5. T1 подтверждается.
6. Транзакция Tg устанавливает поколение g
для записей R1 и R2.
7. Клиент узнаёт об изменениях в записях R1,
R2. Запоминает, что поколение на сервере равно g.
Клиент узнаёт обо всех изменениях.
1. Т.к. поколение устанавливается только для
подтверждённых изменений, то при репликации сохраняется целостность транзакций,
т.е. все изменения, произошедшие в одной транзакции, передадутся за одну
репликацию.
2. При репликации изменения естественным
образом разбиваются на блоки по поколениям. Т.е. репликация всех изменений
разбивается на серию репликаций. Это, например, позволяет при обрыве связи
повторять репликацию не с начала, а с последнего переданного поколения. На
клиенте полученные изменения применяются в небольших транзакциях. Причём размер
блоков может регулироваться в зависимости от алгоритма установки поколений.
Транзакции в ЛБД можно разделить на три типа.
1. Локальная транзакция – пользовательские
изменения в ЛБД не связанные с репликацией. Триггеры таблиц данных находятся в
состоянии локальной работы. В этом состоянии триггеры изменяют таблицы данных
(при вставке генерируется первичный ключ, при изменении меняется собственное
поколение записи). Так же меняются таблицы регистрации
2. Служебные транзакции – транзакции,
запускаемые фоновыми процессами (очистка логов (CLEAR_DEL_LOGS), установка
поколений (SET_NEW_GEN)). Данные не меняются, следовательно, триггеры таблиц
данных не вызываются. Меняются только таблицы регистрации.
3. Транзакции репликации – выполняют
репликацию, в ЛБД применяются изменения полученные из другой ЛБД
(PROCESS_INPUT_TABLES). Триггеры таблиц данных находятся в состоянии
репликации. Состояние репликации включается процедурой BEGIN_RECEIVE и
отключается процедурой END_RECEIVE. В этом состоянии триггеры не изменяют
таблицы данных, с помощью триггеров изменяются только таблицы регистрации.
Несмотря на то, что запись изменяется только в той
ЛБД, где она была создана, конфликты обновления всё равно могут возникнуть,
если ЛБД получает запись несколькими путями.
Рассмотрим пример.
1. ЛБД A создала запись R
2. ЛБД B и C реплицировались с A и получили
запись R
3. ЛБД A изменила запись R
4. ЛБД B реплицировалась с A, получила новую
версию R
5. ЛБД B реплицировалась с C, получила старую
версию R
Для разрешения конфликтов к таблице с данными
добавляется поле LOG_OWN_GEN – собственное поколение записи. Т.е. поколение
записи, в котором она была вставлена или изменена. Тогда в случае конфликтов
проверяются версии и применяются только
новые изменения.
Теперь предыдущий пример будет выглядеть следующим
образом.
1. ЛБД A создала запись R(LOG_OWN_GEN=1)
2. ЛБД B и C реплицировались с A и получили
запись R(LOG_OWN_GEN=1)
3. ЛБД A изменила запись R(LOG_OWN_GEN=2)
4. ЛБД B реплицировалась с A, получила новую
версию R(LOG_OWN_GEN=2)
5. ЛБД B реплицировалась с C, получила старую
версию R(LOG_OWN_GEN=1). Изменение не применяется, т.к. ЛБД B имеет новую
версию записи R с собственным поколением равным 2 (LOG_OWN_GEN=2).
Возможно, например, позволить всем ЛБД изменять
запись и принимать самые поздние изменения. Удалять сможет только владелец
записи. Или принимать изменения от ЛБД с наибольшим приоритетом и т.д.
Непонятно, как долго хранить запыиси об удалении
(мёртвые записи) в таблицах регистрации. Сервер должен убедиться, что все
клиенты знают об удалениях и только тогда удалять мёртвые записи из таблиц
регистрации.
На сервере заводится таблица CLIENT, в которой
содержится список клиентов сервера и для каждого клиента записывается
поколение, которое было на сервере в момент репликации (что клиент знает о
сервере). На основе этой таблицы делается вывод, удалять мёртвую запись из
таблицы регистрации или нет. Таблица CLIENT заполняется клиентами.
Недостаток такого метода в том, что данные
передаются не только от сервера к клиенту, но и в обратном направлении, хотя
клиент может слать уведомление и не в момент репликации.
Проще всего таблицы регистрации вообще не очищать
или удалять мёртвые записи через достаточно большой промежуток времени.
В случае, если список клиентов фиксированный,
можно обойтись без уведомлений от клиента. Сервер сам узнает у клиента, что
клиент знает о нём.
(Реализовано в
TInteractionDM.Receive)
БД, которая
получает данные, назовём клиентом.
БД, с которой
данные считываются, назовём сервером.
Репликация
начинается по инициативе клиента.
Порядок передачи
данных можно разбить на следующие основные шаги
1. Запросить текущее состояние счётчика
поколений сервера (Server.CURR_GEN)
2. Для каждого поколения начиная от
известного поколения сервера + 1 (Выбрать из DB_PROFILE) до
Server.CURR_GEN (изменения неизвестные клиенту) выполнить:
a. Стартовать транзакцию; установить состояние
репликации (Client.BEGIN_RECEIVE)
b. Принять изменения с сервера
(Server.DB_ID_GEN, Server.MASTER_GEN, Server.DETAIL_GEN); изменения
запоминаются в буферных таблицах (Client.DB_PROFILE_INPUT, Client.MASTER_INPUT,
Client.DETAIL_INPUT).
c. Применить изменения
(Client.PROCESS_INPUT_TABLES):
i.
обновление
(порядок главная-подчинённая),
ii.
вставка
(порядок главная-подчинённая),
iii.
удаление
(порядок подчинённая-главная).
d. Отключить состояние репликации, запомнить
поколение сервера(Client.END_RECEIVE); подтвердить транзакцию
e. Установить новое поколение
(Client.SET_NEW_GEN); установка поколения выполняется в специальной транзакции.
В случае
исключительной ситуации репликацию можно продолжить с последнего успешно
принятого поколения, а не сначала.
Также в
реализации сервер запоминает, какое поколение принял клиент
(Server.SET_CLIENT_GEN). Это нужно для очистки логов от мёртвых записей. За
исключением вызова Server.SET_CLIENT_GEN данные передаются только от сервера
клиенту.
(*********************************************************************)
(*получить данных с сервера FileName *)
(*может возбуждать исключение EAbort *)
(*********************************************************************)
procedure TInteractionDM.Receive(const FileName: string);
var
Server_CurrGen: Integer;
Client_ServGen: Integer;
gen: Integer;
begin
ServerMainDM.Open( FileName );
//Предполагается, что уже вызван метод
ClientMainDM.Open
try
//БД не может принимать данные,
если запись о текущей БД (IS_CURR = 1)
//удалена из DB_PROFILE
if
ClientMainDM.Deleted then
begin
ShowMessage( 'DB
is deleted' );//TODO: заменить на своё исключение
ABORT;
end;
Server_CurrGen := ServerMainDM.GetCurrGen;//Текущее поколение на сервере. Вызов CURR_GEN на сервере в отдельной транзакции
Client_ServGen := ClientMainDM.GetServGen(
ServerMainDM.GetCurrDBID );//Поколение сервера о котором клиент уже знает. Select из DB_PROFILE на клиенте в отдельной транзакции. TODO: заменить select на
хранимую процедуру
//Уведомить
сервер, что клиент принял поколение Client_ServGen
//(на случай, если во время
предыдущей репликации уведомить сервер не получилось,
// и новых данных на сервере не
появилось (цикл выполнятся не будет) )
//Эта информация используется
сервером при очистке логов
//Вызов SET_CLIENT_GEN на сервере
ServerMainDM.SetClientGen(
ClientMainDM.GetCurrDBID, Client_ServGen );
for
gen := Client_ServGen + 1 to Server_CurrGen do
begin
StartReceive( gen );//Начать приём поколения gen. Вызов BEGIN_RECEIVE
try
ReceiveDBID( gen );//Принять изменения в DB_PROFILE (заполнить DB_PROFILE_INPUT). Вызов DB_ID_GEN на сервере
(******Scema
specific*****)
ReceiveMaster( gen );//Принять изменения в MASTER (заполнить MASTER_INPUT). Вызов MASTER_GEN на сервере
ReceiveDetail( gen );//Принять изменения в DETAIL (заполнить DETAIL_INPUT). Вызов DETAIL_GEN на сервере
(******/Scema
specific****)
ApplyReceivedData;//Применить изменения(используются таблицы XXX_INPUT). Вызов PROCESS_INPUT_TABLES
if Assigned( OnBeforeCommitReceiveGen ) then
OnBeforeCommitReceiveGen( Self, gen );//В Replication.exe реализовано как запрос пользователю подтвердить или откатить транзакцию,
//если пользователь выбрал откатить, то в обработчике вызывается ABORT
//(запрос работает при отмеченной опции Confirm Receive).
CommitReceive;//Подтвердить
приём данных (транзакции подтверждаются). Вызов END_RECEIVE
//После
приёма данных нужно установить новое поколение (вызов SET_NEW_GEN на клиенте)
//Реализовано в
обработчике как посылка сообщения методом post приложению Backgrounds.exe
if Assigned( OnAfterCommitReceiveGen ) then
OnAfterCommitReceiveGen( Self, gen );
except
RollbackReceive;//Отменить приём данных (транзакции откатываются)
raise;
end;
//Уведомить сервер, что
клиент принял поколение gen
//Эта информация
используется сервером при очистке логов
//Вызывается ПОСЛЕ того как
клиент принял данные.
//Вызов SET_CLIENT_GEN на сервере
ServerMainDM.SetClientGen( ClientMainDM.GetCurrDBID, gen );
end;(*for*)
finally
ServerMainDM.Close;
end;(*try*)
end;
- генерация уникального первичного ключа по
формуле;
- вместо системного таймера используется
счётчик поколений;
- регистрация изменений с помощью
двухэтапной установки поколений.
- ключи не преобразовываются;
- простой алгоритм регистрации изменений;
- при передаче данные естественным образом
разбиваются на блоки;
- при передаче данных сохраняется
целостность транзакций.
- первичный ключ должен быть 64-разрядным
числом (в InterBase/FireBird ‑ NUMERIC(18, 0));
- для каждой ЛБД должен быть запущен
специальный процесс – установщик поколений;
- невозможен одновременный приём одинаковой
записи с разных серверов.
Источники,
которые использовались при написании статьи особо часто, выделены жирным
шрифтом. Источники, наиболее важные с точки зрения статьи, выделены жирным
шрифтом и подчёркиванием
1.
Статья «Репликация данных между центром
и удалёнными филиалами» (IB), автор Igor Ilyinsky http://home.sinn.ru/~mapnn/articles.html
2.
Статья «Практическая репликация», авторы Андрей
Луковенко, Айрат Фаритов http://www.osp.ru/os/2001/12/045.htm
3.
Статья «Репликация базы данных» (IB),
автор: Евдокимов Алексей http://replication.chat.ru
4.
Статья
«Understanding Replication in Databases and Distributed Systems» www.cs.mcgill.ca/~kemme/papers/icdcs00.pdf
5.
Статья «Тиражирование электронных каталогов библиотек»,
авторы Копытков Д.Ю., Цой К.В., Карауш А.С., Кравчук С.С. http://ask.tomsk.ru/files/tusur-05-reli.pdf
6.
Статья «СУБД ЛИНТЕР. Технический обзор»:
http://citforum.novgorod.ru/database/linter/overview/rel12.shtml
7.
Статья «Автоматическое управление диапазонами Identity в
репликации слиянием» (MSSQL) http://www.sql.ru/articles/mssql/03100903AutomaticallyAssignIdentityRangeForSubscription.shtml
8.
Репликация в MSSQL («шпаргалка») http://www.sql.ru/subscribe/70028/10.shtml
9.
Статья «Репликация распределенной БД Oracle» http://katori.pochta.ru/oracle.html
10. Кратко о репликации в Oracle: http://www.omega.ru/example/example9.html
11. «Выбор способа синхронизации
(MDB)».(MS Access).http://office.microsoft.com/ru-ru/assistance/HP052623361049.aspx
12. Статья «Репликация данных в PostgreSQL»
http://www.opennet.ru/links/info/1084.shtml
13. Статьи по репликации в MS SQL Server на
www.sql.ru http://www.sql.ru/articles/Publications.shtml#13
14. Обзор статьи "Database
Replication" DBMS, vol.10, N 5, May 1997, www.dbmsmag.com,
автор обзора С. Кузнецов (MSSQL, Oracle, Sybase) http://citforum.amursu.ru/database/digest/dig_1606.shtml
15.
«Database
Replication», автор Charles Thompson: http://www.dbmsmag.com/9705d15.html
16. Описание системы DBSync. http://www.relex.ru/dbsync_rus.php
17. FAQ по
SybaseASA http://www.sql.ru/faq/faq.aspx?id=173
18.
Презентация, описывает методы
репликации, упоминает термин поколения (generation) http://research.microsoft.com/~gray/WICS_99_TP/18_Philbe%20Replication%20Stanford99.ppt
19.
Статья «Транзакции в InterBase», автор
Кузьменко Дмитрий: http://www.ibase.ru/devinfo/ibtrans.htm
20. Статья «Естественные ключи против
искусственных ключей», автор Анатолий Тенцер: http://www.akzhan.midi.ru/devcorner/articles/NaturalKeysVersusAtrificialKeysByTentser.html
21. Лекция «Базисные средства
манипулирования реляционными данными» http://ergeal.ru/txt/archive/cs/db/glava5.htm#_2_2_1
22. Учебное пособие «Введение в системы
управления базами данных»: http://www.citforum.ru/database/dblearn/
23.
Статья по репликации, описание
логических таймеров (logical clock) www.cs.duke.edu/~chase/cps212-archive/slides/entropy6.pdf
24. Проблемы с безопасностью при
использовании генератора случайных чисел, основанного на таймере http://lnfm1.sai.msu.ru/~leo/rand.html
25. Статья
«The Dirty Little Secret Of Asynchronous Replication», автор
Boaz Palgi: http://www.techworld.com/files/whitepapers/Topio%20Dirty%20Little%20Secret.pdf
26. Дискуссия о репликации (MySQL) http://forums.mysql.com/read.php?24,53048,53048
27.
Обсуждение дипломного проекта на форуме
(проект не мой): http://www.sql.ru/forum/actualthread.aspx?bid=58&tid=232406&hl=%f0%e5%ef%eb%e8%ea%e0%f6%e8%e8
28. Дискуссия о репликации на форуме: http://www.sql.ru/forum/actualthread.aspx?bid=36&tid=174801&hl=%f0%e5%ef%eb%e8%ea%e0%f6%e8%ff
29. Дискуссия о репликации на форуме: http://sql.ru/forum/actualthread.aspx?bid=10&tid=175586&pg=1
30. Дискуссия о репликации на форуме: http://www.sql.ru/forum/actualthread.aspx?bid=36&tid=37654&hl=%f0%e5%ef%eb%e8%ea%e0%f6%e8%ff
31. Репликатор MobiLink (MySQL) http://www.ianywhere.com/developer/product_manuals/sqlanywhere/0901/en/html/dbmlen9/00000134.htm
32. Список ссылок по репликации (IB): http://www.ibase.ru/develop.htm#repl
33. Репликаторы (IB): http://www.ibase.ru/d_repl.htm
34. Репликатор (Oracle, MS Sql Server, DB2,
Daffodil DB, PostgreSql Derby): http://www.daffodildb.com/replicator/heterogeneous_database_replication.html
35. Open Source репликатор «FireBird Replication Engine»: http://fibre.sourceforge.net/docs/index.html
36. Репликация на
SourceForge http://sourceforge.net/search/?words=replication&type_of_search=soft
- SourceForge
37. Репликатор. DataX http://www.ci.ru/inform18_99/p_07_lum.htm
38. Репликатор «db4o Replication System (dRS) ::
Version 1.0»: http://www.db4o.com/about/productinformation/features/drs.aspx
39.
The MIT License: http://www.opensource.org/licenses/mit-license.php
40.
Литература: К. Дейт. Введение в системы
баз данных, 6-е изд. Диалектика, 1998
1.
Исходный
код прототипа (215KB) http://repl2.narod.ru/Replicator_1_beta_src_only.zip
(для компиляции нужен Delphi 7.0)
2.
Исходный код прототипа + скомпилированные exe-файлы (1.48MB) http://repl2.narod.ru/Replicator_1_beta.zip
3. Документация по БД в html-формате (119KB) http://repl2.narod.ru/Doc_Replicator_1_beta.zip