From 1babcf1ea49d9c8823a706a36d1a12ef43733d28 Mon Sep 17 00:00:00 2001 From: Ilya Verbin <iverbin@tarantool.org> Date: Tue, 10 Oct 2023 19:12:53 +0300 Subject: [PATCH] box: fix space:bsize() handling on space alter During building an index in background, some transaction can perform a dml request that affects space size (e.g. a replace), but the size will remain the same, because bsize is moved from the old space to the new space in memtx_space_prepare_alter() prior to space_execute_dml(). Fix this issue by calling space_finish_alter() in alter_space_do(). In fact, this patch partially reverts commit 9ec3b1a445a6 ("alter: zap space_vtab::commit_alter"). NO_DOC=bugfix Closes #9247 (cherry picked from commit 54a42186de2776b4fa970ee1a9cf53c543a1cb26) --- ...fix-space-bsize-handling-on-space-alter.md | 4 + src/box/alter.cc | 1 + src/box/blackhole.c | 1 + src/box/memtx_space.c | 20 ++- src/box/session_settings.c | 1 + src/box/space.c | 7 ++ src/box/space.h | 14 +++ src/box/sysview.c | 1 + src/box/vinyl.c | 1 + ...bsize_on_concurrent_dml_and_alter_test.lua | 118 ++++++++++++++++++ 10 files changed, 166 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/gh-9247-fix-space-bsize-handling-on-space-alter.md create mode 100644 test/box-luatest/gh_9247_bsize_on_concurrent_dml_and_alter_test.lua diff --git a/changelogs/unreleased/gh-9247-fix-space-bsize-handling-on-space-alter.md b/changelogs/unreleased/gh-9247-fix-space-bsize-handling-on-space-alter.md new file mode 100644 index 0000000000..e11362fca6 --- /dev/null +++ b/changelogs/unreleased/gh-9247-fix-space-bsize-handling-on-space-alter.md @@ -0,0 +1,4 @@ +## bugfix/core + +* Fixed a bug that could result in the incorrect `space:bsize()` when altering + a primary index concurrently with DML operations (gh-9247). diff --git a/src/box/alter.cc b/src/box/alter.cc index 51abc28fe7..8d8d40a2b8 100644 --- a/src/box/alter.cc +++ b/src/box/alter.cc @@ -1017,6 +1017,7 @@ alter_space_do(struct txn_stmt *stmt, struct alter_space *alter) * The new space is ready. Time to update the space * cache with it. */ + space_finish_alter(alter->old_space, alter->new_space); space_cache_replace(alter->old_space, alter->new_space); space_detach_constraints(alter->old_space); space_unpin_collations(alter->old_space); diff --git a/src/box/blackhole.c b/src/box/blackhole.c index 63b102aae3..83e1d8bb96 100644 --- a/src/box/blackhole.c +++ b/src/box/blackhole.c @@ -126,6 +126,7 @@ static const struct space_vtab blackhole_space_vtab = { /* .build_index = */ generic_space_build_index, /* .swap_index = */ generic_space_swap_index, /* .prepare_alter = */ generic_space_prepare_alter, + /* .finish_alter = */ generic_space_finish_alter, /* .prepare_upgrade = */ generic_space_prepare_upgrade, /* .invalidate = */ generic_space_invalidate, }; diff --git a/src/box/memtx_space.c b/src/box/memtx_space.c index e36d5bf6e6..3f6594bd65 100644 --- a/src/box/memtx_space.c +++ b/src/box/memtx_space.c @@ -1052,7 +1052,6 @@ memtx_space_drop_primary_key(struct space *space) * can be put back online properly. */ memtx_space->replace = memtx_space_replace_no_keys; - memtx_space->bsize = 0; } static void @@ -1369,10 +1368,26 @@ memtx_space_prepare_alter(struct space *old_space, struct space *new_space) } new_memtx_space->replace = old_memtx_space->replace; - new_memtx_space->bsize = old_memtx_space->bsize; return 0; } +/** + * Copy bsize to the newly altered space from the old space. + * In case of DropIndex or TruncateIndex alter operations, the new space will be + * empty, and bsize must not be copied. + */ +static void +memtx_space_finish_alter(struct space *old_space, struct space *new_space) +{ + struct memtx_space *old_memtx_space = (struct memtx_space *)old_space; + struct memtx_space *new_memtx_space = (struct memtx_space *)new_space; + + bool is_empty = new_space->index_count == 0 || + index_size(new_space->index[0]) == 0; + if (!is_empty) + new_memtx_space->bsize = old_memtx_space->bsize; +} + /* }}} DDL */ static const struct space_vtab memtx_space_vtab = { @@ -1395,6 +1410,7 @@ static const struct space_vtab memtx_space_vtab = { /* .build_index = */ memtx_space_build_index, /* .swap_index = */ generic_space_swap_index, /* .prepare_alter = */ memtx_space_prepare_alter, + /* .finish_alter = */ memtx_space_finish_alter, /* .prepare_upgrade = */ memtx_space_prepare_upgrade, /* .invalidate = */ generic_space_invalidate, }; diff --git a/src/box/session_settings.c b/src/box/session_settings.c index 66fa9fc830..9c38cd3f96 100644 --- a/src/box/session_settings.c +++ b/src/box/session_settings.c @@ -437,6 +437,7 @@ const struct space_vtab session_settings_space_vtab = { /* .build_index = */ generic_space_build_index, /* .swap_index = */ generic_space_swap_index, /* .prepare_alter = */ generic_space_prepare_alter, + /* .finish_alter = */ generic_space_finish_alter, /* .prepare_upgrade = */ generic_space_prepare_upgrade, /* .invalidate = */ generic_space_invalidate, }; diff --git a/src/box/space.c b/src/box/space.c index b7fbf6fdbc..4a2a51b7dd 100644 --- a/src/box/space.c +++ b/src/box/space.c @@ -971,6 +971,13 @@ generic_space_prepare_alter(struct space *old_space, struct space *new_space) return 0; } +void +generic_space_finish_alter(struct space *old_space, struct space *new_space) +{ + (void)old_space; + (void)new_space; +} + int generic_space_prepare_upgrade(struct space *old_space, struct space *new_space) { diff --git a/src/box/space.h b/src/box/space.h index 9a101caf0e..930184cf32 100644 --- a/src/box/space.h +++ b/src/box/space.h @@ -144,6 +144,12 @@ struct space_vtab { */ int (*prepare_alter)(struct space *old_space, struct space *new_space); + /** + * Notify the engine after altering a space and before replacing + * old_space with new_space in the space cache. + */ + void (*finish_alter)(struct space *old_space, + struct space *new_space); /** Prepares a space for online upgrade on alter. */ int (*prepare_upgrade)(struct space *old_space, struct space *new_space); @@ -563,6 +569,13 @@ space_prepare_alter(struct space *old_space, struct space *new_space) return new_space->vtab->prepare_alter(old_space, new_space); } +static inline void +space_finish_alter(struct space *old_space, struct space *new_space) +{ + assert(old_space->vtab == new_space->vtab); + new_space->vtab->finish_alter(old_space, new_space); +} + static inline int space_prepare_upgrade(struct space *old_space, struct space *new_space) { @@ -668,6 +681,7 @@ int generic_space_check_format(struct space *, struct tuple_format *); int generic_space_build_index(struct space *, struct index *, struct tuple_format *, bool); int generic_space_prepare_alter(struct space *, struct space *); +void generic_space_finish_alter(struct space *, struct space *); int generic_space_prepare_upgrade(struct space *old_space, struct space *new_space); void generic_space_invalidate(struct space *); diff --git a/src/box/sysview.c b/src/box/sysview.c index 6b3dfd790d..8b655b291d 100644 --- a/src/box/sysview.c +++ b/src/box/sysview.c @@ -505,6 +505,7 @@ static const struct space_vtab sysview_space_vtab = { /* .build_index = */ generic_space_build_index, /* .swap_index = */ generic_space_swap_index, /* .prepare_alter = */ generic_space_prepare_alter, + /* .finish_alter = */ generic_space_finish_alter, /* .prepare_upgrade = */ generic_space_prepare_upgrade, /* .invalidate = */ generic_space_invalidate, }; diff --git a/src/box/vinyl.c b/src/box/vinyl.c index 2cb41a222d..259c1b9f68 100644 --- a/src/box/vinyl.c +++ b/src/box/vinyl.c @@ -4641,6 +4641,7 @@ static const struct space_vtab vinyl_space_vtab = { /* .build_index = */ vinyl_space_build_index, /* .swap_index = */ vinyl_space_swap_index, /* .prepare_alter = */ vinyl_space_prepare_alter, + /* .finish_alter = */ generic_space_finish_alter, /* .prepare_upgrade = */ generic_space_prepare_upgrade, /* .invalidate = */ vinyl_space_invalidate, }; diff --git a/test/box-luatest/gh_9247_bsize_on_concurrent_dml_and_alter_test.lua b/test/box-luatest/gh_9247_bsize_on_concurrent_dml_and_alter_test.lua new file mode 100644 index 0000000000..5fe9a9bdf6 --- /dev/null +++ b/test/box-luatest/gh_9247_bsize_on_concurrent_dml_and_alter_test.lua @@ -0,0 +1,118 @@ +local server = require('luatest.server') +local t = require('luatest') +local g = t.group('gh-9247') + +g.before_all(function(cg) + cg.server = server:new() + cg.server:start() +end) + +g.after_all(function(cg) + cg.server:drop() +end) + +g.after_each(function(cg) + cg.server:exec(function() + box.space.test:drop() + end) +end) + +-- Based on `test/box/alter-primary-index-tuple-leak-long.test.lua'. +local function concurrent_insert_and_alter_helper(cg, rollback) + cg.server:exec(function(rollback) + local fiber = require('fiber') + local s = box.schema.space.create('test') + local idx = s:create_index('pk') + local alter_started = false + local data = string.rep('a', 66) + + -- Create a table with enough tuples to make primary index alter in + -- background. + for i = 0, 7000, 100 do + box.begin() + for j = i, i + 99 do + s:insert{j, j, data} + end + box.commit() + end + + -- Wait for fiber yield during altering and insert some tuples. + local fib_insert = fiber.new(function() + while not alter_started do + fiber.sleep(0) + end + box.begin() + for i = 8000, 9000 do + s:insert{i, i, data} + end + box.commit() + end) + + -- Alter primary index. + -- If the index will not be altered in background, the test will fail + -- after timeout (fib_insert:join() will wait forever). + local fib_alter = fiber.new(function() + alter_started = true + box.begin() + idx:alter{parts = {{field = 2}}} + if rollback then + box.rollback() + else + box.commit() + end + alter_started = false + end) + + fib_insert:set_joinable(true) + fib_alter:set_joinable(true) + fib_insert:join() + fib_alter:join() + + local bsize = 0 + for _, tuple in s:pairs() do + bsize = bsize + tuple:bsize() + end + t.assert_equals(s:bsize(), bsize) + end, {rollback}) +end + +-- Check that space:bsize() is correct for a built-in-background primary index +-- with concurrent inserts. +g.test_concurrent_insert_and_alter_commit = function(cg) + concurrent_insert_and_alter_helper(cg, false) +end + +-- Check that space:bsize() is correct for a built-in-background primary index +-- with concurrent inserts. +g.test_concurrent_insert_and_alter_rollback = function(cg) + concurrent_insert_and_alter_helper(cg, true) +end + +local function truncate_helper(cg, rollback) + cg.server:exec(function(rollback) + local s = box.schema.space.create('test') + s:create_index('pk') + s:insert{1, 2, 3} + local bsize_before_truncate = s:bsize() + + box.begin() + s:truncate() + if rollback then + box.rollback() + t.assert_equals(s:bsize(), bsize_before_truncate) + else + box.commit() + t.assert_equals(s:bsize(), 0) + end + end, {rollback}) +end + +-- Check that space:bsize() is correct after successful space:truncate(). +g.test_truncate_commit = function(cg) + truncate_helper(cg, false) +end + +-- Check that space:bsize() is correct after rolled back space:truncate(). +g.test_truncate_rollback = function(cg) + truncate_helper(cg, true) +end -- GitLab