Skip to content

router: make full map_callrw with split args#644

Open
mrForza wants to merge 5 commits into
tarantool:masterfrom
mrForza:gh-559-full-map-call-rw-with-split-args
Open

router: make full map_callrw with split args#644
mrForza wants to merge 5 commits into
tarantool:masterfrom
mrForza:gh-559-full-map-call-rw-with-split-args

Conversation

@mrForza

@mrForza mrForza commented Mar 4, 2026

Copy link
Copy Markdown
Contributor

This patch introduces a new way of map_callrw execution by which we can
pass some arguments to all storages and split buckets' arguments to those
storages that have at least one bucket of bucket_ids. To achieve this we
introduce a new string option - mode to map_callrw api.

Also we change the logic of router_ref_storage_all ref function. Firstly
we ref all storages and get back an amount of "moved" buckets according
to the previously built router's cache. Then if there are no "moved"
buckets we accumulate and check total amount of buckets on all storages
and finish map_callrw ref stage. Otherwise, if there are some "moved"
buckets we perform the second network hop by checking on which replicasets
do the remaining "moved" buckets reside on.

Closes #559

@Serpentian Serpentian left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well done, only one major comment (upgrade), we must address, other ones are nits and smth to think about)

Comment thread vshard/router/init.lua Outdated
Comment thread vshard/router/init.lua
Comment thread vshard/router/init.lua Outdated
Comment thread vshard/router/init.lua Outdated
Comment thread vshard/router/init.lua Outdated
-- high-level ref functions (such as router_ref_storage_all and router_ref_
-- storage_by_buckets).
--
local function router_ref_send(router, timeout, args_builder, grouped_buckets)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, did you consider making it even more general: router_map_callrw_send and router_map_callrw_collect and also reuse them in the replicasets_map_reduce too? There we have almost the same code. Not a call to action, just something to think about.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, router_map_callrw_prepare/send/collect sounds better.

But I don't think that we can easily reuse these functions in replicasets_map_reduce because:

  1. We need to change api of router_map_callrw_send by
    • changing router param into replicasets_all, as replicasets_map_reduce does not have router variable in it.
    • adding extra argument - return_raw
  2. The logic of sending map stage in replicasets_map_reduce is more complex than logic of router_map_callrw_send. In last one we only do RPC with no arguments or with grouped_buckets[rs_id] and that's all. But in replicasets_map_reduce we need to dynamically change those arguments which should be passed in RPC. Before RPC we add bucket arguments to args and after RPC we delete them. The main problem here is that we can only easily add groupped buckets in args builder, but not delete, because the deletion is happened after RPC. If we want to do it in our new router_map_callrw_send function we need to complicate args_builder and use table.deepcopy (it can slow down perf).
  3. We need to change api of router_map_callrw_collect by passing extra return_raw argument.
  4. Also router_map_callrw_collect will be complicated as we should add two different ways of extracting results.

@Serpentian Serpentian Apr 8, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changing router param into replicasets_all, as replicasets_map_reduce does not have router variable in it.

