Триггеры
В этом руководстве показано, как настроить конфигурацию мастер-мастер, а также установить персистентный триггер для разрешения конфликта репликации.
Подробная информация о триггерах доступна в документации Tarantool.
Содержание:
- Пререквизиты
- Запуск стенда и подключение к узлам
- Конфигурация мастер-мастер
- Конфликт репликации мастер-мастер
- Реализация механизма разрешения конфликта репликации
- Остановка стенда
Для выполнения примера требуются:
-
установленный Docker-образ Tarantool DB;
-
приложение Docker Compose;
-
исходные файлы примера
triggers.
Для успешного запуска должны быть свободны следующие порты:
- 2379
- 3301
- 3302
- 8081
Перейдите в папку с примером triggers:
cd ./docs/docs/ru/assets/examples/triggers
Запустите стенд:
make start
Команда развернет следующий стенд:
- кластер Tarantool DB, который состоит из одного набора реплик с двумя мастер-узлами;
- кластер etcd из 3 узлов;
- 1 узел Tarantool Cluster Manager (TCM).
После запуска должны работать все контейнеры, кроме init_host.
Также после запуска кластера становится доступен веб-интерфейс TCM. Для входа в TCM откройте в браузере адрес http://localhost:8081. Логин и пароль для входа:
- Username:
admin - Password:
secret
Обратите внимание, что в TCM на вкладке Stateboard в наборе реплик два мастера:

Выберите экземпляр кластера storage-1-msk. В открывшемся окне выберите вкладку Terminal.
Во вкладке Terminal добавьте временную функцию now() для удобства ввода данных:
function now()local datetime = require('datetime')return datetime.now()end
Откройте веб-интерфейс TCM во второй вкладке браузера. В TCM перейдите на вкладку Stateboard и выберите
второй экземпляр кластера storage-1-spb. В открывшемся окне перейдите на
вкладку Terminal
и добавьте туда временную функцию now(), описанную выше.
Теперь в браузере открыты две вкладки TCM (далее TCM 1 и TCM 2) для работы с узлами кластера
storage-1-msk и storage-1-spb соответственно.
В TCM 1 во вкладке Terminal выполните следующую команду:
box.space.my_space:replace({1, now(), 'Too'})
Теперь в TCM 2 во вкладке Terminal выполните такую команду:
box.space.my_space:replace({2, now(), 'Foo'})
Чтобы просмотреть содержимое спейсов, выполните эту команду во вкладках Terminal в TCM 1 и TCM 2:
box.space.my_space:fselect()
Несмотря на то, что записи были вставлены на разных экземплярах, обе записи успешно добавлены в спейс:
---
- |-
+-----+-----------------------------+-----+
| id | dt |data |
+-----+-----------------------------+-----+
| 1 |"2025-02-13T08:00:35.839119Z"|"Too"|
| 2 |"2025-02-13T08:00:41.792330Z"|"Foo"|
+-----+-----------------------------+-----+
...
Если набор реплик работает в режиме мастер-мастер, одновременная запись по одному и тому же первичному ключу с разных экземпляров кластера приводит к конфликту репликации: в наборе реплик у записи с этим первичным ключом возникают две версии текущего значения. Длительность окна, в течение которого запись в два узла считается одновременной, определяется скоростью репликации между этими узлами и зависит от сетевых задержек. В общем случае речь идёт о нескольких миллисекундах.

