Триггеры | Tdb

Триггеры

В этом руководстве показано, как настроить конфигурацию мастер-мастер, а также установить персистентный триггер для разрешения конфликта репликации.

Подробная информация о триггерах доступна в документации 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 в третьей вкладке браузера. Далее:

  1. В левом меню выберите вкладку Migrations.

  2. Нажмите +, чтобы добавить новую миграцию.

  3. Добавьте новую миграцию с названием 002_test.lua.

  4. Скопируйте в поле ввода код миграции 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,
        }
    }
    
  5. Нажмите кнопки 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"|
  +-----+-----------------------------+------+
...

Остановка стенда

Для остановки стенда в терминале ОС выполните следующую команду:

make stop
Found what you were looking for?
Feedback