Top.Mail.Ru
Транзакции | Tarantool
 
Транзакции
Транзакции

Транзакции

Транзакции

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

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

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

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

Это эквивалентно следующему SQL-выражению (оно работает с таблицей, где первичные ключи в field[1]):

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

Assuming this query is received by Tarantool via network, it will be processed with three operating system threads:

  1. The network thread on the server side receives the query, parses the statement, checks if it’s correct, and then transforms it into a special structure–a message containing an executable statement and its options.

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

    The instance’s transaction processor thread uses the primary-key index on field[1] to find the location of the tuple. It determines that the tuple can be updated (not much can go wrong when you’re merely changing an unindexed field value).

  3. The transaction processor thread sends a message to the write-ahead logging (WAL) thread to commit the transaction. When done, the WAL thread replies with a COMMIT or ROLLBACK result to the transaction processor which gives it back to the network thread, and the network thread returns the result to the client.

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

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

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

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

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

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

This model makes all programmatic locks unnecessary: cooperative multitasking ensures that there will be no concurrency around a resource, no race conditions, and no memory consistency issues. The way to achieve this is quite simple: in critical sections, don’t use yields, explicit or implicit, and no one can interfere into the code execution.

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

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

Транзакции

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

In Tarantool, transaction isolation level is serializable with the clause «if no failure during writing to WAL». In case of such a failure that can happen, for example, if the disk space is over, the transaction isolation level becomes read uncommitted.

In vynil, to implement isolation Tarantool uses a simple optimistic scheduler: the first transaction to commit wins. If a concurrent active transaction has read a value modified by a committed transaction, it is aborted.

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

Иногда при тестировании механизма транзакций в Tarantool можно заметить, что выдача после box.begin(), но перед любой операцией чтения/записи не приводит к прерыванию, как это должно происходить согласно описанию. Причина в том, что на самом деле box.begin() не запускает транзакцию: это просто метка, указывающая Tarantool запустить транзакцию после некоторого запроса к базе данных, который следует за этим.

In memtx, if an instruction that implies yields, explicit or implicit, is executed during a transaction, the transaction is fully rolled back. In vynil, we use more complex transactional manager that allows yields.

Примечание

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

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

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

Database requests imply yields if and only if there is disk I/O. For memtx, since all data is in memory, there is no disk I/O during a read request. For vinyl, since some data may not be in memory, there may be disk I/O for a read (to fetch data from disk) or for a write (because a stall may occur while waiting for memory to be free). For both memtx and vinyl, since data-change requests must be recorded in the WAL, there is normally a commit. A commit happens automatically after every request in default «autocommit» mode, or a commit happens at the end of a transaction in «transaction» mode, when a user deliberately commits by calling box.commit(). Therefore for both memtx and vinyl, because there can be disk I/O, some database operations may imply yields.

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

Поэтому выполнение отдельных команд, таких как select(), insert(), update() в консоли внутри транзакции, приведет к прерыванию транзакции. Это связано с тем, что после выполнения каждого фрагмента кода в консоли происходит неявная передача управления (yield).

Пример №1

  • Engine = memtx
    The sequence select() insert() has one yield, at the end of insertion, caused by implicit commit; select() has nothing to write to the WAL and so does not yield.
  • Engine = vinyl
    The sequence select() insert() has one to three yields, since select() may yield if the data is not in cache, insert() may yield waiting for available memory, and there is an implicit yield at commit.
  • Последовательность begin() insert() insert() commit() передает управление только при коммите, если движок – memtx, и может передавать управление до 3 раз, если движок – vinyl.

Пример №2

Assume that in the memtx space ‘tester’ there are tuples in which the third field represents a positive dollar amount. Let’s start a transaction, withdraw from tuple#1, deposit in tuple#2, and end the transaction, making its effects permanent.

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-файл.

If a task is interactive – sending requests to the server and receiving responses – then it involves network I/O, and therefore there is an implicit yield, even if the request that is sent to the server is not itself an implicit yield request. Therefore, the following sequence

conn.space.test:select{1}
conn.space.test:select{2}
conn.space.test:select{3}

causes yields three times sequentially when sending requests to the network and awaiting the results. On the server side, the same requests are executed in common order possibly mixing with other requests from the network and local fibers. Something similar happens when using clients that operate via telnet, via one of the connectors, or via the MySQL and PostgreSQL rocks, or via the interactive mode when using Tarantool as a client.

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