-
Notifications
You must be signed in to change notification settings - Fork 388
Interactive transactions and streams in iproto/http2 #5860
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Summary of a planned solutionThe stream is a wrapper around connection object:
IdsEach stream is associated with its id. Id is generated on the client side, but is hidden from the actual user. Instead, user operates on a stream object and internally it is mapped to the corresponding id. In iproto stream id 0 will indicate that there no stream needed and behaviour is old. SerializationRequests in a stream are sent synchronously. The reason is that transactions can now be yielding, so we must wait for the previous request before sending the next one in order to guarantee serialization. Fibers created from a transactionProblemImagine if a transaction in a stream creates new fiber (e.g. it can be done implicitly via some library call). Should this fiber see uncommitted data from this transaction? What if fiber also writes some data, should it get into transaction also? SolutionOne way to the problem is to forbid fibers that write / read data, but this is too restrictive. Let's say that such fibers are fully independent of this transaction: they can't see any data from this tx, and can create their own tx if they want. Where stream is executedEach stream is executed in its own fiber. Tx thread should somehow decide what to process next - maybe we need some sophisticated prioritization? Stream closingWe can return corresponding fiber back to the pool, when:
If connection is closed, all streams are closed and all non-committed transactions from streams are rollbacked. LimitsStream limitAmount of streams is limited by the existing option - net_msg_max. Pending messages limitImagine there is an infinite yielding loop inside a transaction from a stream. So there are infinitelly many messages from this stream. Again, as I understand it is already limited by the net_msg_max. |
Iproto is supposed to be used asynchronously. Let's look at your example:
With synchronous processing we'll have three network round trips (+processing time). Support of pipelining within a stream (a kind of HTTP2 stream) will shrink the delay to near to one round trip (+processing time). BTW, how the 'optional id' in How to make work with an asynchronous stream from Lua convenient? Say, if we want to process all responses asynchronously: local stream = conn:stream({is_async = true})
local futures = {}
table.insert(futures, stream:call('box.begin'))
table.insert(futures, stream:call('box.insert', {...}))
table.insert(futures, stream:call('box.commit'))
for _, future in ipairs(futures) do
local result = future:wait_result()
<...>
end Or: local stream = conn:stream({is_async = true})
stream:call('box.begin')
stream:call('box.insert', {...})
stream:call('box.commit')
for _, result in stream:pairs() do
<...>
end NB: How to distinguish |
I would say request are processed synchronously. TX thread must process the next request of a stream strictly after the previous request of the same stream is completed. As for
Actually we are designing IPROTO API, not the API of one particular connector. The idea is to create and support IPROTO API that would allow all kinds of connectors - sync/async, one-by-one of batched etc. |
For each stream its ID is not optional. Requests with different stream_id a treated as different streams. |
I think we should add to RFC:
|
Why do you need to use the same fiber? By doing so you basically block a fiber until the transaction ends. The fiber can't process more requests from other streams or without streams, and there is -1 free fiber in the pool. Transaction data is allocated on txn's region. So what is the reason to reserve a fiber? |
Not possible. Transaction can't live in 2 fibers. This is not related to streams, just a general restriction. If you start a new fiber in a transaction, it would be totally new fiber without any context of the creator. You can try it even now - do |
You can create a fiber in transaction. You are right, now the new fiber doesn't inherit transaction from. In this RFC we fix this behavior and state that it will remain the same. |
The example was given for the net.box API, so I asked why it may be useful when creating a net.box stream. Sure, there will be an optional stream id in the protocol. |
Actually the only reason I see is simplicity. As for resources - I think that a transaction itself is potentially much more expensive object in comparison to fiber. Having that I'm not sure that saving a fiber is a big deal. |
Fiber is more expensive at least because it makes system calls when created, right in TX thread, and allocates tons of memory for its stack. And because it is a limited resource. By keeping a fiber from re-usage, you reduce number of fibers serving the other iproto calls. (net_msg_max setting is related to this). With a certain amount of concurrent transactions you might exhaust the entire fiber pool so no requests would be served at all except these transactions even though TX thread might be greatly underloaded. Does not look like a "feature" really. Or even something not worthy of considering because it makes a great impact actually. |
Concurrent transactions are not expected to be cheap. Just one transaction in a read view can double data size needed for processing. And that cannot be fixed by any optimization. A certain amount of concurrent long-lasting transactions will kill a server in any case. All we can discuss is that amount. How many would be a good result? I think about dozens or hundreds. Number of fibers doesn't limit that numbers.
If a task can be divided into parts - it should be. Detaching of transaction from fiber is a good optimization follow-up issue, it's independent internal problem. Now we what introduce streams, fix their API and observable behavior. |
|
Will the requests that share the same stream but aren't made inside in an open transaction, be serialized too? Will several requests from several connections be able to share the same stream (~ transaction), thus making a "SELECT FOR UPDATE" scenario possible? |
Some thoughts on the streams implementation:
struct stream {
/** Currently active stream transaction or NULL * /
struct txn *txn;
/** Pending requests for this stream, processed sequentially */
struct rlist pending_requests;
/** Flag indicates, that some stream request processed by tx thread */
bool is_request_processed;
/** Maybe some other fields */
}; Currently tx thread retrieves messages from the cbus queue between iproto and tx threads and processed them synchroniously. If an fiber yeild occurred while processing the message, tx thread will process the next message without waiting for the previous one.
|
|
During writing the code, many questions occured:
local futures = {}
stream = con:stream()
table.insert(futures, steam:begin({is_async = true})) -- Can begin be async??
table.insert(futures, stream:call('box.insert', {...} , {is_async = true}))
table.insert(futures, steam:commit({is_async = true})) -- Can commit be async??
for _, future in ipairs(futures) do
local result = future:wait_result()
<...>
end A brief description of how I am doing now:
|
Implement `begin`, `commit` and `rollback` methods for stream object in `net.box`, which allows to begin, commit and rollback transaction accordingly. Closes #5860 @TarantoolBot document Title: add interactive transaction support in net.box Implement `begin`, `commit` and `rollback` methods for stream object in `net.box`, which allows to begin, commit and rollback transaction accordingly. Now there are multiple ways to begin, commit and rollback transaction from `net.box`: using appropriate stream methods, using 'call` or 'eval' methods or using `execute` method with sql transaction syntax. User can mix these methods, for example, start transaction using `stream:begin()`, and commit transaction using `stream:call('box.commit')` or stream:execute('COMMIT'). Simple example of using interactive transactions via iproto from net.box: ```lua stream = conn:new_stream() space = stream.space.test space_not_from_stream = conn.space.test stream:begin() space:replace({1}) -- return previously inserted tuple, because request -- belongs to transaction. space:select({}) -- empty select, because select doesn't belongs to -- transaction space_not_from_stream:select({}) stream:call('box.commit') -- now transaction was commited, so all requests -- returns tuple. ``` Different examples of using streams you can find in gh-5860-implement-streams-in-iproto.test.lua
Add stream support to `net.box`. In "net.box", stream is an object over connection that has the same methods, but all requests from it sends with non-zero stream ID. Since there can be a lot of streams, we do not copy the spaces from the connection to the stream immediately when creating a stream, but do it only when we first access space. Also, when updating the schema, we update the spaces in lazy mode: each stream has it's own schema_version, when there is some access to stream space we compare stream schema_version and connection schema_version and if they are different update clear stream space cache and wrap space that is being accessed to stream cache. Part of #5860 @TarantoolBot document Title: stream support was added to net.box In "net.box", stream is an object over connection that has the same methods, but all requests from it sends with non-zero stream ID. Stream ID is generated on the client automatically. Simple example of stream creation using net.box: ```lua stream = conn:new_stream() -- all connection methods are valid, but send requests -- with non zero stream_id. ```
Adding interactive transactions over iproto streamss requires adding new request types for begin, commit and rollback them. The type names of these new requests conflict with the existing names for the 'raft' requests. Adding RAFT prefix for all requests related to 'raft' resolves this problem. Part of #5860 @TarantoolBot document Title: add RAFT prefix for all requests related to 'raft'. Rename IPROTO_PROMOTE, IPROTO_DEMOTE, IPROTO_CONFIRM and IPROTO_ROLLBACK to IPROTO_RAFT_PROMOTE, IPROTO_RAFT_DEMOTE, IPROTO_RAFT_CONFIRM and IPROTO_RAFT_ROLLBACK accordingly.
Implement interactive transactions over iproto streams. Each stream can start its own transaction, so they allows multiplexing several transactions over one connection. If any request fails during the transaction, it will not affect the other requests in the transaction. If disconnect occurs when there is some active transaction in stream, this transaction will be rollbacked, if it does not have time to commit before this moment. Part of #5860 @TarantoolBot document Title: interactive transactions was implemented over iproto streams. The main purpose of streams is transactions via iproto. Each stream can start its own transaction, so they allows multiplexing several transactions over one connection. There are multiple ways to begin, commit and rollback transaction: using IPROTO_CALL and IPROTO_EVAL with corresponding function (box.begin, box.commit and box.rollback), IPROTO_EXECUTE with corresponding sql request ('TRANSACTION START', 'COMMIT', 'ROLLBACK') and IPROTO_BEGIN, IPROTO_COMMIT, IPROTO_ROLLBACK accordingly. If disconnect occurs when there is some active transaction in stream, this transaction will be rollbacked, if it does not have time to commit before this moment. Add new command codes for begin, commit and rollback transactions: `IPROTO_BEGIN 14`, `IPROTO_COMMIT 15` and `IPROTO_ROLLBACK 16` accordingly.
Implement `begin`, `commit` and `rollback` methods for stream object in `net.box`, which allows to begin, commit and rollback transaction accordingly. Closes #5860 @TarantoolBot document Title: add interactive transaction support in net.box Implement `begin`, `commit` and `rollback` methods for stream object in `net.box`, which allows to begin, commit and rollback transaction accordingly. Now there are multiple ways to begin, commit and rollback transaction from `net.box`: using appropriate stream methods, using 'call` or 'eval' methods or using `execute` method with sql transaction syntax. User can mix these methods, for example, start transaction using `stream:begin()`, and commit transaction using `stream:call('box.commit')` or stream:execute('COMMIT'). Simple example of using interactive transactions via iproto from net.box: ```lua stream = conn:new_stream() space = stream.space.test space_not_from_stream = conn.space.test stream:begin() space:replace({1}) -- return previously inserted tuple, because request -- belongs to transaction. space:select({}) -- empty select, because select doesn't belongs to -- transaction space_not_from_stream:select({}) stream:call('box.commit') -- now transaction was commited, so all requests -- returns tuple. ``` Different examples of using streams you can find in gh-5860-implement-streams-in-iproto.test.lua
Implement interactive transactions over iproto streams. Each stream can start its own transaction, so they allows multiplexing several transactions over one connection. If any request fails during the transaction, it will not affect the other requests in the transaction. If disconnect occurs when there is some active transaction in stream, this transaction will be rollbacked, if it does not have time to commit before this moment. Part of #5860 @TarantoolBot document Title: interactive transactions was implemented over iproto streams. The main purpose of streams is transactions via iproto. Each stream can start its own transaction, so they allows multiplexing several transactions over one connection. There are multiple ways to begin, commit and rollback transaction: using IPROTO_CALL and IPROTO_EVAL with corresponding function (box.begin, box.commit and box.rollback), IPROTO_EXECUTE with corresponding sql request ('TRANSACTION START', 'COMMIT', 'ROLLBACK') and IPROTO_BEGIN, IPROTO_COMMIT, IPROTO_ROLLBACK accordingly. If disconnect occurs when there is some active transaction in stream, this transaction will be rollbacked, if it does not have time to commit before this moment. Add new command codes for begin, commit and rollback transactions: `IPROTO_BEGIN 14`, `IPROTO_COMMIT 15` and `IPROTO_ROLLBACK 16` accordingly.
Implement `begin`, `commit` and `rollback` methods for stream object in `net.box`, which allows to begin, commit and rollback transaction accordingly. Closes #5860 @TarantoolBot document Title: add interactive transaction support in net.box Implement `begin`, `commit` and `rollback` methods for stream object in `net.box`, which allows to begin, commit and rollback transaction accordingly. Now there are multiple ways to begin, commit and rollback transaction from `net.box`: using appropriate stream methods, using 'call` or 'eval' methods or using `execute` method with sql transaction syntax. User can mix these methods, for example, start transaction using `stream:begin()`, and commit transaction using `stream:call('box.commit')` or stream:execute('COMMIT'). Simple example of using interactive transactions via iproto from net.box: ```lua stream = conn:new_stream() space = stream.space.test space_not_from_stream = conn.space.test stream:begin() space:replace({1}) -- return previously inserted tuple, because request -- belongs to transaction. space:select({}) -- empty select, because select doesn't belongs to -- transaction space_not_from_stream:select({}) stream:call('box.commit') -- now transaction was commited, so all requests -- returns tuple. ``` Different examples of using streams you can find in gh-5860-implement-streams-in-iproto.test.lua
Implement interactive transactions over iproto streams. Each stream can start its own transaction, so they allows multiplexing several transactions over one connection. If any request fails during the transaction, it will not affect the other requests in the transaction. If disconnect occurs when there is some active transaction in stream, this transaction will be rollbacked, if it does not have time to commit before this moment. Part of #5860 @TarantoolBot document Title: interactive transactions was implemented over iproto streams. The main purpose of streams is transactions via iproto. Each stream can start its own transaction, so they allows multiplexing several transactions over one connection. There are multiple ways to begin, commit and rollback transaction: using IPROTO_CALL and IPROTO_EVAL with corresponding function (box.begin, box.commit and box.rollback), IPROTO_EXECUTE with corresponding sql request ('TRANSACTION START', 'COMMIT', 'ROLLBACK') and IPROTO_BEGIN, IPROTO_COMMIT, IPROTO_ROLLBACK accordingly. If disconnect occurs when there is some active transaction in stream, this transaction will be rollbacked, if it does not have time to commit before this moment. Add new command codes for begin, commit and rollback transactions: `IPROTO_BEGIN 14`, `IPROTO_COMMIT 15` and `IPROTO_ROLLBACK 16` accordingly.
Implement `begin`, `commit` and `rollback` methods for stream object in `net.box`, which allows to begin, commit and rollback transaction accordingly. Closes #5860 @TarantoolBot document Title: add interactive transaction support in net.box Implement `begin`, `commit` and `rollback` methods for stream object in `net.box`, which allows to begin, commit and rollback transaction accordingly. Now there are multiple ways to begin, commit and rollback transaction from `net.box`: using appropriate stream methods, using 'call` or 'eval' methods or using `execute` method with sql transaction syntax. User can mix these methods, for example, start transaction using `stream:begin()`, and commit transaction using `stream:call('box.commit')` or stream:execute('COMMIT'). Simple example of using interactive transactions via iproto from net.box: ```lua stream = conn:new_stream() space = stream.space.test space_not_from_stream = conn.space.test stream:begin() space:replace({1}) -- return previously inserted tuple, because request -- belongs to transaction. space:select({}) -- empty select, because select doesn't belongs to -- transaction space_not_from_stream:select({}) stream:call('box.commit') -- now transaction was commited, so all requests -- returns tuple. ``` Different examples of using streams you can find in gh-5860-implement-streams-in-iproto.test.lua
Implement interactive transactions over iproto streams. Each stream can start its own transaction, so they allows multiplexing several transactions over one connection. If any request fails during the transaction, it will not affect the other requests in the transaction. If disconnect occurs when there is some active transaction in stream, this transaction will be rollbacked, if it does not have time to commit before this moment. Part of #5860 @TarantoolBot document Title: interactive transactions was implemented over iproto streams. The main purpose of streams is transactions via iproto. Each stream can start its own transaction, so they allows multiplexing several transactions over one connection. There are multiple ways to begin, commit and rollback transaction: using IPROTO_CALL and IPROTO_EVAL with corresponding function (box.begin, box.commit and box.rollback), IPROTO_EXECUTE with corresponding sql request ('TRANSACTION START', 'COMMIT', 'ROLLBACK') and IPROTO_BEGIN, IPROTO_COMMIT, IPROTO_ROLLBACK accordingly. If disconnect occurs when there is some active transaction in stream, this transaction will be rollbacked, if it does not have time to commit before this moment. Add new command codes for begin, commit and rollback transactions: `IPROTO_BEGIN 14`, `IPROTO_COMMIT 15` and `IPROTO_ROLLBACK 16` accordingly.
Implement `begin`, `commit` and `rollback` methods for stream object in `net.box`, which allows to begin, commit and rollback transaction accordingly. Closes #5860 @TarantoolBot document Title: add interactive transaction support in net.box Implement `begin`, `commit` and `rollback` methods for stream object in `net.box`, which allows to begin, commit and rollback transaction accordingly. Now there are multiple ways to begin, commit and rollback transaction from `net.box`: using appropriate stream methods, using 'call` or 'eval' methods or using `execute` method with sql transaction syntax. User can mix these methods, for example, start transaction using `stream:begin()`, and commit transaction using `stream:call('box.commit')` or stream:execute('COMMIT'). Simple example of using interactive transactions via iproto from net.box: ```lua stream = conn:new_stream() space = stream.space.test space_not_from_stream = conn.space.test stream:begin() space:replace({1}) -- return previously inserted tuple, because request -- belongs to transaction. space:select({}) -- empty select, because select doesn't belongs to -- transaction space_not_from_stream:select({}) stream:call('box.commit') -- now transaction was commited, so all requests -- returns tuple. ``` Different examples of using streams you can find in gh-5860-implement-streams-in-iproto.test.lua
To apply a client request, we only need to know its type and body. All the meta information, such as LSN, TSN, or replica id, must be set by WAL. Currently, however, it isn't necessarily true: iproto leaves a request header received over iproto as is, and tx will reuse the header instead of allocating a new one in this case, which is needed to process replication requests, see txn_add_redo(). Unless a client actually sets one of those meta fields, this causes no problems. However, if we added transaction support to the replication protocol, reusing the header would result in broken xlog, because currently, all requests received over iproto have the is_commit field set in xrow_header for the lack of TSN, while is_commit must only be set for the final statement in a transaction. One way to fix it would be clearing is_commit explicitly in iproto, but ignoring the whole header received over iproto looks more logical and error-proof. Needed for #5860
For further implementation of streams, we need to separate requests belonging to and not belonging to streams. For this purpose, the stream ID field was added to the iproto binary protocol. For requests that do not belong to stream, this field is omitted or equal to zero. For requests belonging to stream, we use this field to determine which stream the request belongs to. Part of #5860 @TarantoolBot document Title: new field in binary iproto protocol Add new field to binary iproto protocol. `IPROTO_STREAM_ID 0x0a` determines whether a request belongs to a stream or not. If this field is omited or equal to zero this request doesn't belongs to stream.
Implement streams in iproto. There is a hash table of streams for each connection. When a new request comes with a non-zero stream ID, we look for the stream with such ID in this table and if it does not exist, we create it. The request is placed in the queue of pending requests, and if this queue was empty at the time of its receipt, it is pushed to the tx thread for processing. When a request belonging to stream returns to the network thread after processing is completed, we take the next request out of the queue of pending requests and send it for processing to tx thread. If there is no pending requests we remove stream object from hash table and destroy it. Requests with zero stream ID are processed in the old way. Part of #5860 @TarantoolBot document Title: streams are implemented in iproto A distinctive feature of streams is that all requests in them are processed sequentially. The execution of the next request in stream will not start until the previous one is completed. To separate requests belonging to and not belonging to streams we use stream ID field in binary iproto protocol: requests with non-zero stream ID belongs to some stream. Stream ID is unique within the connection and indicates which stream the request belongs to. For streams from different connections, the IDs may be the same.
Add stream support to `net.box`. In "net.box", stream is an object over connection that has the same methods, but all requests from it sends with non-zero stream ID. Since there can be a lot of streams, we do not copy the spaces from the connection to the stream immediately when creating a stream, but do it only when we first access space. Also, when updating the schema, we update the spaces in lazy mode: each stream has it's own schema_version, when there is some access to stream space we compare stream schema_version and connection schema_version and if they are different update clear stream space cache and wrap space that is being accessed to stream cache. Part of #5860 @TarantoolBot document Title: stream support was added to net.box In "net.box", stream is an object over connection that has the same methods, but all requests from it sends with non-zero stream ID. Stream ID is generated on the client automatically. Simple example of stream creation using net.box: ```lua stream = conn:new_stream() -- all connection methods are valid, but send requests -- with non zero stream_id. ```
Adding interactive transactions over iproto streamss requires adding new request types for begin, commit and rollback them. The type names of these new requests conflict with the existing names for the 'raft' requests. Adding RAFT prefix for all requests related to 'raft' resolves this problem. Part of #5860 @TarantoolBot document Title: add RAFT prefix for all requests related to 'raft'. Rename IPROTO_PROMOTE, IPROTO_DEMOTE, IPROTO_CONFIRM and IPROTO_ROLLBACK to IPROTO_RAFT_PROMOTE, IPROTO_RAFT_DEMOTE, IPROTO_RAFT_CONFIRM and IPROTO_RAFT_ROLLBACK accordingly.
Implement interactive transactions over iproto streams. Each stream can start its own transaction, so they allows multiplexing several transactions over one connection. If any request fails during the transaction, it will not affect the other requests in the transaction. If disconnect occurs when there is some active transaction in stream, this transaction will be rollbacked, if it does not have time to commit before this moment. Part of #5860 @TarantoolBot document Title: interactive transactions was implemented over iproto streams. The main purpose of streams is transactions via iproto. Each stream can start its own transaction, so they allows multiplexing several transactions over one connection. There are multiple ways to begin, commit and rollback transaction: using IPROTO_CALL and IPROTO_EVAL with corresponding function (box.begin, box.commit and box.rollback), IPROTO_EXECUTE with corresponding sql request ('TRANSACTION START', 'COMMIT', 'ROLLBACK') and IPROTO_BEGIN, IPROTO_COMMIT, IPROTO_ROLLBACK accordingly. If disconnect occurs when there is some active transaction in stream, this transaction will be rollbacked, if it does not have time to commit before this moment. Add new command codes for begin, commit and rollback transactions: `IPROTO_BEGIN 14`, `IPROTO_COMMIT 15` and `IPROTO_ROLLBACK 16` accordingly.
Implement `begin`, `commit` and `rollback` methods for stream object in `net.box`, which allows to begin, commit and rollback transaction accordingly. Closes #5860 @TarantoolBot document Title: add interactive transaction support in net.box Implement `begin`, `commit` and `rollback` methods for stream object in `net.box`, which allows to begin, commit and rollback transaction accordingly. Now there are multiple ways to begin, commit and rollback transaction from `net.box`: using appropriate stream methods, using 'call` or 'eval' methods or using `execute` method with sql transaction syntax. User can mix these methods, for example, start transaction using `stream:begin()`, and commit transaction using `stream:call('box.commit')` or stream:execute('COMMIT'). Simple example of using interactive transactions via iproto from net.box: ```lua stream = conn:new_stream() space = stream.space.test space_not_from_stream = conn.space.test stream:begin() space:replace({1}) -- return previously inserted tuple, because request -- belongs to transaction. space:select({}) -- empty select, because select doesn't belongs to -- transaction space_not_from_stream:select({}) stream:call('box.commit') -- now transaction was commited, so all requests -- returns tuple. ``` Different examples of using streams you can find in gh-5860-implement-streams-in-iproto.test.lua
To apply a client request, we only need to know its type and body. All the meta information, such as LSN, TSN, or replica id, must be set by WAL. Currently, however, it isn't necessarily true: iproto leaves a request header received over iproto as is, and tx will reuse the header instead of allocating a new one in this case, which is needed to process replication requests, see txn_add_redo(). Unless a client actually sets one of those meta fields, this causes no problems. However, if we added transaction support to the replication protocol, reusing the header would result in broken xlog, because currently, all requests received over iproto have the is_commit field set in xrow_header for the lack of TSN, while is_commit must only be set for the final statement in a transaction. One way to fix it would be clearing is_commit explicitly in iproto, but ignoring the whole header received over iproto looks more logical and error-proof. Needed for #5860
For further implementation of streams, we need to separate requests belonging to and not belonging to streams. For this purpose, the stream ID field was added to the iproto binary protocol. For requests that do not belong to stream, this field is omitted or equal to zero. For requests belonging to stream, we use this field to determine which stream the request belongs to. Part of #5860 @TarantoolBot document Title: new field in binary iproto protocol Add new field to binary iproto protocol. `IPROTO_STREAM_ID 0x0a` determines whether a request belongs to a stream or not. If this field is omited or equal to zero this request doesn't belongs to stream.
Implement streams in iproto. There is a hash table of streams for each connection. When a new request comes with a non-zero stream ID, we look for the stream with such ID in this table and if it does not exist, we create it. The request is placed in the queue of pending requests, and if this queue was empty at the time of its receipt, it is pushed to the tx thread for processing. When a request belonging to stream returns to the network thread after processing is completed, we take the next request out of the queue of pending requests and send it for processing to tx thread. If there is no pending requests we remove stream object from hash table and destroy it. Requests with zero stream ID are processed in the old way. Part of #5860 @TarantoolBot document Title: streams are implemented in iproto A distinctive feature of streams is that all requests in them are processed sequentially. The execution of the next request in stream will not start until the previous one is completed. To separate requests belonging to and not belonging to streams we use stream ID field in binary iproto protocol: requests with non-zero stream ID belongs to some stream. Stream ID is unique within the connection and indicates which stream the request belongs to. For streams from different connections, the IDs may be the same.
Add stream support to `net.box`. In "net.box", stream is an object over connection that has the same methods, but all requests from it sends with non-zero stream ID. Since there can be a lot of streams, we do not copy the spaces from the connection to the stream immediately when creating a stream, but do it only when we first access space. Also, when updating the schema, we update the spaces in lazy mode: each stream has it's own schema_version, when there is some access to stream space we compare stream schema_version and connection schema_version and if they are different update clear stream space cache and wrap space that is being accessed to stream cache. Part of #5860 @TarantoolBot document Title: stream support was added to net.box In "net.box", stream is an object over connection that has the same methods, but all requests from it sends with non-zero stream ID. Stream ID is generated on the client automatically. Simple example of stream creation using net.box: ```lua stream = conn:new_stream() -- all connection methods are valid, but send requests -- with non zero stream_id. ```
Adding interactive transactions over iproto streamss requires adding new request types for begin, commit and rollback them. The type names of these new requests conflict with the existing names for the 'raft' requests. Adding RAFT prefix for all requests related to 'raft' resolves this problem. Part of #5860 @TarantoolBot document Title: add RAFT prefix for all requests related to 'raft'. Rename IPROTO_PROMOTE, IPROTO_DEMOTE, IPROTO_CONFIRM and IPROTO_ROLLBACK to IPROTO_RAFT_PROMOTE, IPROTO_RAFT_DEMOTE, IPROTO_RAFT_CONFIRM and IPROTO_RAFT_ROLLBACK accordingly.
Implement interactive transactions over iproto streams. Each stream can start its own transaction, so they allows multiplexing several transactions over one connection. If any request fails during the transaction, it will not affect the other requests in the transaction. If disconnect occurs when there is some active transaction in stream, this transaction will be rollbacked, if it does not have time to commit before this moment. Part of #5860 @TarantoolBot document Title: interactive transactions was implemented over iproto streams. The main purpose of streams is transactions via iproto. Each stream can start its own transaction, so they allows multiplexing several transactions over one connection. There are multiple ways to begin, commit and rollback transaction: using IPROTO_CALL and IPROTO_EVAL with corresponding function (box.begin, box.commit and box.rollback), IPROTO_EXECUTE with corresponding sql request ('TRANSACTION START', 'COMMIT', 'ROLLBACK') and IPROTO_BEGIN, IPROTO_COMMIT, IPROTO_ROLLBACK accordingly. If disconnect occurs when there is some active transaction in stream, this transaction will be rollbacked, if it does not have time to commit before this moment. Add new command codes for begin, commit and rollback transactions: `IPROTO_BEGIN 14`, `IPROTO_COMMIT 15` and `IPROTO_ROLLBACK 16` accordingly.
To apply a client request, we only need to know its type and body. All the meta information, such as LSN, TSN, or replica id, must be set by WAL. Currently, however, it isn't necessarily true: iproto leaves a request header received over iproto as is, and tx will reuse the header instead of allocating a new one in this case, which is needed to process replication requests, see txn_add_redo(). Unless a client actually sets one of those meta fields, this causes no problems. However, if we added transaction support to the replication protocol, reusing the header would result in broken xlog, because currently, all requests received over iproto have the is_commit field set in xrow_header for the lack of TSN, while is_commit must only be set for the final statement in a transaction. One way to fix it would be clearing is_commit explicitly in iproto, but ignoring the whole header received over iproto looks more logical and error-proof. Needed for #5860 (cherry picked from commit 4fefb51)
Interactive transactions and streams in iproto/http2
Problem
With the introduction of the new transaction manager for memtx, it becomes possible to yield inside a transaction regardless of the engine used. This allows to implement:
Currently tarantool uses iproto as the main communication protocol, but in the future http2 is planned. So the goal is to implement streams for both of the protocols.
Solutions
Rejected in favor of streams
Interactive tx over IPROTO without streams
It was decided that we need a more general approach - streams, plus not only via iproto, but http2 protocol.
issue, PR
Implementation
Introduce begin, commit, rollback commands for a connection object (IPROTO_BEGIN, IPROTO_COMMIT, IPROTO_ROLLBACK accordingly for iproto protocol).
Introduce remote_txn structure: it contains txn and rlist of pending messages (iproto_msg) to be processed. This structure will be allocated only for the new transactions over IPROTO inside the memory pool.
Encapsulate inside iproto_msg new fields: 1) txn - current transaction (or null) 2) next_in_tx - member of remote_txn->pending.
Encapsulate inside the iproto_connection remote_txn object.
Behaviour:
Streams
Old discussion
Idea of streams in iproto was previously [discussed](https://tkn.me/tarantool/rfc-interactive-transactions-in-iproto/) ([archive](https://tkn.me/tarantool/rfc-interactive-transactions-in-iproto.tar.bz2)).Short intro to an idea from this discussion:
Questions extracted from a discussion:
Summary from Osipov:
The stream is a wrapper around connection object:
Ids
Each stream is associated with its id. Id is generated on the client side, but is hidden from the actual user. Instead, user operates on a stream object and internally it is mapped to the corresponding id.
We introduce STREAM_ID of request in IPROTO protocol. Omitted or STREAM_ID = 0 means legacy behavior.
Serialization
Requests in a stream are processed synchronously. The reason is that transactions can now be yielding, so we must wait for the previous request before sending the next one in order to guarantee serialization.
Fibers created from a transaction
Problem
Imagine if a transaction in a stream creates new fiber (e.g. it can be done implicitly via some library call). Should this fiber see uncommitted data from this transaction? What if fiber also writes some data, should it get into transaction also?
Solution
One way to the problem is to forbid fibers that write / read data, but this is too restrictive. Let's say that such fibers are fully independent of this transaction: they can't see any data from this tx, and can create their own tx if they want.
Where stream is executed
Each stream is executed in its own fiber. Tx thread should somehow decide what to process next - maybe we need some sophisticated prioritization?
Stream closing
We can return corresponding fiber back to the pool, when:
If connection is closed, all streams are closed and all non-committed transactions from streams are rollbacked.
Limits
Stream limit
Amount of streams is limited by the existing option - net_msg_max.
Pending messages limit
Imagine there is an infinite yielding loop inside a transaction from a stream. So there are infinitelly many messages from this stream. Again, as I understand it is already limited by the net_msg_max.
HTTP/2.0
The concept of streams is already encapsulated in the protocol. The main idea is to multiplex several streams through one TCP connection.
Stream can be started by either client or server.
Stream id must be positive integer, odd for clients, even for the server. Stream id zero is reserved.
Protocols
IPROTO
HTTP/2.0
The text was updated successfully, but these errors were encountered: