diff --git a/doc/sphinx/book/box/atomic.rst b/doc/sphinx/book/box/atomic.rst index cadea351d5d3c7151a4d92bc08b535eeb50f6f48..bfa4bdcc235c4d9b741b4e9acae3dd7dbd47356f 100644 --- a/doc/sphinx/book/box/atomic.rst +++ b/doc/sphinx/book/box/atomic.rst @@ -1,3 +1,5 @@ +.. _atomic_execution: + ------------------------------------------------------------------------------- Atomic execution ------------------------------------------------------------------------------- @@ -6,38 +8,51 @@ In several places in this manual it's been noted that Lua processes occur in fib single thread. That is why there can be a guarantee of execution atomicity. That requires emphasis. + +.. _cooperative_multitasking: + =========================================================== Cooperative multitasking environment =========================================================== -Tarantool uses cooperative multi-tasking: unless a -running fiber deliberately yields control to some other fiber, it is not -preempted. “Yield points†are built into all calls from Tarantool core to the -operating system. Any system call which can block is performed in an -asynchronous manner and the fiber waiting on the system call is preempted with -a fiber ready to run. This model makes all programmatic locks unnecessary: +Tarantool uses cooperative multitasking: 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": an explicit yield() request, or an implicit +yield due to an operating-system call. +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. +no race conditions, and no memory consistency issues. -When requests are small, e.g. simple UPDATE, INSERT, DELETE, SELECT, fiber +When requests are small, for example 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. -A function, however, can perform complex computations, or be written in such a -way that control is not given away for a long time. This can lead to unfair +However, a function might perform complex computations or might be written in such a +way that yields do not occur for a long time. 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. Most of the box calls, such as -:func:`box.space...insert <space_object.insert>`, -:func:`box.space...update <space_object.update>`, -:func:`box.space...delete <space_object.delete>` are yield points; +responsibility of the function's author. For the default memtx storage engine +most of the box calls, including the data-change requests +:func:`box.space...insert <space_object.insert>` or +:func:`box.space...update <space_object.update>` or +:func:`box.space...delete <space_object.delete>`, are yield points; however, :func:`box.space...select <space_object.select>` is not. -It should also be noted that, in the absence of transactions, any yield in a -function is a potential change in the database state. Effectively, it's only -possible to have CAS (compare-and-swap) -like atomic stored procedures: i.e. -functions which select and then modify a record. Multiple data change requests -always run through a built-in yield point. +Note re storage engine: sophia has different rules: +insert or update or delete will very rarely cause a yield, +but select can cause a yield. + +In the absence of transactions, any function that contains yield points +may see changes in the database state caused by fibers that preempt. +Then the only safe atomic functions for memtx databases would be +functions which contain only one database request, or functions which +contain a select request followed by a data-change request. At this point an objection could arise: "It's good that a single data-change request will commit and yield, but surely there are times when multiple @@ -49,19 +64,19 @@ block was designed. .. function:: box.begin() - From this point, implicit yields are suspended. In effect the fiber which - executes ``box.begin()`` is starting an "active multi-request transaction", - blocking all other fibers until the transaction ends. All operations within - this transaction should use the same storage engine. + Begin the transaction. Disable implicit yields until the transaction ends. + Signal that writes to the write-ahead log will be deferred until the transaction ends. + In effect the fiber which executes ``box.begin()`` is starting an "active + multi-request transaction", blocking all other fibers. .. function:: box.commit() - End the currently active transaction, and make all its data-change + End the transaction, and make all its data-change operations permanent. .. function:: box.rollback() - End the currently active transaction, but cancel all its data-change + End the transaction, but cancel all its data-change operations. An explicit call to functions outside ``box.space`` that always yield, such as ``fiber.yield`` or ``fiber.sleep``, will have the same effect. @@ -70,6 +85,11 @@ It is not enough to enclose them between ``begin`` and ``commit`` or ``rollback` To ensure they are sent as a single block: put them in a function, or put them all on one line, or use a delimiter so that multi-line requests are handled together. +**All database operations in a transaction should use the same storage engine**. +It is not safe to access tuple sets that are defined with {engine='sophia'} +and also access tuple sets that are defined with {engine='memtx'}, +in the same transaction. + =========================================================== Example =========================================================== diff --git a/doc/sphinx/book/box/index.rst b/doc/sphinx/book/box/index.rst index f8f9bc05d4817b1445439e89227c7f4471262799..020c3a52f845b512ace7bb07c2c83f1e10dd21a6 100644 --- a/doc/sphinx/book/box/index.rst +++ b/doc/sphinx/book/box/index.rst @@ -325,6 +325,8 @@ because after the UPDATE the transaction processor thread can switch to another fiber, and delete the tuple that was just updated. Note re storage engine: sophia handles yields differently, see :ref:`differences between memtx and sophia <sophia_diff>`. +Note re multi-request transactions: there is a way to delay yields, +see :ref:`Atomic execution <atomic_execution>`. Since locks don't exist, and disk writes only involve the write-ahead log, transactions are usually fast. Also the Tarantool server may not be using diff --git a/doc/sphinx/book/box/sophia_diff.rst b/doc/sphinx/book/box/sophia_diff.rst index 9bac36dd7f44230e8d542aee15c5dbd48fc0e4cb..924ef45d3b52a51d3d57c3a7a955d90f98b1cd8a 100644 --- a/doc/sphinx/book/box/sophia_diff.rst +++ b/doc/sphinx/book/box/sophia_diff.rst @@ -55,7 +55,7 @@ It was explained :ref:`earlier <yields_must_happen>` that memtx does not "yield" on a select request, it yields only on data-change requests. However, sophia does yield on a select request, or on an equivalent such as get() or pairs(). This has significance - for cooperative multitasking. + for :ref:`cooperative multitasking <cooperative_multitasking>`. For more about sophia, see Appendix E :ref:`sophia <sophia>`.