Рекомендуется использовать одно из решений ниже:
- использовать коммутативные операции;
- реализовать механизм разрешения конфликта репликации.
Кроме того, в системах с репликацией мастер-мастер по описанным выше причинам нельзя использовать
автоинкремент в индексах. Используйте вместо этого значения типа UID.
Подробнее о конфликтах репликации читайте в документации Tarantool.
Поскольку воспроизвести одновременную запись в оба узла сложно, в примере для демонстрации конфликта репликации мастер-мастер будет специальным образом нарушен процесс репликации.
Выполните во вкладках Terminal в TCM 1 и TCM 2 следующие команды:
replication_bak = box.cfg.replicationbox.cfg({replication = {}})
Добавьте в TCM 1 во вкладке Terminal такую запись:
box.space.my_space:replace({3, now(), 'AAAA'})
В TCM 2 во вкладке Terminal вставьте следующую запись:
box.space.my_space:replace({3, now(), 'BBBB'})
Чтобы восстановить процесс репликации, выполните команду на обоих узлах (во вкладках Terminal в TCM 1 и TCM 2):
box.cfg({replication = replication_bak})
Чтобы просмотреть содержимое спейсов, выполните эту команду во вкладках Terminal в TCM 1 и TCM 2:
box.space.my_space:fselect()
На первом экземпляре вывод будет выглядеть так:
---
- |-
+-----+-----------------------------+------+
| id | dt | data |
+-----+-----------------------------+------+
| 1 |"2025-02-13T08:00:35.839119Z"|"Too" |
| 2 |"2025-02-13T08:00:41.792330Z"|"Foo" |
| 3 |"2025-02-13T08:12:34.279904Z"|"BBBB"|
+-----+-----------------------------+------+
...
На втором экземпляре вывод будет следующий:
---
- |-
+-----+-----------------------------+------+
| id | dt | data |
+-----+-----------------------------+------+
| 1 |"2025-02-13T08:00:35.839119Z"|"Too" |
| 2 |"2025-02-13T08:00:41.792330Z"|"Foo" |
| 3 |"2025-02-13T08:12:27.939872Z"|"AAAA"|
+-----+-----------------------------+------+
...
Видно, что данные не согласованы, и каждый экземпляр кластера имеет свою версию данных.
Строгого критерия, однозначно определяющего порядок, в котором были сделаны записи в кластер, не существует. Это означает, что критерий нужно выбирать персонально для каждого приложения. Виды критериев:
- временные метки. Временные метки имеют свои недостатки, в том числе разность времени между серверами и високосные секунды;
- критерии, характерные для данных конкретного спейса.
В этом примере в качестве критерия используется временная метка. Логику разрешения конфликта будет выполнять триггер перед записью в спейс.
Откройте веб-интерфейс TCM в третьей вкладке браузера. Далее:
-
В левом меню выберите вкладку Migrations.
-
Нажмите
+, чтобы добавить новую миграцию. -
Добавьте новую миграцию с названием
002_test.lua.
-
Скопируйте в поле ввода код миграции
002_test.lua:local function apply()-- Описана логика работы триггераlocal body = [[function (old_tuple, new_tuple, space_name, operation_name)if old_tuple and new_tuple then-- Индекс 2 соответствует полю dtif old_tuple[2] < new_tuple[2] thenreturn new_tupleendelsereturn new_tupleendreturn old_tupleend]]-- Объявлен персистентный триггерbox.schema.func.create('example.replicated_trigger', {body = body,trigger = 'box.space.my_space.before_replace'})return trueend-- Стандартный блок возврата для модулей миграцииreturn {apply = {scenario = apply,}} -
Нажмите кнопки Save и Apply, чтобы сохранить и применить добавленную миграцию. В этой миграции был добавлен триггер, который будет продолжать работать после перезапуска экземпляров.
Обратите внимание, что для нескольких спейсов можно указывать один триггер:
box.schema.func.create('example.replicated_trigger', {body = body,trigger = {'box.space.my_space1.before_replace', -- < --'box.space.my_space2.before_replace', -- < --}})
Чтобы снова нарушить репликацию, выполните следующие команды на обоих узлах (во вкладках Terminal в TCM 1 и TCM 2):
replication_bak = box.cfg.replicationbox.cfg({replication = {}})
На первом узле, во вкладке Terminal в TCM 1, добавьте такую запись:
box.space.my_space:replace({4, now(), 'CCCC'})
На втором узле, во вкладке Terminal в TCM 2, добавьте следующую запись:
box.space.my_space:replace({4, now(), 'DDDD'})
Чтобы включить репликацию, выполните команду на обоих узлах:
box.cfg({replication = replication_bak})
Чтобы просмотреть содержимое спейсов, выполните эту команду на обоих узлах:
box.space.my_space:fselect()
Четвёртая строка будет одинаковой везде, потому что сработал триггер, оставивший из двух версий более свежую:
---
- |-
+-----+-----------------------------+------+
| id | dt | data |
+-----+-----------------------------+------+
| 1 |"2025-02-13T08:00:35.839119Z"|"Too" |
| 2 |"2025-02-13T08:00:41.792330Z"|"Foo" |
| 3 |"2025-02-13T08:12:34.279904Z"|"BBBB"|
| 4 |"2025-02-13T08:56:01.204955Z"|"DDDD"|
+-----+-----------------------------+------+
...
Для остановки стенда в терминале ОС выполните следующую команду:
make stop