Replies: 2 comments 9 replies
-
Looks great, but seems that bucket_id in request on client is a big problem. Why not to use shrading_keys array which will make unnecessary to calculate the bucket_id on the client? Acoording to Roadmap ddl and crud will support custom sharding functions from the box in the nearest future. |
Beta Was this translation helpful? Give feedback.
-
I did some benchmarks. Locally on Mac and on a Linux server, results are almost the same (comparatively, not in absolute timing). Overall it doesn't look like a killer feature TBH. Here is the source code: https://github.com/tarantool/vshard/tree/gerold103/benchmark/bench. I run it like this:
Then observe results. Time is measured for getting the responses. Their sending is not counted. To try various parameters change The bench tried to compare Test 1 - a single number
Quite bad result. Here Test 2 - several big numbers are sent and received
Well, more than twice faster now! The arguments are an array of 10 items. Each is Test 3 - big complex multi-level objects, JSON, sent and received
The big JSON object is a big Lua table with many fields. See the source code of the bench. Even bigger difference. Obviously the more decode/encode does normal call, the faster Test 4 - big JSON, not many of them per call, but more calls
Expected result. Less arguments = less difference. But still good improvement. Test 5 - empty arguments, many numbers in result
Select does not take any arguments but returns an array of tuples. Each has 5 numbers, each number wrapped into 5 arrays. The difference is much less notable now. Only one part of the data stream competes for tx time on the router. Test 6 - empty arguments, big JSONs in result
When select results get bigger, the difference grows again in favor of Test 7 - a lot of flat integers in result
Router uses I've also collected The reason is that In In plain usage can see that CPU is all over Lua, not just in GC - string allocation (lj_str_new), encode/decode ( In both traces can see that some time is spent in syscalls. This is because netbox sockets live in TX thread. SummaryThe outcome of this IMO is that router can't be truly optimized while it is written in Lua. And maybe even while it works in Tarantool runtime with just one thread. Even if you scale iproto threads, still netbox connections to the storages are in TX. Moreover, iproto threads suffer from connections being pinned to threads. So that would make router's load on its threads imbalanced. The |
Beta Was this translation helpful? Give feedback.
-
The related issue is #312. The discussion starts with a description of how the task looks in my understanding. Then I provide my vision of API and behaviour, some insights at internals, frequent questions, alternatives.
Problems with how it works now
Router sometimes is used as a proxy, not as a client itself. In that case it does a lot of unnecessary MessagePack decoding in Lua. For instance, when
vshard.router.call()
is called by a remote client, firstly the router will decode arguments ofvshard.router.call()
itself. Secondly, when it is executed on the storage, it will decode storage's response.The decoding itself is cheap, but the results are pushed to Lua and that is very expensive. Puts a lot of load on Lua GC.
But most of that decoding-work is not needed. Indeed, the signature of
vshard.router.call()
isbucket_id, opts, func, args, netbox_opts
. The heaviest part isargs
which are the storage's function arguments and the router doesn't need it.When func's result is returned from the storage, the router doesn't need it either. It is forwarded back to the client as is.
How it should work
When used as a proxy, the router must not decode unnecessary data. It should only decode a few light arguments of
vshard.router.call()
and leave the rest untouched as a binary buffer.Since 2.10.0-beta2 Tarantool supports 2 APIs which make the idea possible:
box.schema.func
withtakes_raw_args = true
. Then it will take a single argument in Lua - anmsgpack
object which internally stores the array of arguments in a plain MessagePack buffer, received from the outside as is.return_raw = true
option. Then the result is anmsgpack
object. Regardless of what was the remotely called function.It is proposed to utilise these new features as follows.
API and behaviour
Storage
Nothing changes. Storage needs to decode the user's function arguments anyway.
Router
Part 1
Firstly, the router will need to support
return_raw
option in allvshard.router.call*
functions. It is going to be needed regardless of what happens next.When has that option, it will forward it to call
conn:call('vshard.storage.call', ...)
invocations inside and will decode only first small part of the result to find if it is an error, wither it needs a retry, etc.Part 2
The support of
return_raw
option is not enough. The arguments ofvshard.router.call()
itself still are decoded when it is called by a remote instance via iproto.It is proposed to add a new function:
The
raw_args
is expected to be a Lua object of typemsgpack
. It should be a MessagePack array with the same arguments asvshard.router.call()
.The function should be registered in
box.schema.func
by the user if he wants to utilise it. The router can't do it itself because it shouldn't depend on the schema anyhow.Internally that new function will perform something like this:
This is how the usage would look on the client:
The value of
args
is decoded only once on the target storage and nowhere else.FAQ
Wouldn't it be faster to put the storage args in the end of
vshard.router.callraw()
signature?This could be asked by someone who thinks that making
local storage_args = iterator:take()
won't be needed ifstorage_args
is the last argument ofvshard.router.callraw()
.It won't help,
iterator:take()
is inevitable. Becauseiterator:decode()
does not change the originalmsgpack
object. The latter still contains the full array of allcallraw
arguments. However, it is not a big deal sinceiterator:take()
won't push anything to Lua. It will only callmp_next()
inside and create a newmsgpack
object from it. The data won't be even copied. It will reference the original buffer.How much faster is it to use
vshard.router.callraw
instead ofvshard.router.call
from a client?I have no idea. It isn't implemented yet. But old benchmarks show that a lot of time was spent in pushing data to Lua stack on the router and it got worse when
storage_args
was getting bigger.Once I have this feature implemented in any form, I will make a benchmark and update this RFC.
Alternatives
Make
vshard.router.call()
be able to acceptmsgpack
argsThe idea is to allow to call
vshard.router.call()
in 2 ways at the same time:vshard.router.call(msgpack_args)
vshard.router.call(bucket_id, router_opts, func, args, netbox_opts)
.It could make the API more compact, but on the other hand would complicate the most used function of the router and slow it down a bit as it will need to branch depending on its arguments type. Hence it was decided to go with a new router function.
Call new function
vshard.router.callproxy
instead ofvshard.router.callraw
It could look more explicit on the client. But on the other hand it can happen that the arguments are very light. So their decoding is actually faster than carrying them in a Lua
msgpack
object. Then callingvshard.router.call
would be just fine. The namecallproxy()
then would raise the question why shouldn't it be always used by clients.Thus it was decided to use
callraw
.Maybe drop
callraw
at all if it just calls a few:decode()
and one:take()
?Its implementation is provided above and it is indeed quite simple. I am actually thinking about not introducing it at all. So as to keep the router's API simpler. User's will need to make
box.schema.func.create()
anyway. In the same place they could implement it on their own.Beta Was this translation helpful? Give feedback.
All reactions