1.1 Yes, and it will become replicasets_map_send, which is way cleaner, since you will explicitly pass the replicasets and not use some non obvious loigic to create the list of replicasets based on optional argument grouped_buckets (which won't be needed). You can return replicasets from the router_map_prepare.

adding extra argument - return_raw

1.2. Let's just pass the opts and add is_async explicitly inside the function. And call the funciton router_map_callrw_send_async. The name should always say, what the function does.

The logic of sending map stage in replicasets_map_reduce is more complex than logic of router_map_callrw_send.

  1. Agree, see no other way than deepcopy.

We need to change api of router_map_callrw_collect by passing extra return_raw argument.
Also router_map_callrw_collect will be complicated as we should add two different ways of extracting results.

  1. You can always just create the new function router_map_callrw_collect_raw and not pass the raw to it

Again, up to you, if you think, that the current variant is better, I'm ok

Comment thread vshard/storage/init.lua Outdated
Comment thread vshard/storage/init.lua Outdated
Comment thread vshard/storage/init.lua Outdated
@Serpentian Serpentian requested a review from Gerold103 March 12, 2026 18:23
@Serpentian Serpentian assigned Gerold103 and mrForza and unassigned Serpentian Mar 12, 2026

@kamenkremen kamenkremen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great patch! Left some minor comments below

Comment thread vshard/router/init.lua Outdated
local futures = {}
local opts_async = {is_async = true}
local replicasets_all = router.replicasets
local rs_ids = grouped_buckets and grouped_buckets or replicasets_all

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't that equivalent to local rs_ids = grouped_buckets or replicasets_all?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Comment thread vshard/router/init.lua

--
-- Perform Ref stage of the Ref-Map-Reduce process on a subset of all the
-- replicasets, which contains all the listed bucket IDs.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment got cut out

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Comment thread vshard/router/init.lua Outdated
--
-- Ref stage: collect.
--
futures = futures or {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

futures is already either {}(because of line 820) or some table, so we don't need this

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Comment thread vshard/router/init.lua Outdated
-- Group the buckets by replicasets according to the router cache.
grouped_buckets, err = buckets_group(router, bucket_ids, timeout)
if err ~= nil then
return nil, err

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: triple space in indentation

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Comment thread vshard/router/init.lua
Comment thread vshard/router/init.lua
Comment thread test/router-luatest/map_callrw_test.lua
Comment thread test/router-luatest/map_callrw_test.lua
@kamenkremen kamenkremen removed their assignment Mar 20, 2026
@mrForza mrForza force-pushed the gh-559-full-map-call-rw-with-split-args branch 2 times, most recently from 8962951 to 8f5223c Compare April 1, 2026 12:58
@mrForza mrForza requested a review from Serpentian April 1, 2026 13:33
@mrForza mrForza assigned Serpentian and unassigned Gerold103 and mrForza Apr 1, 2026

@Serpentian Serpentian left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have any major comments, I think it's time to ask @Gerold103 for review, so that we can be sure, that there no major flaws, which I've missed

Comment thread test/luatest_helpers/vtest.lua Outdated
Comment thread test/router-luatest/reload_test.lua Outdated
Comment thread test/router-luatest/reload_test.lua
Comment thread test/router-luatest/reload_test.lua Outdated
Comment thread test/router-luatest/reload_test.lua Outdated
Comment thread test/router-luatest/reload_test.lua Outdated
Comment thread vshard/storage/init.lua Outdated
Comment thread vshard/router/init.lua Outdated
local replicasets_all = router.replicasets
local deadline = fiber_clock() + timeout
if bucket_ids then
bucket_ids = bucket_ids or {}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be in the first commit

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it's not

Comment thread vshard/router/init.lua Outdated
Comment thread vshard/router/init.lua Outdated
@Serpentian Serpentian assigned Gerold103 and mrForza and unassigned Serpentian Apr 9, 2026

@Gerold103 Gerold103 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this complex topic and being patient about comments 🙏.

Comment thread vshard/router/init.lua Outdated
Comment thread vshard/router/init.lua Outdated
Comment thread vshard/router/init.lua Outdated
Comment thread vshard/router/init.lua Outdated
Comment thread vshard/router/init.lua Outdated
Comment thread vshard/router/init.lua Outdated
Comment thread vshard/storage/init.lua Outdated
Comment thread vshard/storage/init.lua Outdated
Comment thread vshard/storage/init.lua Outdated
@mrForza mrForza force-pushed the gh-559-full-map-call-rw-with-split-args branch 2 times, most recently from 3a2407a to 74f7492 Compare May 27, 2026 14:34
@mrForza mrForza requested a review from Gerold103 May 27, 2026 19:04
Comment thread vshard/router/init.lua Outdated
Comment thread vshard/router/init.lua Outdated
Comment thread vshard/router/init.lua Outdated
Comment thread test/router-luatest/reload_test.lua Outdated
Comment thread test/router-luatest/map_callrw_test.lua
Comment thread vshard/storage/init.lua Outdated
Comment thread vshard/error.lua Outdated
Comment thread vshard/router/init.lua
Comment thread vshard/router/init.lua Outdated
@mrForza mrForza force-pushed the gh-559-full-map-call-rw-with-split-args branch from 74f7492 to fc4a323 Compare June 15, 2026 09:53
Comment thread vshard/router/init.lua Outdated
local buckets = grouped_buckets[rs_id]
local target
if buckets then
args_ref[1] = 'storage_ref_make_with_buckets'

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I'd propose to specify the whole arg table. It's very difficult to parse

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@mrForza mrForza force-pushed the gh-559-full-map-call-rw-with-split-args branch 2 times, most recently from 7cc821a to 3eda39c Compare June 22, 2026 19:00
@mrForza mrForza assigned Gerold103 and unassigned Gerold103 and Serpentian Jun 22, 2026

@Gerold103 Gerold103 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am so sorry, but I have to write more comments 😭🥲.

Comment thread test/router-luatest/router_2_2_test.lua
Comment thread vshard/error.lua Outdated
Comment thread vshard/router/init.lua Outdated
Comment thread vshard/router/init.lua Outdated
Comment thread vshard/router/init.lua Outdated
Comment thread vshard/router/init.lua Outdated
Comment thread vshard/storage/init.lua
Comment on lines +3500 to +3504
local bucket = M.bucket_refs[bucket_id] or
box.space._bucket:get{bucket_id}
if bucket then
assert(bucket.status == BACTIVE or bucket.status == BPINNED)
table.insert(res, bucket_id)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is same as before - bucket refs have no status. This is bucket ref:

        struct bucket_ref {
            uint32_t ro;
            uint32_t rw;
            bool rw_lock;
            bool ro_lock;
        };

When the ref exists, you will try to do bucket.status which will throw an exception. The questions remains - how do tests pass if the code will fail always when any of these buckets have an existing ref object?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I deleted the assertion in which we check bucket.status. The ref guarantees us that status of bucket will be ACTIVE or PINNED.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not exactly. Ref might also exist for a bucket in READONLY or SENDING state. Or even for SENT. But in this case it shouldn't matter. We are only checking a previously created ref. If it exists, it means a bucket couldn't change its status, I suppose. It might be readonly in theory somehow, but I can't see how at the moment. @Serpentian , to our talk about storage refs vs buckets being readable/writable.

Comment thread vshard/storage/init.lua Outdated
Comment thread vshard/router/init.lua
mrForza added 4 commits June 26, 2026 14:42
Before this patch the main `map_callrw` ref functions such as
`router_ref_storage_all` and `router_ref_storage_by_buckets` were
enormous (71 and 108 lines of code). Also these functions have a large
number of similar functional code blocks such as "preparing refs",
"waiting refs" e.t.c. Since in tarantool#559 patch we will
extend the logic of full map_callrw making it able to work with split
args, the `router_ref_storage_all` can double in size. It can lead to
degradation of our codebase due to less readability.

To fix it we firstly determine general and repeated code blocks in ref
functions:
1) `ref-prepare`: groups buckets by replicasets with router's cache,
   builds a table of "target" replicasets and waits necessary masters.
2) `ref-wait`: waits until future objects are ready in order to
   extract payload from it (responses of storages' functions).
3) `ref-process`: a custom logic for `full` or `partial` map_callrw
   modes which describes how we should process results from future
   objects.

