diff --git a/test/entrypoint/srv_ddl.lua b/test/entrypoint/srv_ddl.lua index f240c743e..0482f3b49 100755 --- a/test/entrypoint/srv_ddl.lua +++ b/test/entrypoint/srv_ddl.lua @@ -90,6 +90,12 @@ package.preload['customers-storage'] = function() }, } + local customers_id_schema = table.deepcopy(customers_schema) + customers_id_schema.sharding_key = {'id'} + table.insert(customers_id_schema.indexes, primary_index_id) + table.insert(customers_id_schema.indexes, bucket_id_index) + table.insert(customers_id_schema.indexes, age_index) + local customers_name_key_schema = table.deepcopy(customers_schema) customers_name_key_schema.sharding_key = {'name'} table.insert(customers_name_key_schema.indexes, primary_index) @@ -133,6 +139,7 @@ package.preload['customers-storage'] = function() local schema = { spaces = { + customers = customers_id_schema, customers_name_key = customers_name_key_schema, customers_name_key_uniq_index = customers_name_key_uniq_index_schema, customers_name_key_non_uniq_index = customers_name_key_non_uniq_index_schema, @@ -166,8 +173,8 @@ local ok, err = errors.pcall('CartridgeCfgError', cartridge.cfg, { 'customers-storage', 'cartridge.roles.crud-router', 'cartridge.roles.crud-storage', - }, -}) + }}, { readahead = 20 * 1024 * 1024 } +) if not ok then log.error('%s', err) diff --git a/test/performance/perf_test.lua b/test/performance/perf_test.lua index 8f7cc055d..9259fa977 100644 --- a/test/performance/perf_test.lua +++ b/test/performance/perf_test.lua @@ -6,7 +6,7 @@ local net_box = require('net.box') local log = require('log') local t = require('luatest') -local g = t.group('perf') +local group = t.group('perf') local helpers = require('test.helper') @@ -21,10 +21,10 @@ local function reset_gen() id = 0 end -g.before_all(function(g) +group.before_all(function(g) g.cluster = helpers.Cluster:new({ datadir = fio.tempdir(), - server_command = helpers.entrypoint('srv_select'), + server_command = helpers.entrypoint('srv_ddl'), use_vshard = true, replicasets = { { @@ -79,7 +79,7 @@ g.before_all(function(g) g.total_report = {} end) -g.before_each(function(g) +group.before_each(function(g) helpers.truncate_space_on_cluster(g.cluster, 'customers') reset_gen() end) @@ -97,35 +97,44 @@ local function normalize(s, n) return (' '):rep(n - len) .. s end +local row_name = { + insert = 'insert', + select_pk = 'select by pk', + select_gt_pk = 'select gt by pk (limit 10)', + select_secondary_eq = 'select eq by secondary (limit 10)', + select_secondary_sharded = 'select eq by sharding secondary', +} + local column_name = { - without_stats_wrapper = 'without stats wrapper', - stats_disabled = 'stats disabled', - local_stats = 'local stats', - metrics_stats = 'metrics stats', + vshard = 'vshard', + without_stats_wrapper = 'crud (without stats wrapper)', + stats_disabled = 'crud (stats disabled)', + local_stats = 'crud (local stats)', + metrics_stats = 'crud (metrics stats)', } local function visualize_section(total_report, name, section, params) local report_str = ('== %s ==\n\n'):format(name) - local headers = normalize('', params.row_header_width - 1) .. ' ||' + local headers = normalize('', params.row_header_width) .. ' ||' for _, column in ipairs(params.columns) do - headers = headers .. ' ' .. normalize(column, params.col_width - 2) .. ' |' + headers = headers .. ' ' .. normalize(column, params.col_width[column]) .. ' |' end report_str = report_str .. headers .. '\n' report_str = report_str .. ('='):rep(headers:len()) .. '\n' - for row_name, row in pairs(total_report) do - local row_str = normalize(row_name, params.row_header_width - 1) .. ' ||' + for _, row in ipairs(params.rows) do + local row_str = normalize(row, params.row_header_width) .. ' ||' for _, column in ipairs(params.columns) do - local report = row[column] + local report = total_report[row][column] if report ~= nil then - row_str = row_str .. ' ' .. normalize(report.str[section], params.col_width - 2) .. ' |' + row_str = row_str .. ' ' .. normalize(report.str[section], params.col_width[column]) .. ' |' else - row_str = row_str .. ' ' .. normalize('unknown', params.col_width - 2) .. ' |' + row_str = row_str .. ' ' .. normalize('unknown', params.col_width[column]) .. ' |' end end @@ -141,21 +150,34 @@ end local function visualize_report(report) local params = {} - params.col_width = 2 - for _, name in pairs(column_name) do - params.col_width = math.max(name:len() + 2, params.col_width) - end - - params.row_header_width = 30 - - -- Set columns explicitly to preserve custom order. + -- Set columns and rows explicitly to preserve custom order. params.columns = { + column_name.vshard, column_name.without_stats_wrapper, column_name.stats_disabled, column_name.local_stats, column_name.metrics_stats, } + params.rows = { + row_name.select_pk, + row_name.select_gt_pk, + row_name.select_secondary_eq, + row_name.select_secondary_sharded, + row_name.insert, + } + + params.row_header_width = 1 + for _, name in pairs(row_name) do + params.row_header_width = math.max(name:len(), params.row_header_width) + end + + local min_col_width = 12 + params.col_width = {} + for _, name in ipairs(params.columns) do + params.col_width[name] = math.max(name:len(), min_col_width) + end + local report_str = '\n==== PERFORMANCE REPORT ====\n\n\n' report_str = report_str .. visualize_section(report, 'SUCCESS REQUESTS', 'success_count', params) @@ -167,7 +189,7 @@ local function visualize_report(report) log.info(report_str) end -g.after_all(function(g) +group.after_all(function(g) g.cluster:stop() fio.rmtree(g.cluster.datadir) @@ -175,7 +197,7 @@ g.after_all(function(g) end) local function generate_customer() - return { gen(), box.NULL, 'David', 'Smith', 33, 'Los Angeles' } + return { gen(), box.NULL, 'David Smith', 33 } end local select_prepare = function(g) @@ -192,14 +214,240 @@ local select_prepare = function(g) reset_gen() end +local select_sharded_by_secondary_prepare = function(g) + local count + if g.perf_mode_on then + count = 10100 + else + count = 100 + end + + for _ = 1, count do + g.router:call( + 'crud.insert', + { + 'customers_name_age_key_different_indexes', + { gen(), box.NULL, 'David Smith', gen() % 50 + 18 } + } + ) + end + reset_gen() +end + +local vshard_prepare = function(g) + g.router:eval([[ + local vshard = require('vshard') + + local function _vshard_insert(space_name, tuple) + local bucket_id = vshard.router.bucket_id_strcrc32(tuple[1]) + return vshard.router.callrw( + bucket_id, + '_vshard_insert_storage', + { space_name, tuple, bucket_id } + ) + end + + rawset(_G, '_vshard_insert', _vshard_insert) + + + local function _vshard_select(space_name, key) + local bucket_id = vshard.router.bucket_id_strcrc32(key) + return vshard.router.callrw( + bucket_id, + '_vshard_select_storage', + { space_name, key } + ) + end + + rawset(_G, '_vshard_select', _vshard_select) + + + local function sort(a, b) + return a[1] < b[1] + end + + local function _vshard_select_gt(space, key, opts) + assert(type(opts.limit) == 'number') + assert(opts.limit > 0) + + local tuples = {} + + for id, replicaset in pairs(vshard.router.routeall()) do + local resp, err = replicaset:call( + '_vshard_select_storage', + { space, key, nil, box.index.GT, opts.limit } + ) + if err ~= nil then + error(err) + end + + for _, v in ipairs(resp) do + table.insert(tuples, v) + end + + end + + -- Naive merger. + local response = { } + + table.sort(tuples, sort) + + for i = 1, opts.limit do + response[i] = tuples[i] + end + + return response + end + + rawset(_G, '_vshard_select_gt', _vshard_select_gt) + + + local function _vshard_select_secondary(space_name, index_name, key, opts) + assert(type(opts.limit) == 'number') + assert(opts.limit > 0) + + local storage_response = {} + + for id, replicaset in pairs(vshard.router.routeall()) do + local resp, err = replicaset:call( + '_vshard_select_storage', + { space_name, key, index_name, box.index.EQ, opts.limit } + ) + if err ~= nil then + error(err) + end + + storage_response[id] = resp + end + + -- Naive merger. + local response = { } + + local ind = 0 + for i = 1, opts.limit do + for _, tuples in pairs(storage_response) do + if tuples[i] ~= nil then + response[ind] = tuples[i] + end + end + + if ind == opts.limit then + break + end + end + + return response + end + + rawset(_G, '_vshard_select_secondary', _vshard_select_secondary) + + + local function _vshard_select_customer_by_name_and_age(key) + local bucket_id = vshard.router.bucket_id_strcrc32(key) + + return vshard.router.callrw( + bucket_id, + '_vshard_select_customer_by_name_and_age_storage', + { key } + ) + end + + rawset(_G, '_vshard_select_customer_by_name_and_age', _vshard_select_customer_by_name_and_age) + ]]) + + for _, server in ipairs(g.cluster.servers) do + server.net_box:eval([[ + local function _vshard_insert_storage(space_name, tuple, bucket_id) + local space = box.space[space_name] + assert(space ~= nil) + + assert(space.index.bucket_id ~= nil) + tuple[space.index.bucket_id.parts[1].fieldno] = bucket_id + + local ok = space:insert(tuple) + assert(ok ~= nil) + end + + rawset(_G, '_vshard_insert_storage', _vshard_insert_storage) + + + local function _vshard_select_storage(space_name, key, index_name, iterator, limit) + local space = box.space[space_name] + assert(space ~= nil) + + local index = nil + if index_name == nil then + index = box.space[space_name].index[0] + else + index = box.space[space_name].index[index_name] + end + assert(index ~= nil) + + iterator = iterator or box.index.EQ + return index:select(key, { limit = limit, iterator = iterator }) + end + + rawset(_G, '_vshard_select_storage', _vshard_select_storage) + + + local function _vshard_select_customer_by_name_and_age_storage(key) + local space = box.space.customers_name_age_key_different_indexes + local index = space.index.age + + for _, tuple in index:pairs(key[2]) do + if tuple.name == key[1] then + return tuple + end + end + return {} + end + + rawset(_G, '_vshard_select_customer_by_name_and_age_storage', + _vshard_select_customer_by_name_and_age_storage) + ]]) + end +end + local insert_params = function() return { 'customers', generate_customer() } end +local select_params_pk_eq = function() + return { 'customers', {{'==', 'id', gen() % 10000}} } +end + +local vshard_select_params_pk_eq = function() + return { 'customers', gen() % 10000 } +end + local select_params_pk_gt = function() return { 'customers', {{'>', 'id', gen() % 10000}}, { first = 10 } } end +local vshard_select_params_pk_gt = function() + return { 'customers', gen() % 10000, { limit = 10 } } +end + +local select_params_secondary_eq = function() + return { 'customers', {{'==', 'age', 33}}, { first = 10 } } +end + +local vshard_select_params_secondary_eq = function() + return { 'customers', 'age', 33, { limit = 10 } } +end + +local select_params_sharded_by_secondary = function() + return { + 'customers_name_age_key_different_indexes', + { { '==', 'name', 'David Smith' }, { '==', 'age', gen() % 50 + 18 }, }, + { first = 1 } + } +end + +local vshard_select_params_sharded_by_secondary = function() + return {{ 'David Smith', gen() % 50 + 18 }} +end + local stats_cases = { stats_disabled = { prepare = function(g) @@ -251,7 +499,7 @@ local cases = { matrix = stats_cases, integration_params = integration_params, perf_params = insert_perf, - row_name = 'insert', + row_name = row_name.insert, }, crud_insert_without_stats_wrapper = { @@ -265,7 +513,53 @@ local cases = { matrix = { [''] = { column_name = column_name.without_stats_wrapper } }, integration_params = integration_params, perf_params = insert_perf, - row_name = 'insert', + row_name = row_name.insert, + }, + + vshard_insert = { + prepare = vshard_prepare, + call = '_vshard_insert', + params = insert_params, + matrix = { [''] = { column_name = column_name.vshard } }, + integration_params = integration_params, + perf_params = insert_perf, + row_name = row_name.insert, + }, + + crud_select_pk_eq = { + prepare = select_prepare, + call = 'crud.select', + params = select_params_pk_eq, + matrix = stats_cases, + integration_params = integration_params, + perf_params = select_perf, + row_name = row_name.select_pk, + }, + + crud_select_without_stats_wrapper_pk_eq = { + prepare = function(g) + g.router:eval("_plain_select = require('crud.select').call") + select_prepare(g) + end, + call = '_plain_select', + params = select_params_pk_eq, + matrix = { [''] = { column_name = column_name.without_stats_wrapper } }, + integration_params = integration_params, + perf_params = select_perf, + row_name = row_name.select_pk, + }, + + vshard_select_pk_eq = { + prepare = function(g) + select_prepare(g) + vshard_prepare(g) + end, + call = '_vshard_select', + params = vshard_select_params_pk_eq, + matrix = { [''] = { column_name = column_name.vshard } }, + integration_params = integration_params, + perf_params = select_perf, + row_name = row_name.select_pk, }, crud_select_pk_gt = { @@ -275,7 +569,7 @@ local cases = { matrix = stats_cases, integration_params = integration_params, perf_params = select_perf, - row_name = 'select gt by pk (limit 10)', + row_name = row_name.select_gt_pk, }, crud_select_without_stats_wrapper_pk_gt = { @@ -290,7 +584,92 @@ local cases = { matrix = { [''] = { column_name = column_name.without_stats_wrapper } }, integration_params = integration_params, perf_params = select_perf, - row_name = 'select gt by pk (limit 10)', + row_name = row_name.select_gt_pk, + }, + + vshard_select_pk_gt = { + prepare = function(g) + select_prepare(g) + vshard_prepare(g) + end, + call = '_vshard_select_gt', + params = vshard_select_params_pk_gt, + matrix = { [''] = { column_name = column_name.vshard } }, + integration_params = integration_params, + perf_params = select_perf, + row_name = row_name.select_gt_pk, + }, + + crud_select_secondary_eq = { + prepare = select_prepare, + call = 'crud.select', + params = select_params_secondary_eq, + matrix = stats_cases, + integration_params = integration_params, + perf_params = select_perf, + row_name = row_name.select_secondary_eq, + }, + + crud_select_without_stats_wrapper_secondary_eq = { + prepare = function(g) + g.router:eval("_plain_select = require('crud.select').call") + select_prepare(g) + end, + call = '_plain_select', + params = select_params_secondary_eq, + matrix = { [''] = { column_name = column_name.without_stats_wrapper } }, + integration_params = integration_params, + perf_params = select_perf, + row_name = row_name.select_secondary_eq, + }, + + vshard_select_secondary_eq = { + prepare = function(g) + select_prepare(g) + vshard_prepare(g) + end, + call = '_vshard_select_secondary', + params = vshard_select_params_secondary_eq, + matrix = { [''] = { column_name = column_name.vshard } }, + integration_params = integration_params, + perf_params = select_perf, + row_name = row_name.select_secondary_eq, + }, + + crud_select_sharding_secondary_eq = { + prepare = select_sharded_by_secondary_prepare, + call = 'crud.select', + params = select_params_sharded_by_secondary, + matrix = stats_cases, + integration_params = integration_params, + perf_params = select_perf, + row_name = row_name.select_secondary_sharded, + }, + + crud_select_without_stats_wrapper_sharding_secondary_eq = { + prepare = function(g) + g.router:eval("_plain_select = require('crud.select').call") + select_sharded_by_secondary_prepare(g) + end, + call = '_plain_select', + params = select_params_sharded_by_secondary, + matrix = { [''] = { column_name = column_name.without_stats_wrapper } }, + integration_params = integration_params, + perf_params = select_perf, + row_name = row_name.select_secondary_sharded, + }, + + vshard_select_sharding_secondary_eq = { + prepare = function(g) + select_sharded_by_secondary_prepare(g) + vshard_prepare(g) + end, + call = '_vshard_select_customer_by_name_and_age', + params = vshard_select_params_sharded_by_secondary, + matrix = { [''] = { column_name = column_name.vshard } }, + integration_params = integration_params, + perf_params = select_perf, + row_name = row_name.select_secondary_sharded, }, } @@ -329,7 +708,7 @@ for name, case in pairs(cases) do local test_name = ('test_%s%s'):format(name, name_tail) - g.before_test(test_name, function(g) + group.before_test(test_name, function(g) if case.prepare ~= nil then case.prepare(g) end @@ -339,7 +718,7 @@ for name, case in pairs(cases) do end end) - g[test_name] = function(g) + group[test_name] = function(g) local params if g.perf_mode_on then params = case.perf_params