Tarantool CE/EE Documentation portal logo
Помощь
Обновлена 15 сентября 2026 г. в 08:55

Решение конфликтов репликации

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

Замена по одному и тому же первичному ключу

Случай 1: Есть два экземпляра Tarantool. Например, выполняется операция replace с одним и тем же первичным ключом на обоих экземплярах одновременно. Это вызывает конфликт: необходимо решить, какой кортеж сохранить, а какой отбросить.

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

Сначала нужен триггер before_replace() на спейсе, в котором могут возникнуть конфликты. В этом триггере можно сравнить старую и новую записи реплики и выбрать, какую из них использовать (либо полностью пропустить обновление, либо объединить две записи).

Затем необходимо установить триггер в нужный момент - до того, как спейс начнет получать обновления. Обычно триггер before_replace устанавливается в момент создания спейса, поэтому потребуется триггер на системном спейсе _space, чтобы отследить момент создания целевого спейса и установить триггер на нем. Это может быть триггер on_replace().

Разница между триггерами before_replace и on_replace в том, что on_replace вызывается после вставки строки в спейс, а before_replace вызывается перед ней.

Устанавливать триггер _space:on_replace() также нужно в корректный момент. Лучшее время для его использования – это когда только что создан _space, что является триггером функции box.ctl.on_schema_init().

Также потребуется использовать функцию box.on_commit для получения доступа к создаваемому спейсу. В результате получится следующий фрагмент кода:

local my_space_name = 'my_space'local my_trigger = function(old, new) ... end -- ваша функция, устраняющая конфликтbox.ctl.on_schema_init(function()    box.space._space:on_replace(function(old_space, new_space)        if not old_space and new_space and new_space.name == my_space_name then            box.on_commit(function()                box.space[my_space_name]:before_replace(my_trigger)            end        end    end)end)

Предотвращение дублирующей вставки

Случай 2: В наборе реплик из двух мастеров оба пытаются вставить данные с одним и тем же уникальным ключом:

tarantool> box.space.tester:insert{1, 'data'}

Это вызовет сообщение об ошибке дубликата ключа (Duplicate key exists in unique index 'primary' in space 'tester'), и репликация остановится. Такое поведение системы обеспечивается использованием рекомендуемого значения false (по умолчанию) для конфигурационного параметра replication_skip_conflict.

$ # error messages from master #12017-06-26 21:17:03.233 [30444] main/104/applier/rep_user@100.96.166.1 I> can't read row2017-06-26 21:17:03.233 [30444] main/104/applier/rep_user@100.96.166.1 memtx_hash.cc:226 E> ER_TUPLE_FOUND:Duplicate key exists in unique index 'primary' in space 'tester'2017-06-26 21:17:03.233 [30444] relay/[::ffff:100.96.166.178]/101/main I> the replica has closed its socket, exiting2017-06-26 21:17:03.233 [30444] relay/[::ffff:100.96.166.178]/101/main C> exiting the relay loop$ # error messages from master #22017-06-26 21:17:03.233 [30445] main/104/applier/rep_user@100.96.166.1 I> can't read row2017-06-26 21:17:03.233 [30445] main/104/applier/rep_user@100.96.166.1 memtx_hash.cc:226 E> ER_TUPLE_FOUND:Duplicate key exists in unique index 'primary' in space 'tester'2017-06-26 21:17:03.234 [30445] relay/[::ffff:100.96.166.178]/101/main I> the replica has closed its socket, exiting2017-06-26 21:17:03.234 [30445] relay/[::ffff:100.96.166.178]/101/main C> exiting the relay loop

Если проверить статус репликации через функцию box.info, то можно увидеть, что репликация на мастере №1 остановлена (1.upstream.status = stopped). Кроме того, данные с этого мастера не реплицируются (группа 1.downstream отсутствует в отчете), поскольку возникает та же ошибка:

# статусы репликации (отчет от мастера №3)tarantool> box.info---- version: 1.7.4-52-g980d30092  id: 3  ro: false  vclock: {1: 9, 2: 1000000, 3: 3}  uptime: 557  lsn: 3  vinyl:   cluster:    uuid: 34d13b1a-f851-45bb-8f57-57489d3b3c8b  pid: 30445  status: running  signature: 1000012  replication:    1:      id: 1      uuid: 7ab6dee7-dc0f-4477-af2b-0e63452573cf      lsn: 9      upstream:        peer: replicator@192.168.0.101:3301        lag: 0.00050592422485352        status: stopped        idle: 445.8626639843        message: Duplicate key exists in unique index 'primary' in space 'tester'    2:      id: 2      uuid: 9afbe2d9-db84-4d05-9a7b-e0cbbf861e28      lsn: 1000000      upstream:        status: follow        idle: 201.99915885925        peer: replicator@192.168.0.102:3301        lag: 0.0015020370483398      downstream:        vclock: {1: 8, 2: 1000000, 3: 3}    3:      id: 3      uuid: e826a667-eed7-48d5-a290-64299b159571      lsn: 3  uuid: e826a667-eed7-48d5-a290-64299b159571...

О том, как разрешить конфликт репликации путем пересоздания реплики, см. в разделе Разрешение конфликтов репликации.

Рассинхронизация репликации

Пример описывает выполнение следующей операции в кластере из двух экземпляров с конфигурацией мастер-мастер:

tarantool> box.space.tester:upsert({1}, {{'=', 2, box.info.uuid}})

Когда эта операция применяется на обоих экземплярах в наборе реплик:

# на мастере #1tarantool> box.space.tester:upsert({1}, {{'=', 2, box.info.uuid}})# на мастере #2tarantool> box.space.tester:upsert({1}, {{'=', 2, box.info.uuid}})

... можно получить следующие результаты в зависимости от порядка выполнения:

  • В строке каждого мастера содержится UUID мастера №1.
  • В строке каждого мастера содержится UUID мастера №2.
  • На мастере №1 содержится UUID мастера №2, и наоборот.

Коммутативные изменения

Случаи, описанные в предыдущих раздела - примеры некоммутативных операций, то есть операций, результат которых зависит от порядка выполнения. Напротив, для коммутативных операций порядок выполнения не имеет значения.

Эта команда представляет собой коммутативную операцию:

tarantool> box.space.tester:upsert{{1, 0}, {{'+', 2, 1)}

Ее выполнение приводит к одинаковому результату, независимо от порядка, в котором обновление применяется на других мастерах.

Использование триггеров

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

local my_space_name = 'test'local my_trigger = function(old, new, sp, op)    -- op:  ‘INSERT’, ‘DELETE’, ‘UPDATE’, or ‘REPLACE’    if new == nil then        print("No new during "..op, old)        return -- deletes are ok    end    if old == nil then        print("Insert new, no old", new)        return new  -- insert without old value: ok    end    print(op.." duplicate", old, new)    if op == 'INSERT' then        if new[2] > old[2] then            -- Creating new tuple will change op to ‘REPLACE’            return box.tuple.new(new)        end        return old    end    if new[2] > old[2] then        return new    else        return old    end    returnendbox.ctl.on_schema_init(function()    box.space._space:on_replace(function(old_space, new_space)        if not old_space and new_space and new_space.name == my_space_name then            box.on_commit(function()                box.space[my_space_name]:before_replace(my_trigger)            end)        end    end)end)