Skip to content

Commit e4f0d21

Browse files
crud: fix explicit bucket_id in *_many operations
In case `insert_many`, `insert_object_many`, `replace_many`, `replace_object_many`, `upsert_many` or `upsert_object_many` has been called on a space with custom sharding info and every tuple/object in the request had `bucket_id`, `ShardingHashMismatchError` has been returned even though there are no issues with sharding info. For example, some users met this issue while working with tt-ee `tt crud import` command. The reason is as follows. To ensure sharding info consistency between the storage and the router, some metainfo is calculated for a request in case bucket_id is generated. In case no bucket_id is generated, no sharding info is passed and consistency check is skipped (since it is not needed). Before this patch, `*_many` operations haven't skipped consistency check when it was expected due to improper `skip_sharding_hash_check` flag setup. Closes #437
1 parent 435b61e commit e4f0d21

File tree

7 files changed

+196
-6
lines changed

7 files changed

+196
-6
lines changed

CHANGELOG.md

+8
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
66
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
77

8+
## Unreleased
9+
10+
### Fixed
11+
* `insert_many`, `insert_object_many`, `replace_many`, `replace_object_many`,
12+
`upsert_many`, `upsert_object_many` operations no longer fail with
13+
`ShardingHashMismatchError` if a space has custom sharding info and
14+
every tuple/object in the request has `bucket_id` set (#437).
15+
816
## [1.5.1] - 27-04-24
917

1018
### Added

crud/common/sharding/init.lua

+4-6
Original file line numberDiff line numberDiff line change
@@ -222,13 +222,11 @@ function sharding.split_tuples_by_replicaset(vshard_router, tuples, space, opts)
222222

223223
local batches = {}
224224

225-
local sharding_func_hash
226-
local sharding_key_hash
227-
local skip_sharding_hash_check
228-
local sharding_data
229-
local err
225+
local sharding_func_hash = nil
226+
local sharding_key_hash = nil
227+
local skip_sharding_hash_check = true
230228
for i, tuple in ipairs(tuples) do
231-
sharding_data, err = sharding.tuple_set_and_return_bucket_id(vshard_router, tuple, space)
229+
local sharding_data, err = sharding.tuple_set_and_return_bucket_id(vshard_router, tuple, space)
232230
if err ~= nil then
233231
return nil, BucketIDError:new("Failed to get bucket ID: %s", err)
234232
end

test/entrypoint/srv_batch_operations/storage.lua

+40
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,46 @@ return {
4747
unique = true,
4848
if_not_exists = true,
4949
})
50+
51+
local customers_sharded_by_age_space = box.schema.space.create('customers_sharded_by_age', {
52+
format = {
53+
{name = 'id', type = 'unsigned'},
54+
{name = 'bucket_id', type = 'unsigned'},
55+
{name = 'name', type = 'string'},
56+
{name = 'age', type = 'number'},
57+
},
58+
if_not_exists = true,
59+
engine = engine,
60+
})
61+
customers_sharded_by_age_space:create_index('id', {
62+
parts = { {field = 'id'} },
63+
if_not_exists = true,
64+
})
65+
customers_sharded_by_age_space:create_index('bucket_id', {
66+
parts = { {field = 'bucket_id'} },
67+
unique = false,
68+
if_not_exists = true,
69+
})
70+
71+
-- https://github.com/tarantool/migrations/blob/a7c31a17f6ac02d4498b4203c23e495856861444/migrator/utils.lua#L35-L53
72+
if box.space._ddl_sharding_key == nil then
73+
local sharding_space = box.schema.space.create('_ddl_sharding_key', {
74+
format = {
75+
{name = 'space_name', type = 'string', is_nullable = false},
76+
{name = 'sharding_key', type = 'array', is_nullable = false}
77+
},
78+
if_not_exists = true,
79+
})
80+
sharding_space:create_index(
81+
'space_name', {
82+
type = 'TREE',
83+
unique = true,
84+
parts = {{'space_name', 'string', is_nullable = false}},
85+
if_not_exists = true,
86+
}
87+
)
88+
end
89+
box.space._ddl_sharding_key:replace{'customers_sharded_by_age', {'age'}}
5090
end),
5191
wait_until_ready = helper.wait_schema_init,
5292
}

