Триггеры¶
В этом руководстве показано, как настроить конфигурацию мастер-мастер, а также установить персистентный триггер для разрешения конфликта репликации.
Подробная информация о триггерах доступна в документации Tarantool.
Содержание:
Пререквизиты¶
Для выполнения примера требуются:
установленный Docker-образ Tarantool DB;
приложение Docker Compose;
исходные файлы примера
triggers
.Примечание
Есть два способа получить исходные файлы примера:
Архив с полной документацией Tarantool DB, полученный по почте или скачанный в личном кабинете tarantool.io. Пример архива:
tarantooldb-documentation-2.0.0.tar.gz
. Примерtriggers
расположен в таком архиве в директории./doc/examples/triggers/
.Отдельный архив triggers.tar.gz, скачанный c сайта Tarantool.
Запуск стенда и подключение к узлам¶
Для успешного запуска должны быть свободны следующие порты:
2379
3301
3302
8081
Перейдите в папку с примером triggers
:
cd ./doc/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.replication
box.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 соответствует полю dt if old_tuple[2] < new_tuple[2] then return new_tuple end else return new_tuple end return old_tuple end ]] -- Объявлен персистентный триггер box.schema.func.create('example.replicated_trigger', { body = body, trigger = 'box.space.my_space.before_replace' }) return true end -- Стандартный блок возврата для модулей миграции 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.replication
box.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"|
+-----+-----------------------------+------+
...