diff --git a/doc/book/box/atomic/atomic-cooperative_multitasking.rst b/doc/book/app_server/cooperative_multitasking.rst similarity index 74% rename from doc/book/box/atomic/atomic-cooperative_multitasking.rst rename to doc/book/app_server/cooperative_multitasking.rst index 722c17efb5..275275e9ef 100644 --- a/doc/book/box/atomic/atomic-cooperative_multitasking.rst +++ b/doc/book/app_server/cooperative_multitasking.rst @@ -1,28 +1,29 @@ -.. _atomic-cooperative_multitasking: - -Cooperative multitasking -======================== - -Cooperative multitasking means that unless a running fiber deliberately yields -control, it is not preempted by some other fiber. But a running fiber will -deliberately yield when it encounters a “yield point”: a transaction commit, -an operating system call, or an explicit :ref:`"yield" ` request. -Any system call which can block will be performed asynchronously, and any running -fiber which must wait for a system call will be preempted, so that another -ready-to-run fiber takes its place and becomes the new running fiber. - -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 simple: -Use no yields, explicit or implicit in critical sections, and no one can -interfere with code execution. - -When dealing with small requests, such as simple UPDATE or INSERT or DELETE or -SELECT, fiber scheduling is fair: it takes only a little time to process the -request, schedule a disk write, and yield to a fiber serving the next client. - -However, a function may perform complex computations or be written in -such a way that yields take a long time to occur. This can lead to -unfair scheduling when a single client throttles the rest of the system, or to -apparent stalls in request processing. Avoiding this situation is -the responsibility of the function’s author. +.. _app-cooperative_multitasking: + +Cooperative multitasking +======================== + +Cooperative multitasking means that unless a running fiber deliberately yields +control, it is not preempted by some other fiber. But a running fiber will +deliberately yield when it encounters a “yield point”: a transaction commit, +an operating system call, or an explicit :ref:`"yield" ` request. +Any system call which can block will be performed asynchronously, and any running +fiber which must wait for a system call will be preempted, so that another +ready-to-run fiber takes its place and becomes the new running fiber. + +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 simple: +Use no yields, explicit or implicit in critical sections, and no one can +interfere with code execution. + +For small requests, such as simple UPDATE or INSERT or DELETE or +SELECT, fiber scheduling is fair: it takes little time to process the +request, schedule a disk write, and yield to a fiber serving the next client. + +However, a function may perform complex calculations or be written in +such a way that yields take a long time to occur. This can lead to +unfair scheduling when a single client throttles the rest of the system, or to +apparent stalls in processing requests. It is the responsibility of the function +author to avoid this situation. + diff --git a/doc/book/app_server/index.rst b/doc/book/app_server/index.rst index b390f30e96..780447f5a8 100644 --- a/doc/book/app_server/index.rst +++ b/doc/book/app_server/index.rst @@ -21,6 +21,8 @@ This chapter contains the following sections: installing_module contributing_module reloading_module + yields + cooperative_multitasking luajit_memprof luajit_getmetrics using_ide diff --git a/doc/book/app_server/yields.rst b/doc/book/app_server/yields.rst new file mode 100644 index 0000000000..9e4135a41d --- /dev/null +++ b/doc/book/app_server/yields.rst @@ -0,0 +1,233 @@ +.. _app-yields: + +Yields +====== + +Any live fiber can be in one of three states: ``running``, ``suspended``, and +``ready``. After a fiber dies, the ``dead`` status returns. By observing +fibers from the outside, you can only see ``running`` (for the current fiber) +and ``suspended`` for any other fiber waiting for an event from eventloop (``ev``) +for execution. + + +.. image:: yields.svg + :align: center + + +Yield is an action that occurs in a :ref:`cooperative ` environment that +transfers control of the thread from the current fiber to another fiber that is ready to execute. + + +After yield has occurred, the next ``ready`` fiber is taken from the queue and executed. +When there are no more ``ready`` fibers, execution is transferred to the event loop. + +After a fiber has yielded and regained control, it immediately issues :ref:`testcancel `. + +There are :ref:`explicit ` and :ref:`implicit ` yields. + +.. _app-explicit-yields: + +Explicit yields +--------------- + +**Explicit yield** is clearly visible from the invoking code. There are only two +explicit yields: :ref:`fiber.yield() ` and :ref:`fiber.sleep(t) ` + +:ref:`fiber.yield() ` yields execution to another ``ready`` fiber while putting itself in the ``ready`` state, +meaning that it will be executed again as soon as possible while being polite to other fibers +waiting for execution. + +:ref:`fiber.sleep(n) ` yields execution to another ``ready`` fiber and puts itself in the ``suspended`` +state for time ``t`` until time pass and the event loop wakes up that fiber to the ``ready`` state. + +In general, it is good behavior for long-running cpu-intensive tasks to yield periodically to +be :ref:`cooperative ` to other waiting fibers. + +.. _app-implicit-yields: + +Implicit yields +--------------- + +On the other hand, there are many operations, such as operations with sockets, file system, +and disk I/O, which imply some waiting for the current fiber while others can be +executed. When such an operation occurs, a possible blocking operation would be passed into the +event loop and the fiber would be suspended until the resource is ready to +continue fiber execution. + +Here is the list of implicitly yielding operations: + +* Connection establishment (:ref:`socket `). + +* Socket read and write (:ref:`socket `). + +* Filesystem operations (from :ref:`fio `). + +* Channel data transfer (:ref:`fiber.channel `). + +* File input/output (from :ref:`fio `). + +* Console operations (since console is a socket). + +* HTTP requests (since HTTP is a socket operation). + +* Database modifications (if they imply a disk write). + +* Database reading for the :ref:`vinyl ` engine. + +* Invocation of another process (:ref:`popen `). + +.. note:: + + Please note that all operations of ``os`` mosule are non-cooperative and + exclusively block the whole tx thread. + +For :ref:`memtx `, since all data is in memory, there is no yielding for a read requests +(like ``:select``, ``:pairs``, ``:get``). + +For :ref:`vinyl `, since some data may not be in memory, there may be disk I/O for a +read (to fetch data from disk) or write (because a stall may occur while waiting for memory to be freed). + +For both :ref:`memtx ` and :ref:`vinyl `, since data change requests +must be recorded in the :ref:`WAL `, there is normally a :doc:`/reference/reference_lua/box_txn_management/commit`. + +With the default ``autocommit`` mode the following operations are yielding: + +* :ref:`space:alter `. + +* :ref:`space:drop `. + +* :ref:`space:create_index `. + +* :ref:`space:truncate `. + +* :ref:`space:insert `. + +* :ref:`space:replace `. + +* :ref:`space:update `. + +* :ref:`space:upserts `. + +* :ref:`space:delete `. + +* :ref:`index:update `. + +* :ref:`index:delete `. + +* :ref:`index:alter `. + +* :ref:`index:drop `. + +* :ref:`index:rename `. + +* :ref:`box.commit ` (*if there were some modifications within the transaction*). + +To provide atomicity for transactions in transaction mode, some changes are applied to the +modification operations for the :ref:`memtx ` engine. After executing +:ref:`box.begin ` or within a :ref:`box.atomic ` +call, any modification operation will not yield, and yield will occur only on :ref:`box.commit ` or upon return +from :ref:`box.atomic `. Meanwhile, :ref:`box.rollback ` does not yield. + +That is why executing separate commands like ``select()``, ``insert()``, ``update()`` in the console inside a +transaction without MVCC will cause it to an abort. This is due to implicit yield after each +chunk of code is executed in the console. + + +**Example #1** + +* Engine = memtx. + +.. code-block:: memtx + + space:get() + space:insert() + + +The sequence has one yield, at the end of the insert, caused by implicit commit; +``get()`` has nothing to write to the :ref:`WAL ` and so does not yield. + +* Engine = memtx. + +.. code-block:: memtx + + box.begin() + space1:get() + space1:insert() + space2:get() + space2:insert() + box.commit() + + +The sequence has one yield, at the end of the ``box.commit``, none of the inserts are yielding. + +* Engine = vinyl. + +.. code-block:: vinyl + + space:get() + space:insert() + + +The sequence has one to three yields, since ``get()`` may yield if the data is not in the cache, +``insert()`` may yield if it waits for available memory, and there is an implicit yield +at commit. + +* Engine = vinyl. + +.. code-block:: vinyl + + box.begin() + space1:get() + space1:insert() + space2:get() + space2:insert() + box.commit() + + +The sequence may yield from 1 to 5 times. + + +**Example #2** + +Assume that there are tuples in the :ref:`memtx ` space ``tester`` where 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. + +.. code-block:: tarantoolsession + + tarantool> function txn_example(from, to, amount_of_money) + > box.atomic(function() + > box.space.tester:update(from, {{'-', 3, amount_of_money}}) + > box.space.tester:update(to, {{'+', 3, amount_of_money}}) + > end) + > return "ok" + > end + + Result: + --- + ... + tarantool> txn_example({999}, {1000}, 1.00) + --- + - "ok" + ... + +If :ref:`wal_mode ` = ``none``, then +there is no implicit yielding at the commit time because there are +no writes to the :ref:`WAL `. + +If a request if performed via network connector such as :ref:`net.box ` and implies +sending requests to the server and receiving responses, then it involves network +I/O and thus implicit yielding. Even if the request that is sent to the server +has no implicit yield. Therefore, the following sequence causes yields +three times sequentially when sending requests to the network and awaiting the results. + + +.. cssclass:: highlight +.. parsed-literal:: + + conn.space.test:get{1} + conn.space.test:get{2} + conn.space.test:get{3} diff --git a/doc/book/app_server/yields.svg b/doc/book/app_server/yields.svg new file mode 100644 index 0000000000..542851b12f --- /dev/null +++ b/doc/book/app_server/yields.svg @@ -0,0 +1,4 @@ + + + +
suspended
suspended
ready
ready
running
running
wake up from evdelayed actionscheduleyield
Text is not SVG - cannot display
\ No newline at end of file diff --git a/doc/book/box/atomic.rst b/doc/book/box/atomic.rst index 318c116ba0..e3ee0e9bf3 100644 --- a/doc/book/box/atomic.rst +++ b/doc/book/box/atomic.rst @@ -13,8 +13,8 @@ For more information on how transactions work in Tarantool, see the following se .. toctree:: :maxdepth: 2 - atomic/atomic-threads_fibers_yields - atomic/atomic-cooperative_multitasking - atomic/atomic-transactions - atomic/atomic-implicit-yields - atomic/atomic-transactional-manager + atomic/transaction_model + atomic/thread_model + atomic/txn_mode_default + atomic/txn_mode_mvcc + diff --git a/doc/book/box/atomic/atomic-implicit-yields.rst b/doc/book/box/atomic/atomic-implicit-yields.rst deleted file mode 100644 index 27251e1524..0000000000 --- a/doc/book/box/atomic/atomic-implicit-yields.rst +++ /dev/null @@ -1,94 +0,0 @@ -.. _atomic-implicit-yields: - -Implicit yields -=============== - -The only explicit yield requests in Tarantool are :ref:`fiber.sleep() ` -and :ref:`fiber.yield() `, but many other requests "imply" yields -because Tarantool is designed to avoid blocking. - -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 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 each request in the default "autocommit" mode, -or a commit happens at the end of a transaction in "transaction" mode, -when a user intentionally commits by calling :doc:`/reference/reference_lua/box_txn_management/commit`. -Therefore for both memtx and vinyl, because there can be disk I/O, -some database operations may imply yields. - -Many functions in the modules :ref:`fio `, :ref:`net_box `, -:ref:`console ` and :ref:`socket ` -(the "os" and "network" requests) yield. - -That is why executing separate commands like ``select()``, ``insert()``, -``update()`` in the console inside a transaction will cause an abort. This is -due to implicit yield happening after each chunk of code is executed in the console. - -**Example #1** - -* *Engine = memtx* |br| - The sequence ``select() insert()`` has one yield, at the end of the insert, caused - by implicit commit; ``select()`` has nothing to write to the WAL and so does not - yield. - -* *Engine = vinyl* |br| - The sequence ``select() insert()`` has one to three yields, since ``select()`` - may yield if the data is not in the cache, ``insert()`` may yield if it is waiting - for available memory, and there is an implicit yield at commit. - -* The sequence ``begin() insert() insert() commit()`` only yields on commit - if the engine is memtx, and can yield up to 3 times if the engine is vinyl. - -**Example #2** - -Assume that there are tuples in the memtx space ‘tester’ where 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. - -.. code-block:: tarantoolsession - - 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" - ... - -If :ref:`wal_mode ` = ‘none’, then -implicit yielding at the commit time does not take place, because there are -no writes to the 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 - -.. cssclass:: highlight -.. parsed-literal:: - - 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 a 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 -:ref:`MySQL and PostgreSQL rocks `, or via the interactive mode when -:ref:`using Tarantool as a client `. - -After a fiber has yielded and then regained control, it immediately issues -:ref:`testcancel `. diff --git a/doc/book/box/atomic/atomic-threads_fibers_yields.rst b/doc/book/box/atomic/atomic-threads_fibers_yields.rst deleted file mode 100644 index 8fd5fb0510..0000000000 --- a/doc/book/box/atomic/atomic-threads_fibers_yields.rst +++ /dev/null @@ -1,67 +0,0 @@ -.. _atomic-threads_fibers_yields: - -Threads, fibers and yields -========================== - -How does Tarantool process a basic operation? As an example, let's take this -query: - -.. code-block:: tarantoolsession - - 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]``: - -.. code-block:: SQL - - 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. The network thread ships this message to the instance's - **transaction processor thread** using a lock-free message bus. - Lua programs are executed directly in the transaction processor thread, - and do not need to be parsed and prepared. - - 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 - :ref:`write-ahead logging (WAL) thread ` to commit the - transaction. When this is done, the WAL thread replies with a COMMIT or ROLLBACK - result to the transaction processor, which returns it to the network thread, - and the network thread returns the result to the client. - -Notice that there is only one transaction processor thread in Tarantool. -Some people are used to the idea that there can be multiple threads operating -on the database, with (say) thread #1 reading row #x, while thread #2 writes -row #y. With Tarantool, no such thing ever happens. -Only the transaction processor thread can access the database, and there is -only one transaction processor thread for each Tarantool instance. - -Like any other Tarantool thread, the transaction processor thread can handle -many :ref:`fibers `. A fiber is a set of computer instructions -that may contain "**yield**" signals. The transaction processor thread will -execute all computer instructions until a yield, and then switches to execute the -instructions of a different fiber. For example, the thread reads row #x for the -sake of fiber #1, and then writes row #y for the sake of fiber #2. - -Yields must happen, otherwise the transaction processor thread would stick -permanently on the same fiber. There are two types of yields: - -* :ref:`implicit yields `: every data-change operation - or network-access causes an implicit yield, and every statement that goes - through the Tarantool client causes an implicit yield. - -* explicit yields: in a Lua function, you can (and should) add - :ref:`"yield" ` statements to prevent hogging. This is called - **cooperative multitasking**. - \ No newline at end of file diff --git a/doc/book/box/atomic/atomic-transactional-manager.rst b/doc/book/box/atomic/atomic-transactional-manager.rst deleted file mode 100644 index c16549d569..0000000000 --- a/doc/book/box/atomic/atomic-transactional-manager.rst +++ /dev/null @@ -1,33 +0,0 @@ -.. _atomic-transactional-manager: - -Transactional manager -===================== - -Since version :doc:`2.6.1 `, -Tarantool has another option for transaction behavior that -allows yielding inside a memtx transaction. This is controlled by -the *transactional manager*. - -The transactional manager is designed to isolate concurrent transactions -and provides a *serializable* `transaction isolation level `_. -It consists of two parts: - -* *MVCC engine* -* *conflict manager* - -The MVCC engine provides personal read views for transactions if necessary. -The conflict manager tracks chaanges to transactions and determines their correctness -in serialization order. Of course, once yielded, a transaction could interfere -with other transactions and be aborted due to a conflict. - -Another important point is that the transaction manager -provides a non-classical snapshot isolation level. This means that a transaction -can get a consistent snapshot of the database (that is common), but this snapshot -is not necessarily bound to the moment of the transaction started -(that is not common). -The conflict manager decides if and when each transaction gets -which snapshot. That allows to avoid some conflicts compared to the classic -snapshot isolation approach. - -The transactional manager can be switched on and off by the ``box.cfg`` option -:ref:`memtx_use_mvcc_engine `. diff --git a/doc/book/box/atomic/atomic-transactions.rst b/doc/book/box/atomic/atomic-transactions.rst deleted file mode 100644 index 1c8b64642f..0000000000 --- a/doc/book/box/atomic/atomic-transactions.rst +++ /dev/null @@ -1,50 +0,0 @@ -.. _atomic-transactions: - -Transactions -============ - -Transactions in Tarantool occur in **fibers** on a single **thread**. -That is why Tarantool has a guarantee of atomicity of execution. -That requires emphasis. - -Since :tarantool-release:`2.10.0`, Tarantool supports streams and interactive transactions over them. -See :ref:`Streams `. - -In the absence of transactions, any function containing yield points can see -changes in database state caused by fibers that preempt. -Multi-statement transactions exist to provide **isolation**: each transaction -sees consistent database state and commits all changes atomically. -At the :doc:`commit ` time, -a yield happens and all transaction changes -are written to the :ref:`write ahead log ` in a single batch. -Or, if needed, transaction changes can be rolled back -- -:doc:`completely ` or to -a specific -:doc:`savepoint `. - -In Tarantool, the `transaction isolation level `_ -is *serializable* with the clause "if no failure during writing to the 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 :ref:`vinyl `, Tarantool uses a simple optimistic scheduler to implement the isolation: -the first transaction to commit wins. If a concurrently active transaction -has read a value that has been modified by a committed transaction, it is aborted. - -The cooperative scheduler ensures that, in absence of yields, -a multi-statement transaction is not preempted and hence is never aborted. -Therefore, understanding yields is essential for writing abort-free code. - -Sometimes while testing the transaction mechanism in Tarantool, you can notice -that yielding after ``box.begin()`` but before any read/write operation does not -cause an abort as it should according to the description. This is because -``box.begin()`` does not actually start a transaction. It is a mark telling -Tarantool to start a transaction after some database request that follows. - -In memtx, if an instruction that implies yields, explicit or implicit, is -executed during a transaction, the transaction is fully rolled back. In vinyl, -we use more complex transactional manager that allows yields. - -.. note:: - - You can’t mix storage engines in a transaction today. diff --git a/doc/book/box/atomic/thread_model.rst b/doc/book/box/atomic/thread_model.rst new file mode 100644 index 0000000000..762ab84f8d --- /dev/null +++ b/doc/book/box/atomic/thread_model.rst @@ -0,0 +1,98 @@ +.. _thread_model: + +Thread model +============ + +The thread model assumes that a query received by Tarantool via network +is processed with three operating system **threads**: + +1. The **network thread** on the server side receives the query, parses + the statement, checks if it is correct, and then transforms it into a special + structure -- a message containing an executable statement and its options. + +2. The network thread ships this message to the instance's + **transaction processor thread** (*tx thread*) using a lock-free message bus. + Lua programs are executed directly in the transaction processor thread, + and do not need to be parsed and prepared. + + The tx thread either use space index to find and update the tuple, + or executes stored function, that will perform some data operations. + +3. The execution of operation will result in a message to the + :ref:`write-ahead logging (WAL) ` thread to commit + the transaction and the fiber executing the transaction will be suspended. + When the transaction will result in a COMMIT or ROLLBACK, :ref:`WAL ` thread will + reply with a message to the TX, fiber will be resumed to have an ability + to process the result of transaction and the result of fiber execution + will be passed to the network thread, and the network thread returns + the result to the client. + + +.. note:: + + There is only one tx thread in Tarantool. + Some users are used to the idea that there can be multiple threads + working on the database. For example, thread #1 reads row #x while + thread #2 writes row #y. With Tarantool, this never happens. + Only the tx thread can access the database, + and there is only one tx thread for each Tarantool instance. + + +The tx thread can handle many :ref:`fibers ` -- +a set of computer instructions that may contain "**yield**" signals. +The tx thread executes all computer instructions up to a +yield signal, and then switches to execute the instructions of another fiber. + + +:ref:`Yields ` must happen, otherwise the tx thread would +be permanently stuck on the same fiber. + +.. _thread_model-example: + +Example +------- + +Create space ``tester``: + +.. code-block:: tarantoolsession + + box.schema.create_space('tester',{ if_not_exists = true; }) + + box.space.tester:format( { + { name = 'id'; type = 'number' }, + { name = 'name'; type = 'string' }, + { name = 'data'; type = '*' }, + } ); + + box.space.tester:create_index('primary', { + parts = {{ 'id','number' }}; + if_not_exists = true; + }) + + box.space.tester:update({3}, {{'=', 'name, 'size'}, {'=', 'data', 0}}) + + +Perform a basic operation with this query: + +.. code-block:: tarantoolsession + + box.space.tester:update({3}, {{'=', 'name, 'size'}, {'=', 'data', 0}}) + + +This is equivalent to the following SQL statement: + +.. code-block:: SQL + + UPDATE tester SET "name" = 'size', "data" = 0 WHERE "id" = 3 + +.. note:: + + It is better to follow best practice and use SQL with placeholders: + + ``box.execute([[ UPDATE tester SET "name" = ?, "data" = ? WHERE "id" = ? ]], 'size', 0, 123)`` + + + + + + diff --git a/doc/book/box/atomic/transaction_model.rst b/doc/book/box/atomic/transaction_model.rst new file mode 100644 index 0000000000..f667bab0d0 --- /dev/null +++ b/doc/book/box/atomic/transaction_model.rst @@ -0,0 +1,94 @@ +.. _transaction_model: + +Transaction model +================= + +The transaction model of Tarantool corresponds to the properties ACID +(atomicity, consistency, isolation, durability). + + +Tarantool has two modes of transaction behavior that allow users to choose between +fast monopolistic atomic transactions and long-running concurrent transactions with +:ref:`MVCC `: + +* :ref:`Default `. + +* :ref:`MVCC `. + + +Each transaction in Tarantool is executed in a single fiber on a single thread, sees a consistent database state +and commits all changes atomically. + +All transaction changes are written to the WAL (:ref:`Write Ahead Log `) +in a single batch in a specific order at the time of the +:doc:`commit `. +If needed, transaction changes can also be rolled back -- +:doc:`completely ` or to +a specified :doc:`savepoint `. + +Therefore, every transaction in Tarantool has the highest +`transaction isolation level `_ -- *serializable*. + +.. _transaction_model_levels: + +Isolation level +--------------- + +By :ref:`default `, the isolation level of Tarantool is *serializable*, +except in the case of a failure during writing to the WAL, which can occur, for example, +when the disk space is over. In this case, the isolation level of the concurrent read transaction +would be *read committed*. + + +The :ref:`MVСС mode ` provides several options that allows you to tune +the visibility behavior during transaction execution. To achieve *serializable*, any write transaction +should read all data that has already been committed (otherwise it may conflict +when it reaches its commit). For read transactions, however, it is sufficient +and safe to *read confirmed* data that is on disk (for asynchronous replication) or even in other replicas +(for synchronous replication). + + +So, during transaction execution, the MVCC must choose between *read-commited* or *read-confirmed* visibility, +which inevitably leads to the *serializable* isolation level. + + +The *read committed* isolation level makes visible all transactions that started +commit (``box.commit()`` was called). By using this transaction level only for +read-write transactions, you can minimize the conflicts that may occur. + + +The *read confirmed* isolation level makes visible all transactions that finished +the commit (``box.commit()`` was returned), meaning it is already on disk or even on other replicas. + + +If the *serializable* isolation level becomes unreachable, the transaction is marked as "conflicted" +and can no longer be committed. + + +To minimize the possibility of conflicts, MVCC uses what is called *best-effort* visibility: +for write transactions it chooses *read-commited*, for read transactions it chooses *read-confirmed*. +Since there is no option for MVCC to analyze the whole transaction to make a decision, it makes the choice on +the first operation. The author of the transaction has more knowledge about the whole transaction and could give +a hint to minimize conflicts. + +Manual usage of *read-commited* for write transactions with reads is completely safe, as this +transaction will eventually result in a commit. And if some previous transactions fail, this +transaction will inevitably fail as well due to the *serializable* isolation level. + +Manual usage of *read-commited* for pure read transactions may be unsafe, as it may lead to phantom reads. + + + + + + + + + + + + + + + + diff --git a/doc/book/box/atomic/txn_mode_default.rst b/doc/book/box/atomic/txn_mode_default.rst new file mode 100644 index 0000000000..a26dc70ccc --- /dev/null +++ b/doc/book/box/atomic/txn_mode_default.rst @@ -0,0 +1,27 @@ +.. _txn_mode-default: + +Transaction mode: default +=========================== + +By default, Tarantool does not allow :ref:`"yielding" ` inside a :ref:`memtx ` +transaction and the :ref:`transaction manager ` is disabled. This allows fast +atomic transactions without conflicts, but brings some limitations: + +* You cannot use interactive transactions. + +* Any fiber yield leads to the abort of a transaction. + +* All changes are made immediately, but in the event of a yield or error, + the transaction is rolled back, including the return of the previous data. + + +To allow yielding inside a :ref:`memtx ` transaction, see :ref:`Transaction mode: MVCC `. + +To switch back to the default mode, disable the transaction manager: + +.. code-block:: enabling + + box.cfg{memtx_use_mvcc_engine = false} + + + diff --git a/doc/book/box/atomic/txn_mode_mvcc.rst b/doc/book/box/atomic/txn_mode_mvcc.rst new file mode 100644 index 0000000000..d17b3d8e7e --- /dev/null +++ b/doc/book/box/atomic/txn_mode_mvcc.rst @@ -0,0 +1,284 @@ +.. _txn_mode_transaction-manager: + +Transaction mode: MVCC +====================== + +Since version :doc:`2.6.1 `, +Tarantool has another transaction behavior mode that +allows :ref:`"yielding" ` inside a :ref:`memtx ` transaction. +This is controlled by the *transaction manager*. + + +This mode allows concurrent transactions but may cause conflicts. +You can use this mode on the :ref:`memtx ` storage engine. +The :ref:`vinyl ` storage engine also supports MVCC mode, +but has a different implementation. + +.. note:: + + Currently, you cannot use several different storage engines within one transaction. + +.. _txn_mode_mvcc-tnx-manager: + +Transaction manager +------------------- + +The transaction manager is designed to isolate concurrent transactions +and provides a *serializable* +`transaction isolation level `_. +It consists of two parts: + +* *MVCC* -- multi version concurrency control engine, which stores all change actions of all + transactions. It also creates the transaction view of the database state and a read view + (a fixed state of the database that is never changed by other transactions) when necessary. + + +* *Conflict manager* -- a manager that tracks changes to transactions and determines their correctness + in the serialization order. The conflict manager declares transactions to be in conflict + or sends transactions to read views when necessary. + +The transaction manager also provides a non-classical snapshot isolation level -- this snapshot is not +necessarily tied to the start time of the transaction, like the classical snapshot where a transaction +can get a consistent snapshot of the database. The conflict manager decides if and when each transaction +gets which snapshot. This avoids some conflicts compared to the classic snapshot isolation approach. + +.. _txn_mode_mvcc-enabling: + +Enabling the transaction manager +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +By default, the transaction manager is disabled. Use the :ref:`memtx_use_mvcc_engine ` +option to enable it via ``box.cfg``. + +.. code-block:: + + box.cfg{memtx_use_mvcc_engine = true} + + +.. _txn_mode_mvcc-options: + +Transaction manager options +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. note:: + + For autocommit transactions (actions with a statement without explicit ``box.begin/commit`` calls) + there is an obvious rule: read-only transactions (for example, ``select``) are performed with ``read-confirmed``; + all others (for example, ``replace``) with ``read-committed``. + + +The transaction manager has four options for the transaction isolation level that you can set in ``box-cfg``: + +* ``default``. + +* ``best-effort``. + +* ``read-committed``. + +* ``read-confirmed``. + +The ``best-effort`` option is set by default (or if the level is not specified). +This allows MVCC to consider the actions of transactions independently and determine the +best :ref:`isolation level ` for them. It increases the probability +of successful completion of the transaction and helps to avoid possible conflicts. + +To set the default isolation level with the other option, for example, +to ``read-committed``, use the following command: + +.. code-block:: + + box.cfg{default_txn_isolation = 'read-committed'} + + +If a transaction has an explicit ``box.begin()`` call, the level can be +specified as follows: + +.. code-block:: + + box.begin({tnx_isolation = 'best-effort'}) + +.. note:: + + You can also do this in the net.box :ref:`stream:begin() ` method. + + +Choosing the better option depends on whether you have conflicts or not. +If you have many conflicts, you should set the different options or use +the :ref:`default mode `. + + +.. _txn_mode_mvcc-examples: + +Examples with MVCC enabled and disabled +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Create a file ``init.lua``, containing the following: + +.. code-block:: + + fiber = require 'fiber' + + box.cfg{ listen = '127.0.0.1:3301', memtx_use_mvcc_engine = false } + box.schema.user.grant('guest', 'super', nil, nil, {if_not_exists = true}) + + tickets = box.schema.create_space('tickets', { if_not_exists = true }) + tickets:format({ + { name = "id", type = "number" }, + { name = "place", type = "number" }, + }) + tickets:create_index('primary', { + parts = { 'id' }, + if_not_exists = true + }) + +Connect to the instance: + +.. code-block:: + + tarantooctl connect 127.0.0.1:3301 + +Then try to execute the transaction with yield inside: + +.. code-block:: + + box.atomic(function() tickets:replace{1, 429} fiber.yield() tickets:replace{2, 429} end) + + +You will receive an error message: + +.. code-block:: + + --- + - error: Transaction has been aborted by a fiber yield + ... + +Also, if you leave a transaction open while returning from a request, you will get an error message: + +.. code-block:: + + 127.0.0.1:3301> box.begin() + --- + - error: Transaction is active at return from function + ... + +Change ``memtx_use_mvcc_engine`` to ``true``, restart tarantool and try again: + +.. code-block:: + + 127.0.0.1:3301> box.atomic(function() tickets:replace{1, 429} fiber.yield() tickets:replace{2, 429} end) + --- + ... + +Now check if this transaction was successful: + +.. code-block:: + + 127.0.0.1:3301> box.space.tickets:select({}, {limit = 10}) + --- + - - [1, 429] + - [2, 429] + ... + + +.. _txn_mode_stream-interactive-transactions: + +Streams and interactive transactions +------------------------------------ + +Since :tarantool-release:`2.10.0`, IPROTO implements streams and interactive +transactions that can be used when :ref:`memtx_use_mvcc_engine ` +is enabled on the server. + +.. glossary:: + + Stream + A stream supports multiplexing several transactions over one connection. + Each stream has its own identifier, which is unique within the connection. + All requests with the same non-zero stream ID belong to the same stream. + All requests in a stream are executed strictly sequentially. + This allows the implementation of + :term:`interactive transactions `. + If the stream ID of a request is ``0``, it does not belong to any stream and is + processed in the old way. + + +In :doc:`net.box `, a stream is an object above +the connection that has the same methods but allows sequential execution of requests. +The ID is automatically generated on the client side. +If a user writes their own connector and wants to use streams, +they must transmit the ``stream_id`` over the :ref:`IPROTO protocol `. + +Unlike a thread, which involves multitasking and execution within a program, +a stream transfers data via the protocol between a client and a server. + +.. glossary:: + + Interactive transaction + An interactive transaction is one that does not need to be sent in a single request. + There are multiple ways to begin, commit, and roll back a transaction, and they can be mixed. + You can use :ref:`stream:begin() `, :ref:`stream:commit() `, + :ref:`stream:rollback() ` or the appropriate stream methods + -- ``call``, ``eval``, or ``execute`` -- using the SQL transaction syntax. + + +Let’s create a Lua client (``client.lua``) and run it with tarantool: + +.. code-block:: lua + + local net_box = require 'net.box' + local conn = net_box.connect('127.0.0.1:3301') + local conn_tickets = conn.space.tickets + local yaml = require 'yaml' + + local stream = conn:new_stream() + local stream_tickets = stream.space.tickets + + -- Begin transaction over an iproto stream: + stream:begin() + print("Replaced in a stream\n".. yaml.encode( stream_tickets:replace({1, 768}) )) + + -- Empty select, the transaction was not committed. + -- You can't see it from the requests that do not belong to the + -- transaction. + print("Selected from outside of transaction\n".. yaml.encode(conn_tickets:select({}, {limit = 10}) )) + + -- Select returns the previously inserted tuple + -- because this select belongs to the transaction: + print("Selected from within transaction\n".. yaml.encode(stream_tickets:select({}, {limit = 10}) )) + + -- Commit transaction: + stream:commit() + + -- Now this select also returns the tuple because the transaction has been committed: + print("Selected again from outside of transaction\n".. yaml.encode(conn_tickets:select({}, {limit = 10}) )) + + os.exit() + +Then call it and see the following output: + +.. code-block:: + + Replaced in a stream + --- [1, 768] + ... + + Selected from outside of transaction + --- + - [1, 429] + - [2, 429] + ... + + Selected from within transaction + --- + - [1, 768] + - [2, 429] + ... + + Selected again from outside of transaction + --- + - [1, 768] + - [2, 429] + ...``` + + + diff --git a/doc/book/box/index.rst b/doc/book/box/index.rst index 5f9d1253af..063ff1a785 100644 --- a/doc/book/box/index.rst +++ b/doc/book/box/index.rst @@ -15,7 +15,6 @@ This chapter contains the following sections: data_model atomic - stream authentication triggers limitations diff --git a/doc/book/box/stream.rst b/doc/book/box/stream.rst deleted file mode 100644 index 52659ec472..0000000000 --- a/doc/book/box/stream.rst +++ /dev/null @@ -1,94 +0,0 @@ -.. _box_stream: - -Streams and interactive transactions -==================================== - -.. _box_stream-overview: - -Overview --------- - -Since :tarantool-release:`2.10.0`, iproto implements streams and interactive transactions. - -.. glossary:: - - Stream - A stream supports multiplexing several transactions over one connection. - All requests in the stream are executed strictly sequentially, - which allows the implementation of - :term:`interactive transactions `. - -Unlike a thread associated with multitasking and execution within a program, -a stream transfers data via the protocol between a client and a server. - -.. glossary:: - - Interactive transaction - An interactive transaction is a transaction that does not need to be sent in a single request. - The ``begin``, ``commit``, and other TX statements can be sent and executed in different requests. - -.. _box_stream-features: - -Features --------- - -The primary purpose of :term:`streams ` is to execute transactions via iproto. -Every stream has its own identifier, which is unique within the connection. -All requests with the same non-zero stream ID belong to the same stream. -All requests in the stream are processed synchronously. -The next request will not start executing until the previous one is completed. -If a request's stream ID is ``0``, it does not belong to any stream and is processed in the old way. - -In :doc:`net.box `, a stream is an object above the connection that has the same methods -but allows executing requests sequentially. -The ID is generated on the client side automatically. -If a user writes their own connector and wants to use streams, -they must transmit the ``stream_id`` over the iproto protocol. - -Interactive transactions over streams only work if -the ``box.cfg{}`` option :ref:`memtx_use_mvcc_engine ` -is enabled on the server: ``memtx_use_mvcc_engine = true``. - -.. _box_stream-interaction: - -Interaction between streams and transactions --------------------------------------------- - -As each stream can start a transaction, several transactions can be multiplexed over one connection. -There are multiple ways to begin, commit, and roll back a transaction. -One can do that using the appropriate stream methods -- ``call``, ``eval``, -or ``execute`` -- with the SQL transaction syntax. Users can mix these methods. -For example, one might start a transaction using ``stream:begin()`` -and commit it with ``stream:call('box.commit')`` or ``stream:execute('COMMIT')``. -All the requests between ``stream:begin()`` and ``stream:commit()`` are executed within the same transaction. -If any request fails during the transaction, it will not affect the other requests in the transaction. -If a disconnect occurs while there is an active transaction in the stream, -that transaction will be rolled back if it hasn't been committed before the connection failure. - -Example: - -.. code-block:: lua - - local conn = net_box.connect(remote_server_addr) - local conn_space = conn.space.test - local stream = conn:new_stream() - local stream_space = stream.space.test - - -- Begin transaction over an iproto stream: - stream:begin() - stream_space:replace({1}) - - -- Empty select, the transaction was not committed. - -- You can't see it from the requests that do not belong to the - -- transaction. - conn_space:select{} - - -- Select returns the previously inserted tuple, - -- because this select belongs to the transaction: - stream_space:select({}) - - -- Commit transaction: - stream:commit() - - -- Now this select also returns the tuple, because the transaction has been committed: - conn_space:select{} \ No newline at end of file diff --git a/doc/index.rst b/doc/index.rst index d375fa1b3b..18a3934b83 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -72,7 +72,6 @@ CRUD operations book/box/indexes book/box/atomic - Streams book/box/authentication book/box/triggers reference/reference_rock/vshard/vshard_index diff --git a/doc/reference/reference_lua/box_txn_management.rst b/doc/reference/reference_lua/box_txn_management.rst index 9d84b37797..4594b3260d 100644 --- a/doc/reference/reference_lua/box_txn_management.rst +++ b/doc/reference/reference_lua/box_txn_management.rst @@ -5,7 +5,7 @@ Functions for transaction management -------------------------------------------------------------------------------- For general information and examples, see section -:ref:`Transaction control `. +:ref:`Transactions `. Observe the following rules when working with transactions: diff --git a/doc/toctree.rst b/doc/toctree.rst index c01e6705e8..f6273d1944 100644 --- a/doc/toctree.rst +++ b/doc/toctree.rst @@ -9,7 +9,6 @@ CRUD operations book/box/indexes book/box/atomic - Streams book/box/authentication book/box/triggers reference/reference_rock/vshard/vshard_index