test/integration/insert_many_test.lua

+26
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ local t = require('luatest')
22
local crud = require('crud')
33

44
local helpers = require('test.helper')
5+
local write_scenario = require('test.integration.write_scenario')
56

67
local batching_utils = require('crud.common.batching_utils')
78

@@ -19,6 +20,7 @@ end)
1920

2021
pgroup.before_each(function(g)
2122
helpers.truncate_space_on_cluster(g.cluster, 'customers')
23+
helpers.truncate_space_on_cluster(g.cluster, 'customers_sharded_by_age')
2224
end)
2325

2426
pgroup.test_non_existent_space = function(g)
@@ -2051,3 +2053,27 @@ pgroup.test_zero_objects = function(g)
20512053
t.assert_str_contains(errs[1].err, "At least one object expected")
20522054
t.assert_equals(result, nil)
20532055
end
2056+
2057+
pgroup.test_gh_437_insert_many_explicit_bucket_ids = function(g)
2058+
write_scenario.gh_437_many_explicit_bucket_ids(g, 'insert_many')
2059+
end
2060+
2061+
pgroup.test_gh_437_insert_many_partial_explicit_bucket_ids = function(g)
2062+
write_scenario.gh_437_many_explicit_bucket_ids(
2063+
g,
2064+
'insert_many',
2065+
{partial_explicit_bucket_ids = true}
2066+
)
2067+
end
2068+
2069+
pgroup.test_gh_437_insert_object_many_explicit_bucket_ids = function(g)
2070+
write_scenario.gh_437_many_explicit_bucket_ids(g, 'insert_object_many', {objects = true})
2071+
end
2072+
2073+
pgroup.test_gh_437_insert_object_many_partial_explicit_bucket_ids = function(g)
2074+
write_scenario.gh_437_many_explicit_bucket_ids(
2075+
g,
2076+
'insert_object_many',
2077+
{objects = true, partial_explicit_bucket_ids = true}
2078+
)
2079+
end

test/integration/replace_many_test.lua

+26
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ local t = require('luatest')
33
local crud = require('crud')
44

55
local helpers = require('test.helper')
6+
local write_scenario = require('test.integration.write_scenario')
67

78
local batching_utils = require('crud.common.batching_utils')
89

@@ -20,6 +21,7 @@ end)
2021

2122
pgroup.before_each(function(g)
2223
helpers.truncate_space_on_cluster(g.cluster, 'developers')
24+
helpers.truncate_space_on_cluster(g.cluster, 'customers_sharded_by_age')
2325
end)
2426

2527
pgroup.test_non_existent_space = function(g)
@@ -2062,3 +2064,27 @@ pgroup.test_zero_objects = function(g)
20622064
t.assert_str_contains(errs[1].err, "At least one object expected")
20632065
t.assert_equals(result, nil)
20642066
end
2067+
2068+
pgroup.test_gh_437_replace_many_explicit_bucket_ids = function(g)
2069+
write_scenario.gh_437_many_explicit_bucket_ids(g, 'replace_many')
2070+
end
2071+
2072+
pgroup.test_gh_437_replace_many_partial_explicit_bucket_ids = function(g)
2073+
write_scenario.gh_437_many_explicit_bucket_ids(
2074+
g,
2075+
'replace_many',
2076+
{partial_explicit_bucket_ids = true}
2077+
)
2078+
end
2079+
2080+
pgroup.test_gh_437_replace_object_many_explicit_bucket_ids = function(g)
2081+
write_scenario.gh_437_many_explicit_bucket_ids(g, 'replace_object_many', {objects = true})
2082+
end
2083+
2084+
pgroup.test_gh_437_replace_object_many_partial_explicit_bucket_ids = function(g)
2085+
write_scenario.gh_437_many_explicit_bucket_ids(
2086+
g,
2087+
'replace_object_many',
2088+
{objects = true, partial_explicit_bucket_ids = true}
2089+
)
2090+
end

