diff --git a/test/vinyl-luatest/common.lua b/test/vinyl-luatest/common.lua new file mode 100644 index 0000000000000000000000000000000000000000..520fe85b3fda8daefbe28767f3514df6aac5106f --- /dev/null +++ b/test/vinyl-luatest/common.lua @@ -0,0 +1,17 @@ +local common = {} + +function common.default_box_cfg() + return { + vinyl_read_threads = 2, + vinyl_write_threads = 3, + vinyl_memory = 512 * 1024 * 1024, + vinyl_range_size = 1024 * 64, + vinyl_page_size = 1024, + vinyl_run_count_per_level = 1, + vinyl_run_size_ratio = 2, + vinyl_cache = 10240, -- 10kB + vinyl_max_tuple_size = 1024 * 1024 * 6, + } +end + +return common diff --git a/test/vinyl-luatest/update_optimize_test.lua b/test/vinyl-luatest/update_optimize_test.lua new file mode 100644 index 0000000000000000000000000000000000000000..699339c75032291b649be08f47fe35bc89d20dbf --- /dev/null +++ b/test/vinyl-luatest/update_optimize_test.lua @@ -0,0 +1,532 @@ +local t = require('luatest') + +local server = require('test.luatest_helpers.server') +local common = require('test.vinyl-luatest.common') + +local g = t.group() + +g.before_all(function() + g.server = server:new( + {alias = 'master', box_cfg = common.default_box_cfg()} + ) + g.server:start() + g.server:exec(function() + rawset(_G, 'dump_stmt_count', function(indexes) + local dumped_count = 0 + for _, i in ipairs(indexes) do + dumped_count = dumped_count + + box.space.test.index[i]:stat().disk.dump.output.rows + end + return dumped_count + end) + end) +end) + +g.before_each(function() + g.server:exec(function() + box.schema.space.create('test', {engine = 'vinyl'}) + end) +end) + +g.after_each(function() + g.server:exec(function() box.space.test:drop() end) +end) + +g.test_optimize_one_index = function() + g.server:exec(function() + local t = require('luatest') + + box.space.test:create_index('primary', {run_count_per_level = 20}) + box.space.test:create_index('secondary', + {parts = {5, 'unsigned'}, run_count_per_level = 20}) + + box.snapshot() + + local old_stmt_count = _G.dump_stmt_count({'primary', 'secondary'}) + + box.space.test:insert({1, 2, 3, 4, 5}) + box.space.test:insert({2, 3, 4, 5, 6}) + box.space.test:insert({3, 4, 5, 6, 7}) + box.space.test:insert({4, 5, 6, 7, 8}) + box.snapshot() + + local new_stmt_count = _G.dump_stmt_count({'primary', 'secondary'}) + t.assert_equals(new_stmt_count - old_stmt_count, 8) + + -- Not optimized updates. + + -- Change secondary index field. + t.assert_equals( + box.space.test:update(1, {{'=', 5, 10}}), {1, 2, 3, 4, 10} + ) + -- Need a snapshot after each operation to avoid purging some statements + -- in vy_write_iterator during dump. + box.snapshot() + + -- Move range containing index field. + t.assert_equals( + box.space.test:update(1, {{'!', 4, 20}}), {1, 2, 3, 20, 4, 10} + ) + box.snapshot() + + -- Move range containing index field. + t.assert_equals( + box.space.test:update(1, {{'#', 3, 1}}), {1, 2, 20, 4, 10} + ) + box.snapshot() + + old_stmt_count = new_stmt_count + new_stmt_count = _G.dump_stmt_count({'primary', 'secondary'}) + t.assert_equals(new_stmt_count - old_stmt_count, 9) + + t.assert_equals( + box.space.test.index.primary:select {}, + { + {1, 2, 20, 4, 10}, + {2, 3, 4, 5, 6}, + {3, 4, 5, 6, 7}, + {4, 5, 6, 7, 8} + } + ) + t.assert_equals( + box.space.test.index.secondary:select {}, + { + {2, 3, 4, 5, 6}, + {3, 4, 5, 6, 7}, + {4, 5, 6, 7, 8}, + {1, 2, 20, 4, 10} + } + ) + + -- Optimized updates. + + -- Change not indexed field. + t.assert_equals( + box.space.test:update(2, {{'=', 6, 10}}), {2, 3, 4, 5, 6, 10} + ) + box.snapshot() + + -- Move range that doesn't contain indexed fields. + t.assert_equals( + box.space.test:update(2, {{'!', 7, 20}}), {2, 3, 4, 5, 6, 10, 20} + ) + box.snapshot() + + t.assert_equals( + box.space.test:update(2, {{'#', 6, 1}}), {2, 3, 4, 5, 6, 20} + ) + box.snapshot() + + old_stmt_count = new_stmt_count + new_stmt_count = _G.dump_stmt_count({'primary', 'secondary'}) + t.assert_equals(new_stmt_count - old_stmt_count, 3) + + t.assert_equals( + box.space.test.index.primary:select {}, + { + {1, 2, 20, 4, 10}, + {2, 3, 4, 5, 6, 20}, + {3, 4, 5, 6, 7}, + {4, 5, 6, 7, 8} + } + ) + t.assert_equals( + box.space.test.index.secondary:select {}, + { + {2, 3, 4, 5, 6, 20}, + {3, 4, 5, 6, 7}, + {4, 5, 6, 7, 8}, + {1, 2, 20, 4, 10} + } + ) + end) +end + +g.test_optimize_two_indexes = function() + g.server:exec(function() + local t = require('luatest') + + box.space.test:create_index('primary', + {parts = {2, 'unsigned'}, run_count_per_level = 20}) + box.space.test:create_index('secondary', + {parts = {4, 'unsigned', 3, 'unsigned'}, run_count_per_level = 20}) + box.space.test:create_index('third', + {parts = {5, 'unsigned'}, run_count_per_level = 20}) + + box.snapshot() + + local old_stmt_count = _G.dump_stmt_count( + {'primary', 'secondary', 'third'} + ) + + box.space.test:insert({1, 2, 3, 4, 5}) + box.space.test:insert({2, 3, 4, 5, 6}) + box.space.test:insert({3, 4, 5, 6, 7}) + box.space.test:insert({4, 5, 6, 7, 8}) + box.snapshot() + + local new_stmt_count = _G.dump_stmt_count( + {'primary', 'secondary', 'third'} + ) + t.assert_equals(new_stmt_count - old_stmt_count, 12) + + -- Not optimized updates. + + -- Change all fields. + t.assert_equals( + box.space.test:update( + 2, {{'+', 1, 10}, {'+', 3, 10}, {'+', 4, 10}, {'+', 5, 10}} + ), + {11, 2, 13, 14, 15} + ) + box.snapshot() + + -- Move range containing all indexes. + t.assert_equals( + box.space.test:update(2, {{'!', 3, 20}}), {11, 2, 20, 13, 14, 15} + ) + box.snapshot() + + -- Change two cols but then move range with all indexed fields. + t.assert_equals( + box.space.test:update( + 2, {{'=', 7, 100}, {'+', 5, 10}, {'#', 3, 1}} + ), + {11, 2, 13, 24, 15, 100} + ) + box.snapshot() + + old_stmt_count = new_stmt_count + new_stmt_count = _G.dump_stmt_count({'primary', 'secondary', 'third'}) + t.assert_equals(new_stmt_count - old_stmt_count, 15) + + t.assert_equals( + box.space.test.index.primary:select {}, + { + {11, 2, 13, 24, 15, 100}, + {2, 3, 4, 5, 6}, + {3, 4, 5, 6, 7}, + {4, 5, 6, 7, 8} + } + ) + t.assert_equals( + box.space.test.index.secondary:select {}, + { + {2, 3, 4, 5, 6}, + {3, 4, 5, 6, 7}, + {4, 5, 6, 7, 8}, + {11, 2, 13, 24, 15, 100} + } + ) + t.assert_equals( + box.space.test.index.third:select {}, + { + {2, 3, 4, 5, 6}, + {3, 4, 5, 6, 7}, + {4, 5, 6, 7, 8}, + {11, 2, 13, 24, 15, 100} + } + ) + + -- Optimize one 'secondary' index update. + + -- Change only index 'third'. + t.assert_equals( + box.space.test:update( + 3, {{'+', 1, 10}, {'-', 5, 2}, {'!', 6, 100}} + ), + {12, 3, 4, 5, 4, 100} + ) + box.snapshot() + + old_stmt_count = new_stmt_count + new_stmt_count = _G.dump_stmt_count({'primary', 'secondary', 'third'}) + t.assert_equals(new_stmt_count - old_stmt_count, 3) + + -- Optimize one 'third' index update. + + -- Change only index 'secondary'. + t.assert_equals( + box.space.test:update( + 3, {{'=', 1, 20}, {'+', 3, 5}, {'=', 4, 30}, {'!', 6, 110}} + ), + {20, 3, 9, 30, 4, 110, 100} + ) + box.snapshot() + + old_stmt_count = new_stmt_count + new_stmt_count = _G.dump_stmt_count({'primary', 'secondary', 'third'}) + t.assert_equals(new_stmt_count - old_stmt_count, 3) + + -- Optimize both indexes. + + -- Not change any indexed fields. + t.assert_equals( + box.space.test:update(3, {{'+', 1, 10}, {'#', 6, 1}}), + {30, 3, 9, 30, 4, 100} + ) + box.snapshot() + + old_stmt_count = new_stmt_count + new_stmt_count = _G.dump_stmt_count({'primary', 'secondary', 'third'}) + t.assert_equals(new_stmt_count - old_stmt_count, 1) + + t.assert_equals( + box.space.test.index.primary:select {}, + { + {11, 2, 13, 24, 15, 100}, + {30, 3, 9, 30, 4, 100}, + {3, 4, 5, 6, 7}, + {4, 5, 6, 7, 8} + } + ) + t.assert_equals( + box.space.test.index.secondary:select {}, + { + {3, 4, 5, 6, 7}, + {4, 5, 6, 7, 8}, + {11, 2, 13, 24, 15, 100}, + {30, 3, 9, 30, 4, 100} + } + ) + t.assert_equals( + box.space.test.index.third:select {}, + { + {30, 3, 9, 30, 4, 100}, + {3, 4, 5, 6, 7}, + {4, 5, 6, 7, 8}, + {11, 2, 13, 24, 15, 100} + } + ) + end) +end + +-- gh-1716: optimize UPDATE with field num > 64. +g.test_optimize_UPDATE_with_field_num_more_than_64 = function() + g.server:exec(function() + local t = require('luatest') + + box.space.test:create_index('primary', + {parts = {2, 'unsigned'}, run_count_per_level = 20}) + box.space.test:create_index('secondary', + {parts = {4, 'unsigned', 3, 'unsigned'}, run_count_per_level = 20}) + box.space.test:create_index('third', + {parts = {5, 'unsigned'}, run_count_per_level = 20}) + + -- Create a big tuple. + local long_tuple = {} + for i = 1, 70 do long_tuple[i] = i end + + box.space.test:replace(long_tuple) + box.snapshot() + + -- Make update of not indexed field with pos > 64. + local old_stmt_count = _G.dump_stmt_count( + {'primary', 'secondary', 'third'}) + long_tuple[65] = 1000 + t.assert_equals(box.space.test:update(2, {{'=', 65, 1000}}), long_tuple) + box.snapshot() + + -- Check only primary index to be changed. + local new_stmt_count = _G.dump_stmt_count( + {'primary', 'secondary', 'third'}) + t.assert_equals(new_stmt_count - old_stmt_count, 1) + t.assert_equals(box.space.test:get {2}[65], 1000) + + -- Try to optimize update with negative field numbers. + + t.assert_equals( + box.space.test:update(2, {{'#', -65, 65}}), {1, 2, 3, 4, 5} + ) + box.snapshot() + + old_stmt_count = new_stmt_count + new_stmt_count = _G.dump_stmt_count({'primary', 'secondary', 'third'}) + t.assert_equals(new_stmt_count - old_stmt_count, 1) + + t.assert_equals( + box.space.test.index.primary:select {}, {{1, 2, 3, 4, 5}} + ) + t.assert_equals( + box.space.test.index.secondary:select {}, {{1, 2, 3, 4, 5}} + ) + t.assert_equals(box.space.test.index.third:select {}, {{1, 2, 3, 4, 5}}) + + box.space.test:replace({10, 20, 30, 40, 50}) + box.snapshot() + + old_stmt_count = _G.dump_stmt_count({'primary', 'secondary', 'third'}) + + t.assert_equals( + box.space.test:update(20, {{'=', -1, 500}}), {10, 20, 30, 40, 500} + ) + box.snapshot() + + new_stmt_count = _G.dump_stmt_count({'primary', 'secondary', 'third'}) + -- 3 = REPLACE in 1 index and DELETE + REPLACE in 3 index. + t.assert_equals(new_stmt_count - old_stmt_count, 3) + + t.assert_equals( + box.space.test.index.primary:select {}, + { + {1, 2, 3, 4, 5}, + {10, 20, 30, 40, 500} + } + ) + t.assert_equals( + box.space.test.index.secondary:select {}, + { + {1, 2, 3, 4, 5}, + {10, 20, 30, 40, 500} + } + ) + t.assert_equals( + box.space.test.index.third:select {}, + { + {1, 2, 3, 4, 5}, + {10, 20, 30, 40, 500} + } + ) + end) +end + +g.test_optimize_update_does_not_skip_entire_key_during_dump = function() + g.server:exec(function() + local t = require('luatest') + + box.space.test:create_index('primary', + {parts = {2, 'unsigned'}, run_count_per_level = 20}) + box.space.test:create_index('secondary', + {parts = {4, 'unsigned', 3, 'unsigned'}, run_count_per_level = 20}) + box.space.test:create_index('third', + {parts = {5, 'unsigned'}, run_count_per_level = 20}) + + box.space.test:replace({10, 100, 1000, 10000, 100000, 1000000}) + t.assert_equals( + box.space.test:update(100, {{'=', 6, 1}}), + {10, 100, 1000, 10000, 100000, 1} + ) + + box.begin() + + box.space.test:replace({20, 200, 2000, 20000, 200000, 2000000}) + t.assert_equals( + box.space.test:update(200, {{'=', 6, 2}}), + {20, 200, 2000, 20000, 200000, 2} + ) + + box.commit() + + box.snapshot() + + t.assert_equals( + box.space.test.index.primary:select {}, + { + {10, 100, 1000, 10000, 100000, 1}, + {20, 200, 2000, 20000, 200000, 2} + } + ) + t.assert_equals( + box.space.test.index.secondary:select {}, + { + {10, 100, 1000, 10000, 100000, 1}, + {20, 200, 2000, 20000, 200000, 2} + } + ) + t.assert_equals( + box.space.test.index.third:select {}, + { + {10, 100, 1000, 10000, 100000, 1}, + {20, 200, 2000, 20000, 200000, 2} + } + ) + end) +end + +-- gh-2980: key uniqueness is not checked if indexed fields are not updated. +g.test_key_uniqueness_not_checked_if_indexed_fields_not_updated = function() + g.server:exec(function() + local t = require('luatest') + + box.space.test:create_index('primary', + {parts = {2, 'unsigned'}, run_count_per_level = 20}) + box.space.test:create_index('secondary', + {parts = {4, 'unsigned', 3, 'unsigned'}, run_count_per_level = 20}) + box.space.test:create_index('third', + {parts = {5, 'unsigned'}, run_count_per_level = 20}) + + box.space.test:replace({1, 1, 1, 1, 1}) + + local function get_lookups(lb) + local ret = {} + for i = 1, #lb do + local info = box.space.test.index[i - 1]:stat() + table.insert(ret, info.lookup - lb[i]) + end + return ret + end + + local lookups = get_lookups({0, 0, 0}) + + -- Update field that is not indexed. + t.assert_equals( + box.space.test:update(1, {{'+', 1, 1}}), {2, 1, 1, 1, 1} + ) + t.assert_equals(get_lookups(lookups), {1, 0, 0}) + + -- Update field indexed by space.index[1]. + t.assert_equals( + box.space.test:update(1, {{'+', 3, 1}}), {2, 1, 2, 1, 1} + ) + t.assert_equals(get_lookups(lookups), {2, 1, 0}) + + -- Update field indexed by space.index[2]. + t.assert_equals( + box.space.test:update(1, {{'+', 5, 1}}), {2, 1, 2, 1, 2} + ) + t.assert_equals(get_lookups(lookups), {3, 1, 1}) + end) +end + +-- gh-3607: phantom tuples in secondary index if UPDATE does not change key +-- fields. +g.test_no_phantom_tuples_in_secondary_index = function() + g.server:exec(function() + local t = require('luatest') + + box.space.test:create_index('primary') + box.space.test:create_index('secondary', + {parts = {2, 'unsigned'}, run_count_per_level = 10}) + + box.space.test:insert({1, 10}) + -- Some padding to prevent last-level compaction (gh-3657). + for i = 1001, 1010 do box.space.test:replace {i, i} end + box.snapshot() + + t.assert_equals(box.space.test:update(1, {{'=', 2, 10}}), {1, 10}) + box.space.test:delete(1) + box.snapshot() + + -- Should be 12: INSERT{10, 1} and INSERT[1001..1010] in the first run + -- plus DELETE{10, 1} in the second one. + t.assert_equals(box.space.test.index.secondary:stat().rows, 12) + + box.space.test:insert({1, 20}) + t.assert_equals( + box.space.test.index.secondary:select {}, + { + {1, 20}, + {1001, 1001}, + {1002, 1002}, + {1003, 1003}, + {1004, 1004}, + {1005, 1005}, + {1006, 1006}, + {1007, 1007}, + {1008, 1008}, + {1009, 1009}, + {1010, 1010} + } + ) + end) +end diff --git a/test/vinyl/update_optimize.result b/test/vinyl/update_optimize.result deleted file mode 100644 index 09370e7d53ce128de0a18ff8fd4b8a979b372bc5..0000000000000000000000000000000000000000 --- a/test/vinyl/update_optimize.result +++ /dev/null @@ -1,767 +0,0 @@ -test_run = require('test_run').new() ---- -... --- Restart the server to finish all snaphsots from prior tests. -test_run:cmd('restart server default') -fiber = require('fiber') ---- -... --- optimize one index -space = box.schema.space.create('test', { engine = 'vinyl' }) ---- -... -index = space:create_index('primary', { run_count_per_level = 20 }) ---- -... -index2 = space:create_index('secondary', { parts = {5, 'unsigned'}, run_count_per_level = 20 }) ---- -... -function dumped_stmt_count() return index:stat().disk.dump.output.rows + index2:stat().disk.dump.output.rows end ---- -... -box.snapshot() ---- -- ok -... -test_run:cmd("setopt delimiter ';'") ---- -- true -... -function wait_for_dump(index, old_count) - while index:stat().disk.dump.count == old_count do - fiber.sleep(0) - end - return index:stat().disk.dump.count -end; ---- -... -test_run:cmd("setopt delimiter ''"); ---- -- true -... -dump_count = index:stat().disk.dump.count ---- -... -old_stmt_count = dumped_stmt_count() ---- -... -space:insert({1, 2, 3, 4, 5}) ---- -- [1, 2, 3, 4, 5] -... -space:insert({2, 3, 4, 5, 6}) ---- -- [2, 3, 4, 5, 6] -... -space:insert({3, 4, 5, 6, 7}) ---- -- [3, 4, 5, 6, 7] -... -space:insert({4, 5, 6, 7, 8}) ---- -- [4, 5, 6, 7, 8] -... -box.snapshot() ---- -- ok -... --- Wait for dump both indexes. -dump_count = wait_for_dump(index, dump_count) ---- -... -new_stmt_count = dumped_stmt_count() ---- -... -new_stmt_count - old_stmt_count == 8 ---- -- true -... -old_stmt_count = new_stmt_count ---- -... --- not optimized updates -space:update({1}, {{'=', 5, 10}}) -- change secondary index field ---- -- [1, 2, 3, 4, 10] -... --- Need a snapshot after each operation to avoid purging some --- statements in vy_write_iterator during dump. -box.snapshot() ---- -- ok -... -dump_count = wait_for_dump(index, dump_count) ---- -... -space:update({1}, {{'!', 4, 20}}) -- move range containing index field ---- -- [1, 2, 3, 20, 4, 10] -... -box.snapshot() ---- -- ok -... -dump_count = wait_for_dump(index, dump_count) ---- -... -space:update({1}, {{'#', 3, 1}}) -- same ---- -- [1, 2, 20, 4, 10] -... -box.snapshot() ---- -- ok -... -dump_count = wait_for_dump(index, dump_count) ---- -... -new_stmt_count = dumped_stmt_count() ---- -... -new_stmt_count - old_stmt_count == 9 ---- -- true -... -old_stmt_count = new_stmt_count ---- -... -space:select{} ---- -- - [1, 2, 20, 4, 10] - - [2, 3, 4, 5, 6] - - [3, 4, 5, 6, 7] - - [4, 5, 6, 7, 8] -... -index2:select{} ---- -- - [2, 3, 4, 5, 6] - - [3, 4, 5, 6, 7] - - [4, 5, 6, 7, 8] - - [1, 2, 20, 4, 10] -... --- optimized updates -space:update({2}, {{'=', 6, 10}}) -- change not indexed field ---- -- [2, 3, 4, 5, 6, 10] -... -box.snapshot() ---- -- ok -... -dump_count = wait_for_dump(index, dump_count) ---- -... --- Move range that doesn't contain indexed fields. -space:update({2}, {{'!', 7, 20}}) ---- -- [2, 3, 4, 5, 6, 10, 20] -... -box.snapshot() ---- -- ok -... -dump_count = wait_for_dump(index, dump_count) ---- -... -space:update({2}, {{'#', 6, 1}}) -- same ---- -- [2, 3, 4, 5, 6, 20] -... -box.snapshot() ---- -- ok -... -dump_count = wait_for_dump(index, dump_count) ---- -... -new_stmt_count = dumped_stmt_count() ---- -... -new_stmt_count - old_stmt_count == 3 ---- -- true -... -old_stmt_count = new_stmt_count ---- -... -space:select{} ---- -- - [1, 2, 20, 4, 10] - - [2, 3, 4, 5, 6, 20] - - [3, 4, 5, 6, 7] - - [4, 5, 6, 7, 8] -... -index2:select{} ---- -- - [2, 3, 4, 5, 6, 20] - - [3, 4, 5, 6, 7] - - [4, 5, 6, 7, 8] - - [1, 2, 20, 4, 10] -... -space:drop() ---- -... --- optimize two indexes -space = box.schema.space.create('test', { engine = 'vinyl' }) ---- -... -index = space:create_index('primary', { parts = {2, 'unsigned'}, run_count_per_level = 20 } ) ---- -... -index2 = space:create_index('secondary', { parts = {4, 'unsigned', 3, 'unsigned'}, run_count_per_level = 20 }) ---- -... -index3 = space:create_index('third', { parts = {5, 'unsigned'}, run_count_per_level = 20 }) ---- -... -function dumped_stmt_count() return index:stat().disk.dump.output.rows + index2:stat().disk.dump.output.rows + index3:stat().disk.dump.output.rows end ---- -... -box.snapshot() ---- -- ok -... -dump_count = index:stat().run_count ---- -... -old_stmt_count = dumped_stmt_count() ---- -... -space:insert({1, 2, 3, 4, 5}) ---- -- [1, 2, 3, 4, 5] -... -space:insert({2, 3, 4, 5, 6}) ---- -- [2, 3, 4, 5, 6] -... -space:insert({3, 4, 5, 6, 7}) ---- -- [3, 4, 5, 6, 7] -... -space:insert({4, 5, 6, 7, 8}) ---- -- [4, 5, 6, 7, 8] -... -box.snapshot() ---- -- ok -... -dump_count = wait_for_dump(index, dump_count) ---- -... -new_stmt_count = dumped_stmt_count() ---- -... -new_stmt_count - old_stmt_count == 12 ---- -- true -... -old_stmt_count = new_stmt_count ---- -... --- not optimizes updates -index:update({2}, {{'+', 1, 10}, {'+', 3, 10}, {'+', 4, 10}, {'+', 5, 10}}) -- change all fields ---- -- [11, 2, 13, 14, 15] -... -box.snapshot() ---- -- ok -... -dump_count = wait_for_dump(index, dump_count) ---- -... -index:update({2}, {{'!', 3, 20}}) -- move range containing all indexes ---- -- [11, 2, 20, 13, 14, 15] -... -box.snapshot() ---- -- ok -... -dump_count = wait_for_dump(index, dump_count) ---- -... -index:update({2}, {{'=', 7, 100}, {'+', 5, 10}, {'#', 3, 1}}) -- change two cols but then move range with all indexed fields ---- -- [11, 2, 13, 24, 15, 100] -... -box.snapshot() ---- -- ok -... -dump_count = wait_for_dump(index, dump_count) ---- -... -new_stmt_count = dumped_stmt_count() ---- -... -new_stmt_count - old_stmt_count == 15 ---- -- true -... -old_stmt_count = new_stmt_count ---- -... -space:select{} ---- -- - [11, 2, 13, 24, 15, 100] - - [2, 3, 4, 5, 6] - - [3, 4, 5, 6, 7] - - [4, 5, 6, 7, 8] -... -index2:select{} ---- -- - [2, 3, 4, 5, 6] - - [3, 4, 5, 6, 7] - - [4, 5, 6, 7, 8] - - [11, 2, 13, 24, 15, 100] -... -index3:select{} ---- -- - [2, 3, 4, 5, 6] - - [3, 4, 5, 6, 7] - - [4, 5, 6, 7, 8] - - [11, 2, 13, 24, 15, 100] -... --- optimize one 'secondary' index update -index:update({3}, {{'+', 1, 10}, {'-', 5, 2}, {'!', 6, 100}}) -- change only index 'third' ---- -- [12, 3, 4, 5, 4, 100] -... -box.snapshot() ---- -- ok -... -dump_count = wait_for_dump(index, dump_count) ---- -... -new_stmt_count = dumped_stmt_count() ---- -... -new_stmt_count - old_stmt_count == 3 ---- -- true -... -old_stmt_count = new_stmt_count ---- -... --- optimize one 'third' index update -index:update({3}, {{'=', 1, 20}, {'+', 3, 5}, {'=', 4, 30}, {'!', 6, 110}}) -- change only index 'secondary' ---- -- [20, 3, 9, 30, 4, 110, 100] -... -box.snapshot() ---- -- ok -... -dump_count = wait_for_dump(index, dump_count) ---- -... -new_stmt_count = dumped_stmt_count() ---- -... -new_stmt_count - old_stmt_count == 3 ---- -- true -... -old_stmt_count = new_stmt_count ---- -... --- optimize both indexes -index:update({3}, {{'+', 1, 10}, {'#', 6, 1}}) -- don't change any indexed fields ---- -- [30, 3, 9, 30, 4, 100] -... -box.snapshot() ---- -- ok -... -dump_count = wait_for_dump(index, dump_count) ---- -... -new_stmt_count = dumped_stmt_count() ---- -... -new_stmt_count - old_stmt_count == 1 ---- -- true -... -old_stmt_count = new_stmt_count ---- -... -space:select{} ---- -- - [11, 2, 13, 24, 15, 100] - - [30, 3, 9, 30, 4, 100] - - [3, 4, 5, 6, 7] - - [4, 5, 6, 7, 8] -... -index2:select{} ---- -- - [3, 4, 5, 6, 7] - - [4, 5, 6, 7, 8] - - [11, 2, 13, 24, 15, 100] - - [30, 3, 9, 30, 4, 100] -... -index3:select{} ---- -- - [30, 3, 9, 30, 4, 100] - - [3, 4, 5, 6, 7] - - [4, 5, 6, 7, 8] - - [11, 2, 13, 24, 15, 100] -... --- --- gh-1716: optimize UPDATE with fieldno > 64. --- --- Create a big tuple. -long_tuple = {} ---- -... -for i = 1, 70 do long_tuple[i] = i end ---- -... -_ = space:replace(long_tuple) ---- -... -box.snapshot() ---- -- ok -... --- Make update of not indexed field with pos > 64. -dump_count = wait_for_dump(index, dump_count) ---- -... -old_stmt_count = dumped_stmt_count() ---- -... -_ = index:update({2}, {{'=', 65, 1000}}) ---- -... -box.snapshot() ---- -- ok -... --- Check the only primary index to be changed. -dump_count = wait_for_dump(index, dump_count) ---- -... -new_stmt_count = dumped_stmt_count() ---- -... -new_stmt_count - old_stmt_count == 1 ---- -- true -... -old_stmt_count = new_stmt_count ---- -... -space:get{2}[65] ---- -- 1000 -... --- --- Try to optimize update with negative field numbers. --- -index:update({2}, {{'#', -65, 65}}) ---- -- [1, 2, 3, 4, 5] -... -box.snapshot() ---- -- ok -... -dump_count = wait_for_dump(index, dump_count) ---- -... -new_stmt_count = dumped_stmt_count() ---- -... -new_stmt_count - old_stmt_count == 1 ---- -- true -... -old_stmt_count = new_stmt_count ---- -... -index:select{} ---- -- - [1, 2, 3, 4, 5] - - [30, 3, 9, 30, 4, 100] - - [3, 4, 5, 6, 7] - - [4, 5, 6, 7, 8] -... -index2:select{} ---- -- - [1, 2, 3, 4, 5] - - [3, 4, 5, 6, 7] - - [4, 5, 6, 7, 8] - - [30, 3, 9, 30, 4, 100] -... -index3:select{} ---- -- - [30, 3, 9, 30, 4, 100] - - [1, 2, 3, 4, 5] - - [3, 4, 5, 6, 7] - - [4, 5, 6, 7, 8] -... --- Optimize index2 with negative update op. -space:replace{10, 20, 30, 40, 50} ---- -- [10, 20, 30, 40, 50] -... -box.snapshot() ---- -- ok -... -dump_count = wait_for_dump(index, dump_count) ---- -... -old_stmt_count = dumped_stmt_count() ---- -... -index:update({20}, {{'=', -1, 500}}) ---- -- [10, 20, 30, 40, 500] -... -box.snapshot() ---- -- ok -... -dump_count = wait_for_dump(index, dump_count) ---- -... -new_stmt_count = dumped_stmt_count() ---- -... --- 3 = REPLACE in index1 and DELETE + REPLACE in index3. -new_stmt_count - old_stmt_count == 3 ---- -- true -... -old_stmt_count = new_stmt_count ---- -... -index:select{} ---- -- - [1, 2, 3, 4, 5] - - [30, 3, 9, 30, 4, 100] - - [3, 4, 5, 6, 7] - - [4, 5, 6, 7, 8] - - [10, 20, 30, 40, 500] -... -index2:select{} ---- -- - [1, 2, 3, 4, 5] - - [3, 4, 5, 6, 7] - - [4, 5, 6, 7, 8] - - [30, 3, 9, 30, 4, 100] - - [10, 20, 30, 40, 500] -... -index3:select{} ---- -- - [30, 3, 9, 30, 4, 100] - - [1, 2, 3, 4, 5] - - [3, 4, 5, 6, 7] - - [4, 5, 6, 7, 8] - - [10, 20, 30, 40, 500] -... --- Check if optimizes update do not skip the entire key during --- dump. -space:replace{10, 100, 1000, 10000, 100000, 1000000} ---- -- [10, 100, 1000, 10000, 100000, 1000000] -... -index:update({100}, {{'=', 6, 1}}) ---- -- [10, 100, 1000, 10000, 100000, 1] -... -box.begin() ---- -... -space:replace{20, 200, 2000, 20000, 200000, 2000000} ---- -- [20, 200, 2000, 20000, 200000, 2000000] -... -index:update({200}, {{'=', 6, 2}}) ---- -- [20, 200, 2000, 20000, 200000, 2] -... -box.commit() ---- -... -box.snapshot() ---- -- ok -... -dump_count = wait_for_dump(index, dump_count) ---- -... -old_stmt_count = dumped_stmt_count() ---- -... -index:select{} ---- -- - [1, 2, 3, 4, 5] - - [30, 3, 9, 30, 4, 100] - - [3, 4, 5, 6, 7] - - [4, 5, 6, 7, 8] - - [10, 20, 30, 40, 500] - - [10, 100, 1000, 10000, 100000, 1] - - [20, 200, 2000, 20000, 200000, 2] -... -index2:select{} ---- -- - [1, 2, 3, 4, 5] - - [3, 4, 5, 6, 7] - - [4, 5, 6, 7, 8] - - [30, 3, 9, 30, 4, 100] - - [10, 20, 30, 40, 500] - - [10, 100, 1000, 10000, 100000, 1] - - [20, 200, 2000, 20000, 200000, 2] -... -index3:select{} ---- -- - [30, 3, 9, 30, 4, 100] - - [1, 2, 3, 4, 5] - - [3, 4, 5, 6, 7] - - [4, 5, 6, 7, 8] - - [10, 20, 30, 40, 500] - - [10, 100, 1000, 10000, 100000, 1] - - [20, 200, 2000, 20000, 200000, 2] -... --- --- gh-2980: key uniqueness is not checked if indexed fields --- are not updated. --- -space:truncate() ---- -... -space:replace{1, 1, 1, 1, 1} ---- -- [1, 1, 1, 1, 1] -... -LOOKUPS_BASE = {0, 0, 0} ---- -... -test_run:cmd("setopt delimiter ';'") ---- -- true -... -function lookups() - local ret = {} - for i = 1, #LOOKUPS_BASE do - local info = space.index[i - 1]:stat() - table.insert(ret, info.lookup - LOOKUPS_BASE[i]) - end - return ret -end; ---- -... -test_run:cmd("setopt delimiter ''"); ---- -- true -... -LOOKUPS_BASE = lookups() ---- -... --- update of a field that is not indexed -space:update(1, {{'+', 1, 1}}) ---- -- [2, 1, 1, 1, 1] -... -lookups() ---- -- - 1 - - 0 - - 0 -... --- update of a field indexed by space.index[1] -space:update(1, {{'+', 3, 1}}) ---- -- [2, 1, 2, 1, 1] -... -lookups() ---- -- - 2 - - 1 - - 0 -... --- update of a field indexed by space.index[2] -space:update(1, {{'+', 5, 1}}) ---- -- [2, 1, 2, 1, 2] -... -lookups() ---- -- - 3 - - 1 - - 1 -... -space:drop() ---- -... --- --- gh-3607: phantom tuples in secondary index if UPDATE does not --- change key fields. --- -s = box.schema.space.create('test', {engine = 'vinyl'}) ---- -... -_ = s:create_index('pk') ---- -... -_ = s:create_index('sk', {parts = {2, 'unsigned'}, run_count_per_level = 10}) ---- -... -s:insert{1, 10} ---- -- [1, 10] -... --- Some padding to prevent last-level compaction (gh-3657). -for i = 1001, 1010 do s:replace{i, i} end ---- -... -box.snapshot() ---- -- ok -... -s:update(1, {{'=', 2, 10}}) ---- -- [1, 10] -... -s:delete(1) ---- -... -box.snapshot() ---- -- ok -... --- Should be 12: INSERT{10, 1} and INSERT[1001..1010] in the first run --- plus DELETE{10, 1} in the second run. -s.index.sk:stat().rows ---- -- 12 -... -s:insert{1, 20} ---- -- [1, 20] -... -s.index.sk:select() ---- -- - [1, 20] - - [1001, 1001] - - [1002, 1002] - - [1003, 1003] - - [1004, 1004] - - [1005, 1005] - - [1006, 1006] - - [1007, 1007] - - [1008, 1008] - - [1009, 1009] - - [1010, 1010] -... -s:drop() ---- -... diff --git a/test/vinyl/update_optimize.test.lua b/test/vinyl/update_optimize.test.lua deleted file mode 100644 index a0de6e4cdf562330071fd0769a9b26082edf65bc..0000000000000000000000000000000000000000 --- a/test/vinyl/update_optimize.test.lua +++ /dev/null @@ -1,259 +0,0 @@ -test_run = require('test_run').new() --- Restart the server to finish all snaphsots from prior tests. -test_run:cmd('restart server default') -fiber = require('fiber') - --- optimize one index - -space = box.schema.space.create('test', { engine = 'vinyl' }) -index = space:create_index('primary', { run_count_per_level = 20 }) -index2 = space:create_index('secondary', { parts = {5, 'unsigned'}, run_count_per_level = 20 }) -function dumped_stmt_count() return index:stat().disk.dump.output.rows + index2:stat().disk.dump.output.rows end -box.snapshot() -test_run:cmd("setopt delimiter ';'") -function wait_for_dump(index, old_count) - while index:stat().disk.dump.count == old_count do - fiber.sleep(0) - end - return index:stat().disk.dump.count -end; -test_run:cmd("setopt delimiter ''"); - -dump_count = index:stat().disk.dump.count -old_stmt_count = dumped_stmt_count() -space:insert({1, 2, 3, 4, 5}) -space:insert({2, 3, 4, 5, 6}) -space:insert({3, 4, 5, 6, 7}) -space:insert({4, 5, 6, 7, 8}) -box.snapshot() --- Wait for dump both indexes. -dump_count = wait_for_dump(index, dump_count) -new_stmt_count = dumped_stmt_count() -new_stmt_count - old_stmt_count == 8 -old_stmt_count = new_stmt_count --- not optimized updates -space:update({1}, {{'=', 5, 10}}) -- change secondary index field - --- Need a snapshot after each operation to avoid purging some --- statements in vy_write_iterator during dump. - -box.snapshot() -dump_count = wait_for_dump(index, dump_count) -space:update({1}, {{'!', 4, 20}}) -- move range containing index field -box.snapshot() -dump_count = wait_for_dump(index, dump_count) -space:update({1}, {{'#', 3, 1}}) -- same -box.snapshot() -dump_count = wait_for_dump(index, dump_count) -new_stmt_count = dumped_stmt_count() -new_stmt_count - old_stmt_count == 9 -old_stmt_count = new_stmt_count -space:select{} -index2:select{} - --- optimized updates -space:update({2}, {{'=', 6, 10}}) -- change not indexed field -box.snapshot() -dump_count = wait_for_dump(index, dump_count) --- Move range that doesn't contain indexed fields. -space:update({2}, {{'!', 7, 20}}) -box.snapshot() -dump_count = wait_for_dump(index, dump_count) -space:update({2}, {{'#', 6, 1}}) -- same -box.snapshot() -dump_count = wait_for_dump(index, dump_count) -new_stmt_count = dumped_stmt_count() -new_stmt_count - old_stmt_count == 3 -old_stmt_count = new_stmt_count -space:select{} -index2:select{} -space:drop() - --- optimize two indexes - -space = box.schema.space.create('test', { engine = 'vinyl' }) -index = space:create_index('primary', { parts = {2, 'unsigned'}, run_count_per_level = 20 } ) -index2 = space:create_index('secondary', { parts = {4, 'unsigned', 3, 'unsigned'}, run_count_per_level = 20 }) -index3 = space:create_index('third', { parts = {5, 'unsigned'}, run_count_per_level = 20 }) -function dumped_stmt_count() return index:stat().disk.dump.output.rows + index2:stat().disk.dump.output.rows + index3:stat().disk.dump.output.rows end -box.snapshot() -dump_count = index:stat().run_count -old_stmt_count = dumped_stmt_count() -space:insert({1, 2, 3, 4, 5}) -space:insert({2, 3, 4, 5, 6}) -space:insert({3, 4, 5, 6, 7}) -space:insert({4, 5, 6, 7, 8}) -box.snapshot() -dump_count = wait_for_dump(index, dump_count) -new_stmt_count = dumped_stmt_count() -new_stmt_count - old_stmt_count == 12 -old_stmt_count = new_stmt_count - --- not optimizes updates -index:update({2}, {{'+', 1, 10}, {'+', 3, 10}, {'+', 4, 10}, {'+', 5, 10}}) -- change all fields -box.snapshot() -dump_count = wait_for_dump(index, dump_count) -index:update({2}, {{'!', 3, 20}}) -- move range containing all indexes -box.snapshot() -dump_count = wait_for_dump(index, dump_count) -index:update({2}, {{'=', 7, 100}, {'+', 5, 10}, {'#', 3, 1}}) -- change two cols but then move range with all indexed fields -box.snapshot() -dump_count = wait_for_dump(index, dump_count) -new_stmt_count = dumped_stmt_count() -new_stmt_count - old_stmt_count == 15 -old_stmt_count = new_stmt_count -space:select{} -index2:select{} -index3:select{} - --- optimize one 'secondary' index update -index:update({3}, {{'+', 1, 10}, {'-', 5, 2}, {'!', 6, 100}}) -- change only index 'third' -box.snapshot() -dump_count = wait_for_dump(index, dump_count) -new_stmt_count = dumped_stmt_count() -new_stmt_count - old_stmt_count == 3 -old_stmt_count = new_stmt_count --- optimize one 'third' index update -index:update({3}, {{'=', 1, 20}, {'+', 3, 5}, {'=', 4, 30}, {'!', 6, 110}}) -- change only index 'secondary' -box.snapshot() -dump_count = wait_for_dump(index, dump_count) -new_stmt_count = dumped_stmt_count() -new_stmt_count - old_stmt_count == 3 -old_stmt_count = new_stmt_count --- optimize both indexes -index:update({3}, {{'+', 1, 10}, {'#', 6, 1}}) -- don't change any indexed fields -box.snapshot() -dump_count = wait_for_dump(index, dump_count) -new_stmt_count = dumped_stmt_count() -new_stmt_count - old_stmt_count == 1 -old_stmt_count = new_stmt_count -space:select{} -index2:select{} -index3:select{} - --- --- gh-1716: optimize UPDATE with fieldno > 64. --- --- Create a big tuple. -long_tuple = {} -for i = 1, 70 do long_tuple[i] = i end -_ = space:replace(long_tuple) -box.snapshot() - --- Make update of not indexed field with pos > 64. -dump_count = wait_for_dump(index, dump_count) -old_stmt_count = dumped_stmt_count() -_ = index:update({2}, {{'=', 65, 1000}}) -box.snapshot() - --- Check the only primary index to be changed. -dump_count = wait_for_dump(index, dump_count) -new_stmt_count = dumped_stmt_count() -new_stmt_count - old_stmt_count == 1 -old_stmt_count = new_stmt_count -space:get{2}[65] - --- --- Try to optimize update with negative field numbers. --- -index:update({2}, {{'#', -65, 65}}) -box.snapshot() -dump_count = wait_for_dump(index, dump_count) -new_stmt_count = dumped_stmt_count() -new_stmt_count - old_stmt_count == 1 -old_stmt_count = new_stmt_count -index:select{} -index2:select{} -index3:select{} - --- Optimize index2 with negative update op. -space:replace{10, 20, 30, 40, 50} -box.snapshot() -dump_count = wait_for_dump(index, dump_count) -old_stmt_count = dumped_stmt_count() - -index:update({20}, {{'=', -1, 500}}) -box.snapshot() -dump_count = wait_for_dump(index, dump_count) -new_stmt_count = dumped_stmt_count() --- 3 = REPLACE in index1 and DELETE + REPLACE in index3. -new_stmt_count - old_stmt_count == 3 -old_stmt_count = new_stmt_count -index:select{} -index2:select{} -index3:select{} - --- Check if optimizes update do not skip the entire key during --- dump. -space:replace{10, 100, 1000, 10000, 100000, 1000000} -index:update({100}, {{'=', 6, 1}}) -box.begin() -space:replace{20, 200, 2000, 20000, 200000, 2000000} -index:update({200}, {{'=', 6, 2}}) -box.commit() -box.snapshot() -dump_count = wait_for_dump(index, dump_count) -old_stmt_count = dumped_stmt_count() -index:select{} -index2:select{} -index3:select{} - --- --- gh-2980: key uniqueness is not checked if indexed fields --- are not updated. --- -space:truncate() -space:replace{1, 1, 1, 1, 1} - -LOOKUPS_BASE = {0, 0, 0} -test_run:cmd("setopt delimiter ';'") -function lookups() - local ret = {} - for i = 1, #LOOKUPS_BASE do - local info = space.index[i - 1]:stat() - table.insert(ret, info.lookup - LOOKUPS_BASE[i]) - end - return ret -end; -test_run:cmd("setopt delimiter ''"); -LOOKUPS_BASE = lookups() - --- update of a field that is not indexed -space:update(1, {{'+', 1, 1}}) -lookups() - --- update of a field indexed by space.index[1] -space:update(1, {{'+', 3, 1}}) -lookups() - --- update of a field indexed by space.index[2] -space:update(1, {{'+', 5, 1}}) -lookups() - -space:drop() - --- --- gh-3607: phantom tuples in secondary index if UPDATE does not --- change key fields. --- -s = box.schema.space.create('test', {engine = 'vinyl'}) -_ = s:create_index('pk') -_ = s:create_index('sk', {parts = {2, 'unsigned'}, run_count_per_level = 10}) - -s:insert{1, 10} --- Some padding to prevent last-level compaction (gh-3657). -for i = 1001, 1010 do s:replace{i, i} end -box.snapshot() - -s:update(1, {{'=', 2, 10}}) -s:delete(1) -box.snapshot() - --- Should be 12: INSERT{10, 1} and INSERT[1001..1010] in the first run --- plus DELETE{10, 1} in the second run. -s.index.sk:stat().rows - -s:insert{1, 20} -s.index.sk:select() - -s:drop()