Skip to content

Commit 2ff3351

Browse files
committed
sharding: auto-fill bucket_id when PK is sharding index
Vinyl deployments often live with a single index (bucket_id, …), so CRUD can derive bucket_id from the rest of the key. Primary-key calls still demanded an explicit bucket_id though: the router sent box.NULL and storage rejected the key type. Introduce sharding.fill_bucket_id_pk(), which checks that the sharding index is the primary index and patches the first key part when bucket_id arrives as box.NULL.
1 parent dcac828 commit 2ff3351

File tree

9 files changed

+391
-0
lines changed

9 files changed

+391
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
1111
* Support for `vshard`'s `request_timeout` parameter for calls with `mode = 'read'`
1212

1313
### Changed
14+
* Auto-fill `bucket_id` for primary-key CRUD requests (get/update/delete) when the sharding index is the primary index and `bucket_id` is passed as `box.NULL`.
1415

1516
### Fixed
1617

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,22 @@ require any actions from user side. However, for operations that accepts
236236
tuple/object bucket ID can be specified as tuple/object field as well as
237237
`opts.bucket_id` value.
238238

239+
**Primary key requests without explicit `bucket_id`**
240+
241+
If the sharding index (with `bucket_id` as the first field) also acts as the
242+
primary index, CRUD can populate the missing `bucket_id` automatically. You may
243+
pass `box.NULL` instead of the first key part, and the router will compute the
244+
`bucket_id` from the remaining primary key fields:
245+
246+
```lua
247+
local res, err = crud.get('customers', {box.NULL, customer_id})
248+
local res, err = crud.update('customers', {box.NULL, customer_id}, {{'=', 'name', 'Jane'}})
249+
local res, err = crud.delete('customers', {box.NULL, customer_id})
250+
```
251+
252+
The behaviour works as long as the router has up-to-date sharding metadata and
253+
the primary index layout is `(bucket_id, ...)`.
254+
239255
Starting from 0.10.0 users who don't want to use primary key as a sharding key
240256
may set custom sharding key definition as a part of [DDL
241257
schema](https://github.com/tarantool/ddl#input-data-format) or insert manually

crud/common/sharding/init.lua

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,42 @@ function sharding.key_get_bucket_id(vshard_router, space_name, key, specified_bu
5555
return { bucket_id = vshard_router:bucket_id_strcrc32(key) }
5656
end
5757

58+
local function bucket_id_starts_pk(space)
59+
dev_checks('table')
60+
61+
local primary_index_parts = space.index[0].parts
62+
63+
local bucket_id_fieldno = utils.get_bucket_id_fieldno(space)
64+
65+
return primary_index_parts[1].fieldno == bucket_id_fieldno
66+
end
67+
68+
--- Fills the sharding (bucket_id) index first field with the computed bucket_id
69+
-- when the sharding index is the primary index and bucket_id is box.NULL.
70+
--
71+
-- @param table space
72+
-- Space reference used to detect sharding index layout.
73+
-- @param ?table key
74+
-- Key for the sharding index represented as a Lua table.
75+
-- @param number bucket_id
76+
-- Bucket id associated with the key.
77+
function sharding.fill_bucket_id_pk(space, key, bucket_id)
78+
dev_checks('table', '?', 'number')
79+
80+
if type(key) ~= 'table' then
81+
return
82+
end
83+
84+
if not bucket_id_starts_pk(space) then
85+
return
86+
end
87+
88+
local first_key_part = key[1]
89+
if first_key_part == nil or first_key_part == box.NULL then
90+
key[1] = bucket_id
91+
end
92+
end
93+
5894
function sharding.tuple_get_bucket_id(vshard_router, tuple, space, specified_bucket_id)
5995
if specified_bucket_id ~= nil then
6096
return { bucket_id = specified_bucket_id }

crud/delete.lua

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ local function call_delete_on_router(vshard_router, space_name, key, opts)
112112
return nil, err
113113
end
114114

115+
-- When the sharding index (bucket_id) is the primary index, bucket_id can be passed as box.NULL.
116+
sharding.fill_bucket_id_pk(space, key, bucket_id_data.bucket_id)
117+
115118
local delete_on_storage_opts = {
116119
sharding_func_hash = bucket_id_data.sharding_func_hash,
117120
sharding_key_hash = sharding_key_hash,

crud/get.lua

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ local function call_get_on_router(vshard_router, space_name, key, opts)
110110
return nil, err
111111
end
112112

113+
-- When the sharding index (bucket_id) is the primary index, bucket_id can be passed as box.NULL.
114+
sharding.fill_bucket_id_pk(space, key, bucket_id_data.bucket_id)
115+
113116
local get_on_storage_opts = {
114117
sharding_func_hash = bucket_id_data.sharding_func_hash,
115118
sharding_key_hash = sharding_key_hash,

crud/update.lua

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,9 @@ local function call_update_on_router(vshard_router, space_name, key, user_operat
144144
return nil, err
145145
end
146146

147+
-- When the sharding index (bucket_id) is the primary index, bucket_id can be passed as box.NULL.
148+
sharding.fill_bucket_id_pk(space, key, bucket_id_data.bucket_id)
149+
147150
local update_on_storage_opts = {
148151
sharding_func_hash = bucket_id_data.sharding_func_hash,
149152
sharding_key_hash = sharding_key_hash,
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#!/usr/bin/env tarantool
2+
3+
require('strict').on()
4+
_G.is_initialized = function() return false end
5+
6+
local fio = require('fio')
7+
local log = require('log')
8+
local errors = require('errors')
9+
local cartridge = require('cartridge')
10+
11+
if package.setsearchroot ~= nil then
12+
package.setsearchroot()
13+
else
14+
package.path = package.path .. debug.sourcedir() .. "/?.lua;"
15+
end
16+
17+
local root = fio.dirname(fio.dirname(fio.dirname(debug.sourcedir())))
18+
package.path = package.path .. root .. "/?.lua;"
19+
20+
package.preload['customers-storage'] = function()
21+
return {
22+
role_name = 'customers-storage',
23+
init = require('storage').init,
24+
}
25+
end
26+
27+
local ok, err = errors.pcall('CartridgeCfgError', cartridge.cfg, {
28+
advertise_uri = 'localhost:3301',
29+
http_port = 8081,
30+
bucket_count = 3000,
31+
roles = {
32+
'customers-storage',
33+
'cartridge.roles.crud-router',
34+
'cartridge.roles.crud-storage',
35+
},
36+
})
37+
38+
if not ok then
39+
log.error('%s', err)
40+
os.exit(1)
41+
end
42+
43+
_G.is_initialized = cartridge.is_healthy
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
local helper = require('test.helper')
2+
3+
return {
4+
init = helper.wrap_schema_init(function()
5+
local engine = os.getenv('ENGINE') or 'memtx'
6+
local customers_space = box.schema.space.create('customers', {
7+
format = {
8+
{name = 'id', type = 'unsigned'},
9+
{name = 'bucket_id', type = 'unsigned'},
10+
{name = 'name', type = 'string'},
11+
{name = 'age', type = 'number'},
12+
},
13+
if_not_exists = true,
14+
engine = engine,
15+
})
16+
customers_space:create_index('bucket_id', {
17+
parts = { {field = 'bucket_id'}, {field = 'id'} },
18+
if_not_exists = true,
19+
})
20+
21+
-- https://github.com/tarantool/migrations/blob/a7c31a17f6ac02d4498b4203c23e495856861444/migrator/utils.lua#L35-L53
22+
if box.space._ddl_sharding_key == nil then
23+
local sharding_space = box.schema.space.create('_ddl_sharding_key', {
24+
format = {
25+
{name = 'space_name', type = 'string', is_nullable = false},
26+
{name = 'sharding_key', type = 'array', is_nullable = false}
27+
},
28+
if_not_exists = true,
29+
})
30+
sharding_space:create_index(
31+
'space_name', {
32+
type = 'TREE',
33+
unique = true,
34+
parts = {{'space_name', 'string', is_nullable = false}},
35+
if_not_exists = true,
36+
}
37+
)
38+
end
39+
box.space._ddl_sharding_key:replace{'customers', {'id'}}
40+
end),
41+
wait_until_ready = helper.wait_schema_init,
42+
}

0 commit comments

Comments
 (0)