Skip to content

Commit 3dccab0

Browse files
committed
Use DDL sharding key to calculate bucket_id
Previously by default primary key is used to calculate bucket id. CRUD allows to automatically calculate `bucket_id` based on primary key. However DDL users may set sharding key in DDL schema and now CRUD allows to use that sharding key to calculate bucket id. Closes #166
1 parent ea7560d commit 3dccab0

File tree

10 files changed

+539
-8
lines changed

10 files changed

+539
-8
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
1616
* `crud.len()` function to calculate the number of tuples
1717
in the space for memtx engine and calculate the maximum
1818
approximate number of tuples in the space for vinyl engine.
19+
* CRUD operations automatically calculate bucket id using sharding
20+
key specified with DDL schema.
1921

2022
## [0.8.0] - 02-07-21
2123

@@ -26,6 +28,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
2628

2729
### Added
2830

31+
* Support calculating `bucket_id` based on `ddl.sharding_key`.
2932
* Added jsonpath indexes support for queries
3033
* `tuple-merger` module updated to 0.0.2
3134

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ crud.unflatten_rows(res.rows, res.metadata)
5050

5151
* A space should have a format.
5252
* By default, `bucket_id` is computed as `vshard.router.bucket_id_strcrc32(key)`,
53-
where `key` is the primary key value.
53+
where `key` is the primary key value or a [sharding key set by DDL schema](https://github.com/tarantool/ddl#input-data-format).
5454
Custom bucket ID can be specified as `opts.bucket_id` for each operation.
5555
For operations that accepts tuple/object bucket ID can be specified as
5656
tuple/object field as well as `opts.bucket_id` value.

crud/common/sharding.lua

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
11
local vshard = require('vshard')
22
local errors = require('errors')
3+
local ddl, err = pcall(require, 'ddl')
4+
if ddl == true then
5+
ddl = err
6+
end
37

48
local BucketIDError = errors.new_class("BucketIDError", {capture_stack = false})
9+
local ShardingKeyError = errors.new_class("ShardingKeyError", {capture_stack = false})
510

11+
local dev_checks = require('crud.common.dev_checks')
612
local utils = require('crud.common.utils')
713

14+
-- TODO: invalidate ddl_schema_cache
15+
local ddl_schema_cache = nil
16+
local sharding_key_in_primary_index = {}
17+
818
local sharding = {}
919

1020
function sharding.key_get_bucket_id(key, specified_bucket_id)
@@ -20,7 +30,20 @@ function sharding.tuple_get_bucket_id(tuple, space, specified_bucket_id)
2030
return specified_bucket_id
2131
end
2232

23-
local key = utils.extract_key(tuple, space.index[0].parts)
33+
local primary_index = space.index[0]
34+
local key
35+
local sharding_key = sharding.get_ddl_sharding_key(space.name)
36+
if sharding_key ~= nil then
37+
local sharding_keys_fieldnos = utils.get_keys_fieldnos(space:format(), sharding_key)
38+
if sharding_keys_fieldnos ~= nil then
39+
key = utils.extract_sharding_key(tuple, sharding_keys_fieldnos)
40+
end
41+
end
42+
43+
if key == nil then
44+
key = utils.extract_key(tuple, primary_index.parts)
45+
end
46+
2447
return sharding.key_get_bucket_id(key)
2548
end
2649

@@ -51,4 +74,79 @@ function sharding.tuple_set_and_return_bucket_id(tuple, space, specified_bucket_
5174
return bucket_id
5275
end
5376

77+
-- Get sharding key (actually field names) using DDL module.
78+
function sharding.get_ddl_sharding_key(space_name)
79+
dev_checks('string')
80+
if ddl == false then
81+
return nil
82+
end
83+
if ddl_schema_cache == nil then
84+
ddl_schema_cache = ddl.get_schema()
85+
end
86+
assert(type(ddl_schema_cache.spaces), 'table')
87+
local space_schema = ddl_schema_cache.spaces[space_name]
88+
if space_schema == nil or
89+
space_schema.sharding_key == nil then
90+
return nil
91+
end
92+
93+
return space_schema.sharding_key
94+
end
95+
96+
-- Make sure fields used in sharding key are present in primary index.
97+
-- TODO: invalidate table sharding_key_in_primary_index
98+
function sharding.is_sharding_key_in_primary_index(space_name, primary_index, fieldnos)
99+
dev_checks('string', 'table', 'table')
100+
if sharding_key_in_primary_index[space_name] == nil then
101+
local primary_index_fieldno = utils.get_index_fieldno_map(primary_index)
102+
for _, fieldno in ipairs(fieldnos) do
103+
if primary_index_fieldno[fieldno] == false then
104+
sharding_key_in_primary_index[space_name] = false
105+
break
106+
end
107+
end
108+
end
109+
110+
return sharding_key_in_primary_index[space_name]
111+
end
112+
113+
-- Build an array with sharding key values.
114+
-- Returns a table with sharding keys values or nil.
115+
local function build_sharding_key(key, index_parts, sharding_key_fieldno_map)
116+
dev_checks('table', 'table', 'table')
117+
local sharding_key = {}
118+
for i, k in ipairs(key) do
119+
local fieldno = index_parts[i].fieldno
120+
if sharding_key_fieldno_map[fieldno] == true then
121+
table.insert(sharding_key, k)
122+
end
123+
end
124+
125+
return sharding_key
126+
end
127+
128+
-- Build sharding key using DDL sharding key.
129+
-- Returns a table with sharding key or pair of nil with error.
130+
function sharding.build_ddl_sharding_key(space_obj, sharding_key, ddl_sharding_key, bucket_id)
131+
dev_checks('table', '?', 'table', 'number|nil')
132+
local primary_index = space_obj.index[0]
133+
local space_format = space_obj:format()
134+
local space_name = space_obj.name
135+
136+
local sharding_keys_fieldnos = utils.get_keys_fieldnos(space_format, ddl_sharding_key)
137+
if sharding_keys_fieldnos == nil then
138+
return nil, ShardingKeyError:new("Sharding key(s) not found in a space format")
139+
end
140+
if sharding.is_sharding_key_in_primary_index(space_name, primary_index, sharding_keys_fieldnos) == false and
141+
bucket_id == nil then
142+
return nil, ShardingKeyError:new("Sharding key is missed in primary index, specify bucket_id")
143+
end
144+
if type(sharding_key) ~= 'table' then
145+
sharding_key = {sharding_key}
146+
end
147+
148+
local sharding_key_fieldno_map = utils.get_keys_fieldno_map(space_format, ddl_sharding_key)
149+
return build_sharding_key(sharding_key, primary_index.parts, sharding_key_fieldno_map)
150+
end
151+
54152
return sharding

crud/common/utils.lua

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,15 @@ function utils.unflatten(tuple, space_format)
140140
return object
141141
end
142142

143+
function utils.extract_sharding_key(tuple, fieldnos)
144+
local key = {}
145+
for _, fieldno in ipairs(fieldnos) do
146+
key[fieldno] = tuple[fieldno]
147+
end
148+
149+
return key
150+
end
151+
143152
function utils.extract_key(tuple, key_parts)
144153
local key = {}
145154
for i, part in ipairs(key_parts) do
@@ -375,6 +384,68 @@ function utils.get_bucket_id_fieldno(space, shard_index_name)
375384
return bucket_id_index.parts[1].fieldno
376385
end
377386

387+
function utils.get_index_fieldno_map(index_obj)
388+
local t = {}
389+
for _, part in ipairs(index_obj.parts) do
390+
local fieldno = part.fieldno
391+
t[fieldno] = true
392+
end
393+
394+
return t
395+
end
396+
397+
function utils.get_format_fieldno_map(space_format)
398+
local t = {}
399+
for fieldno, field_format in ipairs(space_format) do
400+
t[field_format.name] = fieldno
401+
end
402+
403+
return t
404+
end
405+
406+
--- Get a map with fieldno of passed field's names.
407+
--
408+
-- @function get_keys_fieldno_map
409+
--
410+
-- @param table space_format
411+
-- A space format
412+
--
413+
-- @param keys
414+
-- A table with field names.
415+
--
416+
-- @return[1] table
417+
-- @return[2] nil
418+
--
419+
function utils.get_keys_fieldno_map(space_format, field_names)
420+
dev_checks('table', 'table')
421+
local t = {}
422+
local fieldno_map = utils.get_format_fieldno_map(space_format)
423+
for _, field_name in ipairs(field_names) do
424+
local fieldno = fieldno_map[field_name]
425+
if fieldno == nil then
426+
return nil
427+
end
428+
t[fieldno] = true
429+
end
430+
431+
return t
432+
end
433+
434+
function utils.get_keys_fieldnos(space_format, field_names)
435+
dev_checks('table', 'table')
436+
local t = {}
437+
local format_fieldno_map = utils.get_format_fieldno_map(space_format)
438+
for _, field_name in ipairs(field_names) do
439+
local fieldno = format_fieldno_map[field_name]
440+
if fieldno == nil then
441+
return nil
442+
end
443+
table.insert(t, fieldno)
444+
end
445+
446+
return t
447+
end
448+
378449
local uuid_t = ffi.typeof('struct tt_uuid')
379450
function utils.is_uuid(value)
380451
return ffi.istype(uuid_t, value)

crud/delete.lua

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,17 @@ local function call_delete_on_router(space_name, key, opts)
5555
key = key:totable()
5656
end
5757

58-
local bucket_id = sharding.key_get_bucket_id(key, opts.bucket_id)
58+
local ddl_sharding_key = sharding.get_ddl_sharding_key(space_name)
59+
local sharding_key = key
60+
if ddl_sharding_key ~= nil then
61+
local err
62+
sharding_key, err = sharding.build_ddl_sharding_key(space, sharding_key, ddl_sharding_key, opts.bucket_id)
63+
if sharding_key == nil then
64+
return nil, err
65+
end
66+
end
67+
68+
local bucket_id = sharding.key_get_bucket_id(sharding_key, opts.bucket_id)
5969
local call_opts = {
6070
mode = 'write',
6171
timeout = opts.timeout,

crud/get.lua

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,17 @@ local function call_get_on_router(space_name, key, opts)
5858
key = key:totable()
5959
end
6060

61-
local bucket_id = sharding.key_get_bucket_id(key, opts.bucket_id)
61+
local ddl_sharding_key = sharding.get_ddl_sharding_key(space_name)
62+
local sharding_key = key
63+
if ddl_sharding_key ~= nil then
64+
local err
65+
sharding_key, err = sharding.build_ddl_sharding_key(space, sharding_key, ddl_sharding_key, opts.bucket_id)
66+
if sharding_key == nil then
67+
return nil, err
68+
end
69+
end
70+
71+
local bucket_id = sharding.key_get_bucket_id(sharding_key, opts.bucket_id)
6272
local call_opts = {
6373
mode = opts.mode or 'read',
6474
prefer_replica = opts.prefer_replica,

crud/select/plan.lua

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ local errors = require('errors')
33
local compare_conditions = require('crud.compare.conditions')
44
local utils = require('crud.common.utils')
55
local dev_checks = require('crud.common.dev_checks')
6+
local sharding = require('crud.common.sharding')
67

78
local compat = require('crud.common.compat')
89
local has_keydef = compat.exists('tuple.keydef', 'key_def')
@@ -48,7 +49,7 @@ local function get_index_for_condition(space_indexes, space_format, condition)
4849
end
4950
end
5051

51-
local function extract_sharding_key_from_scan_value(scan_value, scan_index, sharding_index)
52+
local function extract_sharding_key_from_scan_value(scan_value, scan_index, sharding_index, space_format, space_name)
5253
if #scan_value < #sharding_index.parts then
5354
return nil
5455
end
@@ -64,6 +65,11 @@ local function extract_sharding_key_from_scan_value(scan_value, scan_index, shar
6465

6566
-- check that sharding key is included in the scan index fields
6667
local sharding_key = {}
68+
local sharding_key_fieldno_map
69+
local ddl_sharding_key = sharding.get_ddl_sharding_key(space_name)
70+
if ddl_sharding_key ~= nil then
71+
sharding_key_fieldno_map = utils.get_keys_fieldno_map(space_format, ddl_sharding_key)
72+
end
6773
for _, sharding_key_part in ipairs(sharding_index.parts) do
6874
local fieldno = sharding_key_part.fieldno
6975

@@ -79,7 +85,13 @@ local function extract_sharding_key_from_scan_value(scan_value, scan_index, shar
7985
return nil
8086
end
8187

82-
table.insert(sharding_key, field_value)
88+
-- check if a field is a part of DDL sharding key
89+
if ddl_sharding_key ~= nil and
90+
sharding_key_fieldno_map[fieldno] == true then
91+
table.insert(sharding_key, field_value)
92+
else
93+
table.insert(sharding_key, field_value)
94+
end
8395
end
8496

8597
return sharding_key
@@ -231,7 +243,8 @@ function select_plan.new(space, conditions, opts)
231243
-- get sharding key value
232244
local sharding_key
233245
if scan_value ~= nil and (scan_iter == box.index.EQ or scan_iter == box.index.REQ) then
234-
sharding_key = extract_sharding_key_from_scan_value(scan_value, scan_index, sharding_index)
246+
sharding_key = extract_sharding_key_from_scan_value(scan_value, scan_index, sharding_index,
247+
space_format, space_name)
235248
end
236249

237250
if sharding_key ~= nil and opts.force_map_call ~= true then

crud/update.lua

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,15 @@ local function call_update_on_router(space_name, key, user_operations, opts)
8383
key = key:totable()
8484
end
8585

86+
local ddl_sharding_key = sharding.get_ddl_sharding_key(space_name)
87+
local sharding_key = key
88+
if ddl_sharding_key ~= nil then
89+
sharding_key, err = sharding.build_ddl_sharding_key(space, sharding_key, ddl_sharding_key, opts.bucket_id)
90+
if sharding_key == nil then
91+
return nil, err
92+
end
93+
end
94+
8695
local operations = user_operations
8796
if not utils.tarantool_supports_fieldpaths() then
8897
operations, err = utils.convert_operations(user_operations, space_format)
@@ -91,7 +100,7 @@ local function call_update_on_router(space_name, key, user_operations, opts)
91100
end
92101
end
93102

94-
local bucket_id = sharding.key_get_bucket_id(key, opts.bucket_id)
103+
local bucket_id = sharding.key_get_bucket_id(sharding_key, opts.bucket_id)
95104
local call_opts = {
96105
mode = 'write',
97106
timeout = opts.timeout,

0 commit comments

Comments
 (0)