Версия:

Контроль транзакций

Контроль транзакций

Транзакции в Tarantool’е происходят в файберах в одном потоке. Вот почему Tarantool дает гарантию атомарности выполнения. На этом следует сделать акцент.

Потоки, файберы и передача управления

Как Tarantool выполняет основные операции? Для примера возьмем такой запрос:

tarantool> box.space.tester:update({3}, {{'=', 2, 'size'}, {'=', 3, 0}})

This is equivalent to the following SQL statement for a table that stores primary keys in field[1]:

UPDATE tester SET "field[2]" = 'size', "field[3]" = 0 WHERE "field[1]" = 3

Этот запрос будет обработан тремя потоками операционной системы:

  1. Если мы передадим запрос на удаленный клиент, сетевой поток на стороне сервера получит запрос, разберет выражение и преобразует его в выполняемое сообщение сервера, которое уже проверено. Такое сообщение экземпляр сервера может понимать без повторного разбора.

  2. Сетевой поток отправляет это сообщение в поток «обработки транзакций» с помощью шины передачи сообщений без блокировок. Lua-программы выполняются непосредственно в потоке обработки транзакций и не требуют разбора и подготовки.

    Поток обработки транзакций экземпляра использует индекс на поле первичного ключа field[1], чтобы найти нужный кортеж. Он проверяет, что данный кортеж можно обновить (мы хотим лишь изменить значение не индексированного поля на более короткое, и вряд ли что-то пойдет не так).

  3. Поток обработки транзакций отправляет сообщение в поток упреждающей записи в журнал (WAL) для коммита транзакции. По завершении поток WAL отправляет ответ с результатом COMMIT (коммит) или ROLLBACK (откат) на клиент.

Обратите внимание, что в Tarantool’е есть только один поток обработки транзакций. Некоторые уже привыкли к мысли, что потоков для обработки данных в базе данных может быть много (например, поток №1 читает данные из строки №x, в то время как поток №2 записывает данные в столбец №y). В случае с Tarantool’ом такого не происходит. Доступ к базе есть только у потока обработки транзакций, и на каждый экземпляр Tarantool’а есть только один такой поток.

Как и любой другой поток Tarantool’а, поток обработки транзакций может управлять множеством файберов. Файбер — это набор команд, среди которых могут быть и сигналы «передачи управления». Поток обработки транзакций выполняет все команды, пока не увидит такой сигнал, и тогда он переключается на выполнение команд из другого файбера. Например, таким образом поток обработки транзакций сначала выполняет чтение данных из строки №x для файбера №1, а затем выполняет запись в строку №y для файбера №2.

Передача управления необходима, в противном случае, поток обработки транзакции заклинит на одном файбере. Есть два типа передачи управления:

  • неявная передача управления: каждая операция по изменению данных или доступ к сети вызывают неявную передачу управления, а также каждое выражение, которое проходит через клиент Tarantool’а, вызывает неявную передачу управления.
  • явная передача управления: в Lua-функции можно (и нужно) добавить выражения «передачи управления» для предотвращения захвата ЦП. Это называется кооперативной многозадачностью.

Кооперативная многозадачность

Кооперативная многозадачность означает, что если запущенный файбер намеренно не передаст управление, он не вытесняется каким-либо другим файбером. Но запущенный файбер намеренно передает управление, когда обнаруживает “точку передачи управления”: коммит транзакции, вызов операционной системы или запрос явной «передачи управления». Любой вызов системы, который может блокировать файбер, будет производиться асинхронно, а запущенный файбер, который должен ожидать системного вызова, будет вытеснен так, что другой готовый к работе файбер занимает его место и становится запущенным файбером.

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

При небольших запросах, таких как простые UPDATE, INSERT, DELETE или SELECT, происходит справедливое планирование файберов: немного времени требуется на обработку запроса, планирование записи на диск и передачу управления на файбер, обслуживающий следующего клиента.

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

Транзакции