After defining the main stages of ref map_callrw functions we should
unify them so that we can use them in both `router_ref_storage_all` and
`router_ref_storage_by_buckets`.

Needed for tarantool#559

NO_TEST=refactoring
NO_DOC=refactoring
In this patch we change `status.SENDING/SENT/GARBAGE/RECEIVING/ACTIVE` on
`BSENDING/BSENT/BGARBAGE/BRECEIVING/BACTIVE` to not repeat and
complicate the code.

Needed for tarantool#559

NO_DOC=refactoring
NO_TEST=refactoring
This patch takes initialization of `rid` out to `router_map_callrw` and
passes this variable to ref-functions. It is needed for future features
tidiness, for example - `make full map_callrw with split args` in which
the logic of `router_map_callrw` becomes more complex.

Needed for tarantool#559

NO_DOC=refactoring
NO_TEST=refactoring
Before this patch the `router-luatest/reload_test` checked router's
services only with old routers. However in future patch (tarantoolgh-214) we
need to check map_callrw with old storages.

In order to make it able we:
1) change `vtest.cluster_new` so that we can pass server_config with
   certain ENV (LUA_PATH) variable into it. It can help us to create
   a new cluster on old version of vshard.
2) change `reload_router` to more general `reload_server` in order to
   unify the process of servers (router / storage) upgrade in
   `router-luatest/reload_test`.
3) unify the process of cluster creation -
   `create_cluster_on_specific_version` and the process of getting
   server's config with new ENV (LUA_PATH) variable -
   `get_config_for_specific_vshard_version`.

Needed for tarantool#214

NO_DOC=refactoring
NO_TEST=refactoring
@mrForza mrForza force-pushed the gh-559-full-map-call-rw-with-split-args branch 2 times, most recently from 26a0745 to e54c676 Compare June 29, 2026 14:57
@mrForza mrForza requested a review from Gerold103 June 29, 2026 15:19
@mrForza mrForza assigned Gerold103 and Serpentian and unassigned Gerold103 Jun 29, 2026
@mrForza mrForza requested a review from Serpentian July 2, 2026 11:57
@mrForza mrForza assigned Gerold103 and Serpentian and unassigned Gerold103 and Serpentian Jul 2, 2026
Comment thread vshard/router/init.lua Outdated
Comment on lines -794 to -796
end
if mode == MAP_CALLRW_FULL then
replicasets_to_wait = replicasets_all
else
for rs_id, _ in pairs(grouped_buckets) do
table.insert(replicasets_to_wait, replicasets_all[rs_id])
end
else
replicasets_to_wait = replicasets_all

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: the diff would have been smaller, if you would change the if to mode ~= MAP_CALLRW_FULL, and leave else unchanged.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Comment thread vshard/router/init.lua Outdated
Comment on lines +823 to +828
res, err = res[1], res[2]
if res == nil then
return nil, err, id
if res[1] == nil then
return nil, res[2], id

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary diff.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Comment thread vshard/router/init.lua
Comment on lines +902 to +904
for rs_id, future in pairs(future_buckets) do
results[rs_id] = future:result()[1]
end

