Skip to content

Commit

Permalink
connpool: prefer_ro and prefer_rw for call()
Browse files Browse the repository at this point in the history
This patch introduces "prefer_ro" and "prefer_rw" values for the "mode"
option for the "call()" function.

Closes #9930

@TarantoolBot document
Title: The `mode` option for `call()` and `filter()`

The new `mode` option is now supported by the `call()` and `filter()`
functions from the `experimental.connpool` module. This option allows to
filter candidates based on their `read-only` status.

The `filter()` function supports three values of the `mode` option:
1) `nil` means that the `read_only` status of the instance is not
   checked;
2) `ro` means that only instances with `read_only == true` are
   considered;
3) `rw` means that only instances with `read_only == false` are
   considered.

The `call()` function supports five values of the `mode` option:
1) `nil` means that the `read_only` status of the instance is not
   checked when instance is selected to execute `call()`;
2) `ro` means that only instances with `read_only == true` are
   considered when instance is selected to execute `call()`;
3) `rw` means that only instances with `read_only == false` are
   considered when instance is selected to execute `call()`.
4) `prefer_ro` means that `call()` will only be executed on instances
   with `read_only == false` if it is not possible to execute it on
   instances with `read_only == true`;
5) `prefer_rw` means that `call()` will only be executed on instances
   with `read_only == true` if it is not possible to execute it on
   instances with `read_only == false`.

Note that if this option is not `nil`, a connection will be attempted to
each instance in the config if a connection does not exist. This means
that any of these functions can potentially block for a maximum of
`<number of instances> * 10` seconds.
  • Loading branch information
ImeevMA committed Apr 12, 2024
1 parent c0a4313 commit 60fdffb
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 21 deletions.
4 changes: 4 additions & 0 deletions changelogs/unreleased/gh-9930-mode-option.md
@@ -0,0 +1,4 @@
## feature/experimental

* Introduced the `mode` option for the `filter()` and `call()` functions
in the `experimental.connpool` module (gh-9930).
73 changes: 53 additions & 20 deletions src/box/lua/experimental/connpool.lua
Expand Up @@ -207,29 +207,60 @@ local function get_connection(opts)
return nil, "no candidates are available with these conditions"
end

if opts.prefer_local ~= false then
local candidate_idx = nil
for n, candidate in ipairs(candidates) do
if candidate == box.info.name then
candidate_idx = n
local conn = connect(box.info.name, {wait_connected = false})
if conn:wait_connected() then
return conn
end
break
-- Initialize the weight of each candidate.
local weights = {}
if opts.mode == 'prefer_rw' or opts.mode == 'prefer_ro' then
candidates = connect_to_candidates(candidates)
end
for _, instance_name in pairs(candidates) do
weights[instance_name] = 0
end

-- Increase weights of candidates preferred by mode.
if opts.mode == 'prefer_rw' or opts.mode == 'prefer_ro' then
local mode = opts.mode == 'prefer_ro' and 'ro' or 'rw'
local weight_mode = 2
for _, instance_name in pairs(candidates) do
local conn = connections[instance_name]
assert(conn ~= nil)
if conn:mode() == mode then
weights[instance_name] = weights[instance_name] + weight_mode
end
end
if candidate_idx ~= nil then
table.remove(candidates, candidate_idx)
end

-- Increase weight of local candidate.
if opts.prefer_local ~= false then
local weight_local = 1
if weights[box.info.name] ~= nil then
weights[box.info.name] = weights[box.info.name] + weight_local
end
end

while #candidates > 0 do
local n = math.random(#candidates)
local instance_name = table.remove(candidates, n)
local conn = connect(instance_name, {wait_connected = false})
if conn:wait_connected() then
return conn
-- Select candidate by weight.
while next(weights) ~= nil do
local max_weight = 0
for _, weight in pairs(weights) do
if weight > max_weight then
max_weight = weight
end
end
local preferred_candidates = {}
for instance_name, weight in pairs(weights) do
if weight == max_weight then
table.insert(preferred_candidates, instance_name)
end
end
while #preferred_candidates > 0 do
local n = math.random(#preferred_candidates)
local instance_name = table.remove(preferred_candidates, n)
local conn = connect(instance_name, {wait_connected = false})
if conn:wait_connected() then
return conn
end
end
for _, instance_name in pairs(preferred_candidates) do
weights[instance_name] = nil
end
end
return nil, "connection to candidates failed"
Expand All @@ -249,8 +280,10 @@ local function call(func_name, args, opts)
is_async = '?boolean',
})
opts = opts or {}
if opts.mode ~= nil and opts.mode ~= 'ro' and opts.mode ~= 'rw' then
local msg = 'Expected nil, "ro" or "rw", got "%s"'
if opts.mode ~= nil and opts.mode ~= 'ro' and opts.mode ~= 'rw' and
opts.mode ~= 'prefer_ro' and opts.mode ~= 'prefer_rw' then
local msg = 'Expected nil, "ro", "rw", "prefer_ro" or "prefer_rw", ' ..
'got "%s"'
error(msg:format(opts.mode), 0)
end

Expand Down
40 changes: 39 additions & 1 deletion test/config-luatest/rpc_test.lua
Expand Up @@ -451,7 +451,9 @@ g.test_call_mode = function(g)
mode: rw
labels:
l1: 'first'
instance-002: {}
instance-002:
labels:
l2: 'second'
replicaset-002:
replication:
failover: manual
Expand All @@ -461,6 +463,7 @@ g.test_call_mode = function(g)
instance-004:
labels:
l1: 'first'
l2: 'second'
]]
treegen.write_script(dir, 'config.yaml', config)

Expand Down Expand Up @@ -512,6 +515,41 @@ g.test_call_mode = function(g)
}
local exp_list = {'instance-002', 'instance-004'}
t.assert_items_include(exp_list, {connpool.call('f', nil, opts)})

opts = {
labels = {l1 = 'first'},
mode = 'prefer_ro',
}
t.assert_equals(connpool.call('f', nil, opts), 'instance-004')

opts = {
labels = {l1 = 'first'},
mode = 'prefer_rw',
}
t.assert_equals(connpool.call('f', nil, opts), 'instance-001')

-- Make sure 'prefer_*' mode will execute call on non-preferred
-- instance, if there is no preferred instance.
opts = {
labels = {l2 = 'second'},
mode = 'prefer_rw',
}
exp_list = {'instance-002', 'instance-004'}
t.assert_items_include(exp_list, {connpool.call('f', nil, opts)})

-- Make sure that "prefer_local" has a lower priority than the
-- "prefer_*" mode.
opts = {
labels = {l1 = 'first'},
mode = 'prefer_rw',
prefer_local = true,
}
t.assert_equals(connpool.call('f', nil, opts), 'instance-001')

local exp_err = 'Expected nil, "ro", "rw", "prefer_ro" or ' ..
'"prefer_rw", got "something"'
opts = {mode = 'something'}
t.assert_error_msg_equals(exp_err, connpool.call, 'f', nil, opts)
end

g.server_1:exec(check)
Expand Down

0 comments on commit 60fdffb

Please sign in to comment.