Рейтинг@Mail.ru

6. Репликация

Замечание

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

6. Репликация

Механизм репликации позволяет сразу многим Tarantool-серверам работать с копиями одних и тех же баз данных. При этом все базы остаются в синхронизированном состоянии благодаря тому, что каждый сервер может сообщать другим серверам о совершенных им изменениях. Сервера, которые работают над одними и теми же базами, представляют собой “кластер”. У каждого сервера в кластере есть числовой идентификатор (server id), уникальный в рамках кластера.

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

6.1. Архитектура механизма репликации

Чтобы знать о всех изменениях на стороне главного сервера, каждая реплика непрерывно опрашивает главный сервер на предмет обновлений в его WAL-файле (write ahead log) и применяет эти обновления на своей стороне. Каждая запись в WAL-файле представляет собой один запрос на изменение данных (например, INSERT, UPDATE или DELETE) и присвоенный данной записи номер (LSN = log sequence number). Номера присваиваются в порядке возрастания. По сути, репликация в Tarantool’е является построчной: все команды на изменение данных полностью детерминированы, и каждая такая команда относится только к одному кортежу.

Вызовы хранимых Lua-процедур фиксируются не в WAL-файле, а в журнале событий (event log). Таким образом гарантируется, что не детерминированное поведение логики на Lua не приведет к рассинхронизации реплицированных данных.

A change to a temporary space is not written to the write-ahead log, and therefore is not replicated.

6.2. Setting up a master

Чтобы настроить возможность установки соединения для реплик, на стороне главного сервера требуется лишь указать значение для параметра “listen” в init-запросе box.cfg. Например, box.cfg{listen=3301}. Когда URI для прослушивания задан, главный сервер готов принимать запросы на соединение от любого количества реплик. Каждая реплика при этом находится в некотором статусе репликации.

6.3. Настройка сервера-реплики

Каждому Tarantool-серверу необходим корректный файл со статическим снимком данных (.snap-файл). Файл-снимок создается на сервере при первом запросе box.cfg. Если при первом таком запросе на сервере не определен источник репликации (replication source), то сервер стартует в режиме главного сервера и создает для себя новый кластер с новым уникальными UUID. Если же источник репликации при первом box.cfg-запросе определен, то сервер стартует в режиме реплики, а файл-снимок и информация о кластере берутся из WAL-файлов на главном сервере. Поэтому при настройке репликации нужно указать параметр replication_source в запросе box.cfg. При первом соединении с главным сервером сервер-реплика включается в состав кластера. В дальнейшем такая реплика общается только с главным сервером из данного кластера.

После установки соединения с главным сервером реплика запрашивает у него все изменения, чьи LSN-номера в WAL-файле больше номера последнего локального изменения на реплике. Поэтому WAL-файлы на главном сервере нужно хранить до тех пор, пока все реплики не применят изменения из этих WAL-файлов на своей стороне. Состояние реплики можно “обнулить”, удалив все файлы репликации (.snap-файл со снимком и .xlog-файлы с записями WAL) и запустив сервер снова. Реплика при этом возьмет все кортежи с главного сервера и придет в синхронизированное состояние. Обратите внимание, что такая процедура “обнуления” сработает, только если на главном сервере будут доступны все нужные WAL-файлы.

Примечание

Параметры репликации можно менять на лету, что позволяет назначать реплику на роль главного сервера и наоборот. Для этого используется запрос box.cfg.

Примечание

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

Примечание

Replication requires privileges. Privileges for accessing spaces could be granted directly to the user who will start the replica. However, it is more usual to grant privileges for accessing spaces to a role, and then grant the role to the user who will start the replica.

6.4. Восстановление после сбоя

“Сбой” — это ситуация, когда главный сервер становится недоступен вследствие проблем с оборудованием, сетевых неполадок или программной ошибки. У реплики нет способа автоматически обнаружить, что связь с главным сервером утеряна насовсем, поскольку причины сбоя и окружение, в котором развернута репликация, могут быть очень разными. Поэтому обнаруживать сбой должен человек.

However, once a master failure is detected, the recovery is simple: declare that the replica is now the new master, by saying box.cfg{... listen=URI ...} Then, if there are updates on the old master that were not propagated before the old master went down, they would have to be re-applied manually.

6.5. Quick startup of a new simple two-server cluster