В отсутствие транзакций любая функция, в которой есть точки передачи управления, может видеть изменения в состоянии базы данных, вызванные вытесняющими файберами. Составные транзакции предназначены для изоляции: каждая транзакция видит постоянное состояние базы данных и делает атомарные коммиты изменений. Во время коммита происходит передача управления, а все транзакционные изменения записываются в журнал упреждающей записи в отдельный пакет. Или, при необходимости, можно откатить изменения — полностью или на определенную точку сохранения.

Чтобы осуществить изоляцию, Tarantool использует простой планировщик с оптимистичным управлением: транзакция подтверждена первой — выигрывает. Если параллельная активная транзакция читает значение, измененное подтвержденной транзакцией, она прерывается.

Кооперативный планировщик обеспечивает, что в отсутствие передачи управления составная транзакция не вытесняется, поэтому никогда не прерывается. Таким образом, понимание передачи управления необходимо для написания кода без прерываний.

Примечание

На сегодняшний день нельзя смешивать движки базы данных в транзакции.

Правила неявной передачи управления

Единственные запросы явной передачи данных в Tarantool’е отправляют fiber.sleep() и fiber.yield(), но многие другие запросы «неявно» подразумевают передачу управления, поскольку цель Tarantool’а — избежать блокировок.

Операции по изменению базы данных обычно не передают управление, но это зависит от движка:

  • В memtx’е чтение и запись не требуют ввода-вывода и не передают управление.
  • В vinyl’е не все данные находятся в оперативной памяти, и запрос SELECT часто подразумевает дисковый ввод-вывод и, следовательно, передачу управления, пока запись ожидает освобождения памяти, что также вызывает передачу управления.

В режиме «автокоммита» все операции по изменению данных сопровождаются автоматическим коммитом, который передает управление. Также передает управление явный коммит составной транзакции box.commit().

Многие функции в модулях fio, net_box, console и socket (запросы «ОС» и «сети») передают управление.

Пример №1

  • Движок = memtx
    В select() insert() управление передается один раз в конце вставки, что вызвано неявным коммитом; select() ничего не записывает в WAL-файл, поэтому не передает управление.
  • Движок = vinyl
    В select() insert() управление передается от одного до трех раз, поскольку select() может передавать управление, если данные не находятся в кэше, insert() может передавать управление в ожидании свободной памяти, а при коммите управление передается неявно.
  • Последовательность begin() insert() insert() commit() передает управление только при коммите, если движок — memtx, и может передавать управление до 3 раз, если движок — vinyl.

Пример №2

Предположим, что в спейсе ‘tester’ существуют кортежи, третье поле которых представляет собой положительную сумму в долларах. Начнем транзакцию, снимем сумму из кортежа №1, внесем ее в кортеж №2 и завершим транзакцию, подтверждая изменения.

tarantool> function txn_example(from, to, amount_of_money)
                 >   box.begin()
                 >   box.space.tester:update(from, {{'-', 3, amount_of_money}})
                 >   box.space.tester:update(to,   {{'+', 3, amount_of_money}})
                 >   box.commit()
                 >   return "ok"
                 > end
        ---
        ...
        tarantool> txn_example({999}, {1000}, 1.00)
        ---
        - "ok"
        ...

Если wal_mode = ‘none’, то при коммите управление не передается неявно, потому что не идет запись в WAL-файл.

Если задача интерактивная — отправка запроса на сервер и получение ответа — то она включает в себя сетевой ввод-вывод, поэтому наблюдается неявная передача управления, даже если отправляемый на сервер запрос не представляет собой запрос с неявной передачей управления. Таким образом, последовательность:

select
        select
        select

приводит к блокировке (в memtx’е), если находится внутри функции или Lua-программы, которая выполняется на экземпляре сервера. Однако она вызывает передачу управления (и в memtx, и в vinyl), если выполняется как серия передач от клиента, включая клиентов, работающих по telnet, по одному из коннекторов или модулей MySQL и PostgreSQL или в интерактивном режиме при использовании Tarantool’а как клиента.

После того, как файбер передал управление, а затем вернул его, он незамедлительно вызывает testcancel.