@Gerold103 Gerold103 Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it was decided to reuse the old function storage_ref_make_with_buckets() with a new option force, it means this code will work on old storages too, but they will ignore the force option. You need to check for is_done result to make sure the ref is actually done. Otherwise you have the same problem as before when some storage isn't upgraded, and all its mentioned buckets were moved.

Or do we rely on total being not set on old storages, and then it fails further below? Still, at least an assertion of is_done would be a good enough safe guard.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or do we rely on total being not set on old storages, and then it fails further below?

Yes, the storage_ref_make_with_bucket on old storage can't return total amount of buckets and on router we get UNSUPPORTED error when we find out that there is no total field in the result.

Still, at least an assertion of is_done would be a good enough safe guard.

I don't think that is_done assertion can help us. When we do map_callrw on upgraded storages, we can't nicely determine which result comes from storage_ref and which comes from storage_ref_make_with_bucket because before that we unify results so that they all have a total field. And it may be that one res has total and is_done fields (storage_ref_make_with_buckets) and another has total but doesn't has is_done field (storage_ref).

We can write a small crutch)

diff --git a/vshard/router/init.lua b/vshard/router/init.lua
index bf3da49..087ef7d 100644
--- a/vshard/router/init.lua
+++ b/vshard/router/init.lua
@@ -914,6 +914,9 @@ local function router_ref_storage_all(router, bucket_ids, timeout, rid)
                                 'full map_callrw with split args')
             goto fail
         end
+        if type(res.moved) == 'table' then
+            assert(res.is_done)
+        end
         bucket_count = bucket_count + res.total
     end
     bucket_ids = router_map_callrw_process_moved(router, results)

Or we can modify storage_ref_* functions so that they return its' identification. E.g. storage_ref will return {name = 'storage_ref', total = ...}

Comment thread vshard/storage/init.lua Outdated
--
local function storage_ref_make_with_buckets(rid, timeout, bucket_ids)
local function storage_ref_make_with_buckets(rid, timeout, bucket_ids, opts)
local force = opts and opts.force or false

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for or false. The effect will be the same anyway.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@mrForza mrForza assigned Gerold103 and unassigned Gerold103 and Serpentian Jul 3, 2026
This patch introduces a new way of `map_callrw` execution by which we can
pass some arguments to all storages and split buckets' arguments to those
storages that have at least one bucket of `bucket_ids`. To achieve this we
introduce a new string option - `mode` to `map_callrw` api.

Also we change the logic of `router_ref_storage_all` ref function. Firstly
we ref all storages and get back an amount of "moved" buckets according
to the previously built router's cache. Then if there are no "moved"
buckets we accumulate and check total amount of buckets on all storages
and finish map_callrw ref stage. Otherwise, if there are some "moved"
buckets we perform the second network hop by checking on which replicasets
do the remaining "moved" buckets reside on.

Closes tarantool#559

@TarantoolBot document

Title: vshard: `mode` option for `router.map_callrw()`

This string option regulates on which storages the user function will be
executed via `map_callrw`. Possible values:
1) mode = 'partial'. In this mode user function will be executed on
   storages that have at least one bucket of 'bucket_ids'. The
   'bucket_ids' option can be presented in two ways: like a numeric array
   of buckets' ids or like a map of buckets' arguments. In first one user
   function will only receive args, in second one it will additionally
   receive buckets' arguments.
2) mode = 'full'. In this mode user function will be executed with args on
   all storages in cluster. Also we can pass 'bucket_ids' only like a map
   of bucket's arguments - `bucket_ids = {id = {args}, id = {args}, ...}`.
   In this way the user function will additionally receive buckets'
   arguments on those storages that have at least one bucket of
   `bucket_ids`.

If we didn't specify the 'mode' option, then it is set based on
'bucket_ids' option for backward compatibility. If 'bucket_ids' is
presented, the mode will be 'partial' otherwise 'full'.

Also now `map_callrw` ends with error in cases when we
1) pass `mode = 'full'` and a numeric array of buckets' ids -
   `bucket_ids = {id, id, ...}`
2) pass `mode = 'partial'` and `bucket_ids = nil`.
@mrForza mrForza force-pushed the gh-559-full-map-call-rw-with-split-args branch from e54c676 to 23abb9a Compare July 3, 2026 09:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Full map_callrw with split args

4 participants