test/integration/upsert_many_test.lua

+30
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
local t = require('luatest')
22

33
local helpers = require('test.helper')
4+
local write_scenario = require('test.integration.write_scenario')
45

56
local batching_utils = require('crud.common.batching_utils')
67

@@ -18,6 +19,7 @@ end)
1819

1920
pgroup.before_each(function(g)
2021
helpers.truncate_space_on_cluster(g.cluster, 'customers')
22+
helpers.truncate_space_on_cluster(g.cluster, 'customers_sharded_by_age')
2123
end)
2224

2325
pgroup.test_non_existent_space = function(g)
@@ -2063,3 +2065,31 @@ pgroup.test_zero_objects = function(g)
20632065
t.assert_str_contains(errs[1].err, "At least one object expected")
20642066
t.assert_equals(result, nil)
20652067
end
2068+
2069+
pgroup.test_gh_437_upsert_many_explicit_bucket_ids = function(g)
2070+
write_scenario.gh_437_many_explicit_bucket_ids(g, 'upsert_many', {upsert = true})
2071+
end
2072+
2073+
pgroup.test_gh_437_upsert_many_partial_explicit_bucket_ids = function(g)
2074+
write_scenario.gh_437_many_explicit_bucket_ids(
2075+
g,
2076+
'upsert_many',
2077+
{upsert = true, partial_explicit_bucket_ids = true}
2078+
)
2079+
end
2080+
2081+
pgroup.test_gh_437_upsert_object_many_explicit_bucket_ids = function(g)
2082+
write_scenario.gh_437_many_explicit_bucket_ids(
2083+
g,
2084+
'upsert_object_many',
2085+
{upsert = true, objects = true}
2086+
)
2087+
end
2088+
2089+
pgroup.test_gh_437_upsert_object_many_partial_explicit_bucket_ids = function(g)
2090+
write_scenario.gh_437_many_explicit_bucket_ids(
2091+
g,
2092+
'upsert_object_many',
2093+
{upsert = true, objects = true, partial_explicit_bucket_ids = true}
2094+
)
2095+
end

test/integration/write_scenario.lua

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
local t = require('luatest')
2+
local checks = require('checks')
3+
4+
-- Scenario is for 'srv_batch_operations' entrypoint.
5+
local function gh_437_many_explicit_bucket_ids(cg, operation, opts)
6+
checks('table', 'string', {
7+
objects = '?boolean',
8+
upsert = '?boolean',
9+
partial_explicit_bucket_ids = '?boolean',
10+
})
11+
12+
opts = opts or {}
13+
14+
local rows = {
15+
{1, 1, 'Kumiko', 18},
16+
{2, 2, 'Reina', 19},
17+
{3, 3, 'Shuuichi', 18},
18+
}
19+
20+
if opts.partial_explicit_bucket_ids then
21+
rows[2][2] = box.NULL
22+
end
23+
24+
local objects = {}
25+
for k, v in ipairs(rows) do
26+
objects[k] = {id = v[1], bucket_id = v[2], name = v[3], age = v[4]}
27+
end
28+
29+
local data
30+
if opts.objects then
31+
data = objects
32+
else
33+
data = rows
34+
end
35+
36+
if opts.upsert then
37+
local update_operations = {}
38+
for k, v in ipairs(data) do
39+
data[k] = {v, update_operations}
40+
end
41+
end
42+
43+
local args = {'customers_sharded_by_age', data}
44+
45+
local result, errs = cg.router:call('crud.' .. operation, args)
46+
t.assert_equals(errs, nil)
47+
48+
local result_rows = table.deepcopy(rows)
49+
if opts.partial_explicit_bucket_ids then
50+
result_rows[2][2] = 1325
51+
end
52+
if opts.upsert then
53+
-- upsert never return anything.
54+
t.assert_equals(result.rows, nil)
55+
else
56+
t.assert_items_equals(result.rows, result_rows)
57+
end
58+
end
59+
60+
return {
61+
gh_437_many_explicit_bucket_ids = gh_437_many_explicit_bucket_ids,
62+
}

0 commit comments

Comments
 (0)