Шаг 1. Запустите первый сервер со следующими настройками:

box.cfg{listen = *uri#1*}
-- в этом запросе можно задать больше ограничений
box.schema.user.grant('guest', 'read,write,execute', 'universe')
box.snapshot()

... Итак, создался новый кластер.

Шаг 2. На втором сервере проверьте пути, по которым будут храниться файлы репликации. Эти пути задаются в параметрах snap_dir (для .snap-файлов) и wal_dir (для .xlog-файлов). В указанных директориях должно быть пусто, чтобы не случилось конфликта с теми начальными данными, что придут с первого сервера, когда второй сервер присоединится к кластеру.

Step 3. Запустите второй сервер со следующими настройками:

box.cfg{
  listen = uri#2,
  replication_source = uri#1
}

... где uri#1 = URI, на котором включено прослушивание у первого сервера.

Вот и всё.

В описанной выше конфигурации первый сервер выполняет роль “главного”, а второй служит “репликой”. Далее все изменения, происходящие на стороне главного сервера, будут доступны с реплики. Простой кластер из двух серверов, где главный сервер запущен на одном компьютере, а сервер-реплика — на другом, встречается очень часто и обладает двумя важными преимуществами: FAILOVER (т.е. отказоустойчивость, поскольку в случае отключения главного сервера его место может занять сервер-реплика) и LOAD BALANCING (т.е. балансировка нагрузки, поскольку клиенты могут обращаться с SELECT-запросами как к главному серверу, так и к реплике). При необходимости в настройках реплики можно задать параметр read_only = true.

6.6. Мониторинг действий реплики

In box.info there is a box.info.replication.status field: “off”, “stopped”, “connecting”, “auth”, “follow”, or “disconnected”.
If a replica’s status is “follow”, then there will be more fields – the list is in the section Submodule box.info.

In the log there is a record of replication activity. If a primary server is started with:

box.cfg{
  <...>,
  logger = *имя_файла_для_ведения_журнала*,
  <...>
}

то на каждую установку/потерю соединения реплики с главным сервером в журнале будут появляться строчки со словом “relay”.

6.7. Предотвращение дублирующихся действий

Предположим, что реплика пытается сделать нечто, что уже было сделано на главном сервере. Например:
box.schema.space.create('X')
Это приведет к ошибке “Space X exists” (“Пространство X уже существует”). В данном частном случае можно скорректировать инструкцию следующим образом:
box.schema.space.create('X', {if_not_exists=true})
Но существует и более общее решение: использовать метод box.once(key, function). Если box.once() был вызван ранее с тем же значением параметра key, то функция function игнорируется; в противном случае функция function будет выполнена. Поэтому действия, которые должны совершаться только один раз за время текущей сессии репликации, нужно помещать в функцию и вызывать ее с помощью метода box.once(). Например:

function f()
  box.schema.space.create('X')
end
box.once('space_creator', f)

6.8. Репликация по схеме master-master

В случае настройки репликации по схеме master-replica изменения на главном сервере доступны для просмотра с реплики, но не наоборот, потому как главный сервер в такой схеме указан в качестве единственного источника репликации. В случае схемы master-master (иногда ее также называет multi-master) просмотр изменений возможен в любом направлении. В простом случае (master-master с двумя серверами) на первом сервере нужно задать следующие настройки:

box.cfg{ replication_source = uri#2 }

Этот запрос можно выполнить в любой момент, т.к. параметр replication_source можно задавать на ходу.

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

Если две операции над одним и тем же кортежем производятся “параллельно” (а это может потребовать много времени, поскольку репликация — это асинхронный процесс), причем одна из операций — это delete, а вторая — replace, то существует вероятность, что данные на серверах станут различаться.

6.9. Ответы на вопросы “Что если?”

Q:

What if there are more than two servers with master-master?

A:

On each server, specify the replication_source for all the others. For example, server #3 would have a request:

box.cfg{ replication_source = {uri1}, {uri2} }
Q:

What if a server should be taken out of the cluster?

A:

For a replica, run box.cfg{} again specifying a blank replication source: box.cfg{replication_source=''}

Q:

What if a server leaves the cluster?

A:

The other servers carry on. If the wayward server rejoins, it will receive all the updates that the other servers made while it was away.

Q:

What if two servers both change the same tuple?

A:

The last changer wins. For example, suppose that server#1 changes the tuple, then server#2 changes the tuple. In that case server#2’s change overrides whatever server#1 did. In order to keep track of who came last, Tarantool implements a vector clock.

Q:

What if two servers both insert the same tuple?

A:

If a master tries to insert a tuple which a replica has inserted already, this is an example of a severe error. Replication stops. It will have to be restarted manually.

Q:

What if a master disappears and the replica must take over?

A:

A message will appear on the replica stating that the connection is lost. The replica must now become independent, which can be done by saying box.cfg{replication_source=''}.

Q:

What if it’s necessary to know what cluster a server is in?

A:

The identification of the cluster is a UUID which is generated when the first master starts for the first time. This UUID is stored in a tuple of the box.space._schema system space. So to see it, say: box.space._schema:select{'cluster'}

Q:

What if it’s necessary to know what other servers belong in the cluster?

A:

The universal identification of a server is a UUID in box.info.server.uuid. The ordinal identification of a server within a cluster is a number in box.info.server.id. To see all the servers in the cluster, say: box.space._cluster:select{}. This will return a table with all {server.id, server.uuid} tuples for every server that has ever joined the cluster.

Q:

What if one of the server’s files is corrupted or deleted?

A:

Stop the server, destroy all the database files (the ones with extension “snap” or “xlog” or ”.inprogress”), restart the server, and catch up with the master by contacting it again (just say box.cfg{...replication_source=...}).

Q:

What if replication causes security concerns?

A:

Prevent unauthorized replication sources by associating a password with every user that has access privileges for the relevant spaces, and every user that has a replication role. That way, the URI for the replication_source parameter will always have to have the long form replication_source='username:password@host:port'

Q:

What if advanced users want to understand better how it all works?

A:

See the description of server startup with replication in the Internals section.

6.10. Практическое руководство по репликации

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

Запустите два терминала, каждый в своем окне, и расположите их рядом на экране. (Далее в примерах оба терминала показаны в виде закладок. Щелкните на заголовок закладки — “Terminal #1” или “Terminal #2”, — чтобы увидеть вывод на соответствующем терминале.)

В первом терминале (Terminal #1) выполните следующие команды:

$ # Terminal 1
$ mkdir -p ~/tarantool_test_node_1
$ cd ~/tarantool_test_node_1
$ rm -R ~/tarantool_test_node_1/*
$ ~/tarantool/src/tarantool
tarantool> box.cfg{listen = 3301}
tarantool> box.schema.user.create('replicator', {password = 'password'})
tarantool> box.schema.user.grant('replicator','execute','role','replication')
tarantool> box.space._cluster:select({0}, {iterator = 'GE'})

В результате были заданы настройки нового кластера, а на экране был выведен UUID текущего сервера. Теперь вывод на экране выглядит следующим образом (за тем исключением, что UUID у вас будут другие):

$ # Terminal 1
$ mkdir -p ~/tarantool_test_node_1
$ cd ~/tarantool_test_node_1
$ rm -R ./*
$ ~/tarantool/src/tarantool
$ ~/tarantool/src/tarantool: version 1.7.0-1724-g033ed69
type 'help' for interactive help
tarantool> box.cfg{listen = 3301}
<... ...>
tarantool> box.schema.user.create('replicator', {password = 'password'})
<...> I> creating ./00000000000000000000.xlog.inprogress'
---
...
tarantool> box.schema.user.grant('replicator','execute','role','replication')
---
...
tarantool> box.space._cluster:select({0}, {iterator = 'GE'})
---
- - [1, '6190d919-1133-4452-b123-beca0b178b32']
...
$

Во втором терминале (Terminal #2) выполните следующие команды:

$ # Terminal 2
$ mkdir -p ~/tarantool_test_node_2
$ cd ~/tarantool_test_node_2
$ rm -R ~/tarantool_test_node_2/*
$ ~/tarantool/src/tarantool
tarantool> box.cfg{
         >   listen = 3302,
         >   replication_source = 'replicator:password@localhost:3301'
         > }
tarantool> box.space._cluster:select({0}, {iterator = 'GE'})

В результате были заданы настройки сервера-реплики. На экране первого терминала (Terminal #1) появились сообщения с подтверждениями, что реплика установила соединение с главным сервером и что содержимое WAL-файла было отправлено на реплику. На экране второго терминала (Terminal #2) появились сообщения о том, что репликация начинается, а также там были выведены UUID из системного пространства _cluster (один из них совпадает с UUID в первом терминале, поскольку оба сервера входят в общий кластер).

$ # Terminal 1
$ mkdir -p ~/tarantool_test_node_1
<... ...>
tarantool> box.space._cluster:select({0}, {iterator = 'GE'})
---
- - [1, '6190d919-1133-4452-b123-beca0b178b32']
...
tarantool>
<...> [11031] relay/127.0.0.1:58734/101/main I> recovery start
<...> [11031] relay/127.0.0.1:58734/101/main I> recovering from `./00000000000000000000.snap'
<...> [11031] relay/127.0.0.1:58734/101/main I> snapshot sent
<...> [11031] relay/127.0.0.1:58734/101/main I> recover from `./00000000000000000000.xlog'
<...> [11031] relay/127.0.0.1:58734/101/main recovery.cc:211 W> file
      `./00000000000000000000.xlog` wasn't correctly closed
<...> [11031] relay/127.0.0.1:58734/102/main I> recover from `./00000000000000000000.xlog'
$ # Terminal 2
$ mkdir -p ~/tarantool_test_node_2
$ cd ~/tarantool_test_node_2
$ rm -R ./*
$ ~/tarantool/src/tarantool
/home/username/tarantool/src/tarantool: version 1.7.0-1724-g033ed69
type 'help' for interactive help
tarantool> box.cfg{
         >   listen = 3302,
         >   replication_source = 'replicator:password@localhost:3301'
         > }
<...>
<...> [11243] main/101/interactive I> mapping 1073741824 bytes for tuple arena...
<...> [11243] main/101/interactive C> starting replication from localhost:3301
<...> [11243] main/102/applier/localhost:3301 I> connected to 1.7.0 at 127.0.0.1:3301
<...> [11243] main/102/applier/localhost:3301 I> authenticated
<...> [11243] main/102/applier/localhost:3301 I> downloading a snapshot from 127.0.0.1:3301
<...> [11243] main/102/applier/localhost:3301 I> done
<...> [11243] snapshot/101/main I> creating `./00000000000000000000.snap.inprogress'
<...> [11243] snapshot/101/main I> saving snapshot `./00000000000000000000.snap.inprogress'
<...> [11243] snapshot/101/main I> done
<...> [11243] iproto I> binary: started
<...> [11243] iproto I> binary: bound to 0.0.0.0:3302
<...> [11243] wal/101/main I> creating `./00000000000000000000.xlog.inprogress'
<...> [11243] main/101/interactive I> ready to accept requests
tarantool> box.space._cluster:select({0}, {iterator = 'GE'})
- - [1, '6190d919-1133-4452-b123-beca0b178b32']
  - [2, '236230b8-af3e-406b-b709-15a60b44c20c']
...

В первом терминале (Terminal #1) выполните следующие запросы:

tarantool> s = box.schema.space.create('tester')
tarantool> i = s:create_index('primary', {})
tarantool> s:insert{1, 'Tuple inserted on Terminal #1'}

Теперь вывод на экране выглядит следующим образом:

<... ...>
tarantool>
tarantool>
<...> [11031] relay/127.0.0.1:58734/101/main I> recovery start
<...> [11031] relay/127.0.0.1:58734/101/main I> recovering from `./00000000000000000000.snap'
<...> [11031] relay/127.0.0.1:58734/101/main I> snapshot sent
<...> [11031] relay/127.0.0.1:58734/101/main I> recover from `./00000000000000000000.xlog'
<...> [11031] relay/127.0.0.1:58734/101/main recovery.cc:211 W> file
      `./00000000000000000000.xlog` wasn't correctly closed
<...> [11031] relay/127.0.0.1:58734/102/main I> recover from `./00000000000000000000.xlog'
tarantool> s = box.schema.space.create('tester')
---
...
tarantool> i = s:create_index('primary', {})
---
...
tarantool> s:insert{1, 'Tuple inserted on Terminal #1'}
---
- [1, 'Tuple inserted on Terminal #1']
...
$ # Terminal 2
$ mkdir -p ~/tarantool_test_node_2
$ cd ~/tarantool_test_node_2
$ rm -R ./*
$ ~/tarantool/src/tarantool
/home/username/tarantool/src/tarantool: version 1.7.0-1724-g033ed69
type 'help' for interactive help
tarantool> box.cfg{
         >   listen = 3302,
         >   replication_source = 'replicator:password@localhost:3301'
         > }
<...>
<...> [11243] main/101/interactive I> mapping 1073741824 bytes for tuple arena...
<...> [11243] main/101/interactive C> starting replication from localhost:3301
<...> [11243] main/102/applier/localhost:3301 I> connected to 1.7.0 at 127.0.0.1:3301
<...> [11243] main/102/applier/localhost:3301 I> authenticated
<...> [11243] main/102/applier/localhost:3301 I> downloading a snapshot from 127.0.0.1:3301
<...> [11243] main/102/applier/localhost:3301 I> done
<...> [11243] snapshot/101/main I> creating `./00000000000000000000.snap.inprogress'
<...> [11243] snapshot/101/main I> saving snapshot `./00000000000000000000.snap.inprogress'
<...> [11243] snapshot/101/main I> done
<...> [11243] iproto I> binary: started
<...> [11243] iproto I> binary: bound to 0.0.0.0:3302
<...> [11243] wal/101/main I> creating `./00000000000000000000.xlog.inprogress'
<...> [11243] main/101/interactive I> ready to accept requests
tarantool> box.space._cluster:select({0}, {iterator = 'GE'})
- - [1, '6190d919-1133-4452-b123-beca0b178b32']
  - [2, '236230b8-af3e-406b-b709-15a60b44c20c']
...

В первом терминале успешно отработали операции CREATE и INSERT. Но во втором терминале ничего не произошло.

Во втором терминале (Terminal #2) выполните следующие запросы:

tarantool> s = box.space.tester
tarantool> s:select({1}, {iterator = 'GE'})
tarantool> s:insert{2, 'Tuple inserted on Terminal #2'}

Теперь вывод на экране выглядит следующим образом:

<... ...>
tarantool>
tarantool>
<...> [11031] relay/127.0.0.1:58734/101/main I> recovery start
<...> [11031] relay/127.0.0.1:58734/101/main I> recovering from `./00000000000000000000.snap'
<...> [11031] relay/127.0.0.1:58734/101/main I> snapshot sent
<...> [11031] relay/127.0.0.1:58734/101/main I> recover from `./00000000000000000000.xlog'
<...> [11031] relay/127.0.0.1:58734/101/main recovery.cc:211 W> file
      `./00000000000000000000.xlog` wasn't correctly closed
<...> [11031] relay/127.0.0.1:58734/102/main I> recover from `./00000000000000000000.xlog'
tarantool> s = box.schema.space.create('tester')
---
...
tarantool> i = s:create_index('primary', {})
---
...
tarantool> s:insert{1, 'Tuple inserted on Terminal #1'}
---
- [1, 'Tuple inserted on Terminal #1']
...
<... ...>
tarantool> box.space._cluster:select({0}, {iterator = 'GE'})
- - [1, '6190d919-1133-4452-b123-beca0b178b32']
  - [2, '236230b8-af3e-406b-b709-15a60b44c20c']
...
tarantool> s = box.space.tester
---
...
tarantool> s:select({1}, {iterator = 'GE'})
---
- - [1, 'Tuple inserted on Terminal #1']
...
tarantool> s:insert{2, 'Tuple inserted on Terminal #2'}
---
- [2, 'Tuple inserted on Terminal #2']
...

Во втором терминале успешно отработали операции SELECT и INSERT. Но в первом терминале ничего не произошло.

В первом терминале (Terminal #1) выполните следующие запросы и команды:

$ os.exit()
$ ls -l ~/tarantool_test_node_1
$ ls -l ~/tarantool_test_node_2

Теперь Tarantool-сервер в первом терминале остановлен. В окне второго терминала появились сообщения об этом событии. С помощью команд ls -l мы убедились, что на обоих серверах создались файлы-снимки с одинаковыми размерами, поскольку там содержатся одни и те же кортежи.

<... ...>
tarantool> s:insert{1, 'Tuple inserted on Terminal #1'}
---
- [1, 'Tuple inserted on Terminal #1']
...
$ ls -l ~/tarantool_test_node_1
tarantool> os.exit()
total 8
-rw-rw-r-- 1  4925 May  5 11:12 00000000000000000000.snap
-rw-rw-r-- 1   634 May  5 11:45 00000000000000000000.xlog
$ ls -l ~/tarantool_test_node_2/
total 8
-rw-rw-r-- 1  4925 May  5 11:20 00000000000000000000.snap
-rw-rw-r-- 1   704 May  5 11:38 00000000000000000000.xlog
$
<... ...>
tarantool> s:select({1}, {iterator = 'GE'})
---
- - [1, 'Tuple inserted on Terminal #1']
...
tarantool> s:insert{2, 'Tuple inserted on Terminal #2'}
---
- [2, 'Tuple inserted on Terminal #2']
...
tarantool>
<...> [25579] main/103/replica/localhost:3301 I> can't read row
<...> [25579] main/103/replica/localhost:3301 !> SystemError
  unexpected EOF when reading from socket,
  called on fd 10, aka 127.0.0.1:50884, peer of 127.0.0.1:3301: Broken pipe
<...> [25579] main/103/replica/localhost:3301 I> will retry every 1 second

Во втором терминале (Terminal #2) проигнорируйте сообщения об ошибках и выполните следующие запросы:

tarantool> box.space.tester:select({0}, {iterator = 'GE'})
tarantool> box.space.tester:insert{3, 'Another'}

Теперь вывод на экране выглядит следующим образом (сообщения об ошибках мы не приводим):

<... ...>
tarantool> s:insert{1, 'Tuple inserted on Terminal #1'}
---
- [1, 'Tuple inserted on Terminal #1']
...
$ ls -l ~/tarantool_test_node_1
tarantool> os.exit()
total 8
-rw-rw-r-- 1  4925 May  5 11:12 00000000000000000000.snap
-rw-rw-r-- 1   634 May  5 11:45 00000000000000000000.xlog
$ ls -l ~/tarantool_test_node_2/
total 8
-rw-rw-r-- 1  4925 May  5 11:20 00000000000000000000.snap
-rw-rw-r-- 1   704 May  5 11:38 00000000000000000000.xlog
$
<... ...>
tarantool> s:insert{2, 'Tuple inserted on Terminal #2'}
---
- [2, 'Tuple inserted on Terminal #2']
...
tarantool>
<...> [11243] main/105/applier/localhost:3301 I> can't read row
<...> [11243] main/105/applier/localhost:3301 coio.cc:352 !> SystemError
  unexpected EOF when reading from socket,
  called on fd 6, aka 127.0.0.1:58734, peer of 127.0.0.1:3301: Broken pipe
<...> [11243] main/105/applier/localhost:3301 I> will retry every 1 second
tarantool> box.space.tester:select({0}, {iterator = 'GE'})
---
- - [1, 'Tuple inserted on Terminal #1']
  - [2, 'Tuple inserted on Terminal #2']
...
tarantool> box.space.tester:insert{3, 'Another'}
---
- [3, 'Another']
...

Запросы SELECT и INSERT во втором терминале отработали несмотря на то, что сервер в первом терминале остановлен.

В первом терминале (Terminal #1) выполните следующие запросы:

$ ~/tarantool/src/tarantool
tarantool> box.cfg{listen = 3301}
tarantool> box.space.tester:select({0}, {iterator = 'GE'})

Теперь вывод на экране выглядит следующим образом:

<... ...>
tarantool> s:insert{1, 'Tuple inserted on Terminal #1'}
---
- [1, 'Tuple inserted on Terminal #1']
...
$ ls -l ~/tarantool_test_node_1
tarantool> os.exit()
total 8
-rw-rw-r-- 1  4925 May  5 11:12 00000000000000000000.snap
-rw-rw-r-- 1   634 May  5 11:45 00000000000000000000.xlog
$ ls -l ~/tarantool_test_node_2/
total 8
-rw-rw-r-- 1  4925 May  5 11:20 00000000000000000000.snap
-rw-rw-r-- 1   704 May  5 11:38 00000000000000000000.xlog
$ ~/tarantool/src/tarantool
<...>
tarantool> box.cfg{listen = 3301}
<...> [22612] main/101/interactive I> ready to accept requests
<... ...>
tarantool> box.space.tester:select({0}, {iterator = 'GE'})
- - [1, 'Tuple inserted on Terminal #1']
...
<... ...>
tarantool> s:insert{2, 'Tuple inserted on Terminal #2'}
---
- [2, 'Tuple inserted on Terminal #2']
...
tarantool>
<...> [25579] main/103/replica/localhost:3301 I> can't read row
<...> [25579] main/103/replica/localhost:3301 !> SystemError
  unexpected EOF when reading from socket,
  called on fd 10, aka 127.0.0.1:50884, peer of 127.0.0.1:3301: Broken pipe
<...> [25579] main/103/replica/localhost:3301 I> will retry every 1 second
tarantool> box.space.tester:select({0}, {iterator = 'GE'})
---
- - [1, 'Tuple inserted on Terminal #1']
  - [2, 'Tuple inserted on Terminal #2']
...
tarantool> box.space.tester:insert{3, 'Another'}
---
- [3, 'Another']
...
<...> [11243] main/105/applier/localhost:3301 I> connected to 1.7.0 at 127.0.0.1:3301
<...> [11243] main/105/applier/localhost:3301 I> authenticated

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

В первом терминале (Terminal #1) введите:

tarantool> box.cfg{
         >   replication_source = 'replicator:password@localhost:3302'
         > }
tarantool> box.space.tester:select({0}, {iterator = 'GE'})

Теперь вывод на экране выглядит следующим образом:

<... ...>
$ ~/tarantool/src/tarantool
<...>
tarantool> box.cfg{listen = 3301}
<...> [22612] main/101/interactive I> ready to accept requests
<... ...>
tarantool> box.space.tester:select({0}, {iterator = 'GE'})
---
- - [1, 'Tuple inserted on Terminal #1']
...
tarantool> box.cfg{
         >   replication_source='replicator:password@localhost:3302'
         > }
[28987] main/101/interactive C> starting replication from localhost:3302
---
...
[22612] main/101/interactive C> starting replication from localhost:3302
[22612] main/101/interactive I> set 'replication_source' configuration
        option to "replicator:password@localhost:3302"
[22612] main/104/applier/localhost:3302 I> connected to 1.7.0 at 127.0.0.1:3302
[22612] main/104/applier/localhost:3302 I> authenticated
[22612] wal/101/main I> creating `./00000000000000000008.xlog.inprogress'
[22612] relay/127.0.0.1:33510/102/main I> done `./00000000000000000000.xlog'
[22612] relay/127.0.0.1:33510/102/main I> recover from `./00000000000000000008.xlog'
tarantool> box.space.tester:select({0}, {iterator = 'GE'})
---
- - [1, 'Tuple inserted on Terminal #1']
  - [2, 'Tuple inserted on Terminal #2']
  - [3, 'Another']
...
<... ...>
tarantool> s:insert{2, 'Tuple inserted on Terminal #2'}
---
- [2, 'Tuple inserted on Terminal #2']
...
tarantool>
<...> [25579] main/103/replica/localhost:3301 I> can't read row
<...> [25579] main/103/replica/localhost:3301 !> SystemError
  unexpected EOF when reading from socket,
  called on fd 10, aka 127.0.0.1:50884, peer of 127.0.0.1:3301: Broken pipe
<...> [25579] main/103/replica/localhost:3301 I> will retry every 1 second
tarantool> box.space.tester:select({0}, {iterator = 'GE'})
---
- - [1, 'Tuple inserted on Terminal #1']
  - [2, 'Tuple inserted on Terminal #2']
...
tarantool> box.space.tester:insert{3, 'Another'}
---
- [3, 'Another']
...
<...> [11243] main/105/applier/localhost:3301 I> connected to 1.7.0 at 127.0.0.1:3301
<...> [11243] main/105/applier/localhost:3301 I> authenticated
tarantool>
<...> [11243] relay/127.0.0.1:36150/101/main I> recover from `./00000000000000000000.xlog'
<...> [11243] relay/127.0.0.1:36150/101/main recovery.cc:211 W> file
     `./00000000000000000000.xlog` wasn't correctly closed
<...> [11243] relay/127.0.0.1:36150/102/main I> recover from `./00000000000000000000.xlog'

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

Чтобы удалить все тестовые данные, выполните “os.exit()” на обоих терминалах, а затем на каждом из них выполните следующие команды:

$ cd ~
$ rm -R ~/tarantool_test_node_1
$ rm -R ~/tarantool_test_node_2