Этой статьёй я хочу познакомить Вас с относительно новым механизмом тарантула - полноценные MVCC транзакции.

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

Транзакции - старый механизм

Старый механизм транзакций представлял собой простой батчинг записи на диск. Сообщив тарантулу о том, что Вы начинаете транзакцию (box.begin) Вы де-факто просто отключали все последующие переключения на другие файберы и все Ваши модифицирующие и читающие базу запросы объединялись в один батч так, что никакой другой запрос не мог вклиниться между ними.

рисунок 1

Предположим у нас есть код, который перемещает деньги со счёта одного пользователя на счёт другого:

function move_money(user_from, user_to, sum)
   box.begin()

   balance_from = box.space.accounts:get(user_from)
   balance_to = box.space.accounts:get(user_to)

   box.space.accounts:update(
    balance_from,
    {
        { '=', 'balance', balance_from['balance'] - sum }
    }
   )

   box.space.accounts:update(
    balance_to,
    {
        { '=', 'balance', balance_to['balance'] + sum }
    }
   )

   box.commit()
end

Примечание: В данном примере используются явные присвоения полей БД. Понятно, что есть возможность вычислять некоторые значения средствами самой БД. В данном примере считаем, что такие вычисления почему-то невозможны (например имеют большую, нежели в примере сложность).

Сразу после вызова box.begin модифицирующие базу запросы перестают делать переключение контекста на другие файберы. Таким образом, все команды от box.begin до box.commit объединяются в единый пакет (batch) и получается (почти) полный эквивалент serializable-транзакций.

Однако остаются проблемы:

  • видны Read-Uncommited данные (пока всё хорошо - всё очень хорошо, а когда всё плохо, то всё очень плохо)
  • в процессе обработки такой транзакции нельзя что-то ещё поделать. Любое переключение контекста (RPC запрос, итп) приводит к завершению (откату) транзакции.

Транзакции MVCC

Начиная с версии 2.6.1 в Tarantool появился менеджер транзакций, включить который можно установив значение memtx_use_mvcc_engine в true при конфигурировании тарантула.

Включив эту опцию мы получаем полноценные транзакции: - снимающие проблему Read-Uncommited - позволяющие разрешать конфликты конкурирующих запросов - позволяющие выполнять произвольную работу внутри транзакции (делать yield)

Теперь можно написать:


box.begin()

user = users:get(user_id)

data = rpc_some_request(user)

users:update(user['user_id'], { { '=', 'data', data } })

box.commit()

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

tarantool> box.begin()
---
...

tarantool> box.space.accounts:get('petya_0001')
---
- ['petya_0001', -10]
...

tarantool> box.commit()
2021-06-07 11:56:27.860 [2866769] main/103/test.lua txn.c:704 E> ER_TRANSACTION_YIELD: Transaction has been aborted by a fiber yield
---
- error: Transaction has been aborted by a fiber yield
...