Skip to content

Commit ec4437d

Browse files
committed
gc: make it compatible with memtx mvcc
Bucket garbage collector wakes up on _bucket generation change. It used to happen on each update in _bucket space. When GC fiber wakes up, it deletes the garbage and goes to sleep until next generation change. It didn't work with mvcc because the generation was bumped during replace before commit, GC fiber woke up, saw no changes, and went to sleep until next bump. Then _bucket update would commit and there would be a SENT/GARBAGE bucket visible, but GC fiber wouldn't even try to find it because the generation didn't change since last wakeup. The patch makes so the generation is bumped on commit to _bucket, not on replace. Closes #314
1 parent 22031f3 commit ec4437d

File tree

5 files changed

+98
-11
lines changed

5 files changed

+98
-11
lines changed

test/instances/storage.lua

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ local function get_uuid()
4444
return instance_uuid
4545
end
4646

47+
local function get_first_bucket()
48+
local res = box.space._bucket.index.status:min(vconst.BUCKET.ACTIVE)
49+
return res ~= nil and res.id or nil
50+
end
51+
4752
local function session_set(key, value)
4853
box.session.storage[key] = value
4954
return true
@@ -69,6 +74,7 @@ end
6974
_G.box_error = box_error
7075
_G.echo = echo
7176
_G.get_uuid = get_uuid
77+
_G.get_first_bucket = get_first_bucket
7278
_G.session_set = session_set
7379
_G.session_get = session_get
7480
_G.wait_bucket_gc = wait_bucket_gc

test/luatest_helpers/vtest.lua

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ local t = require('luatest')
22
local helpers = require('test.luatest_helpers')
33
local cluster = require('test.luatest_helpers.cluster')
44
local fiber = require('fiber')
5-
local vconst = require('vshard.consts')
65
local vrepset = require('vshard.replicaset')
76

87
local uuid_idx = 1
@@ -115,6 +114,7 @@ local function storage_new(g, cfg)
115114
box_cfg.listen = helpers.instance_uri(replica.name)
116115
-- Need to specify read-only explicitly to know how is master.
117116
box_cfg.read_only = not replica.master
117+
box_cfg.memtx_use_mvcc_engine = cfg.memtx_use_mvcc_engine
118118
local server = g.cluster:build_server({
119119
alias = name,
120120
box_cfg = box_cfg,
@@ -343,10 +343,9 @@ end
343343
-- where the buckets are located by hardcoded numbers and uuids.
344344
--
345345
local function storage_first_bucket(storage)
346-
return storage:exec(function(status)
347-
local res = box.space._bucket.index.status:min(status)
348-
return res ~= nil and res.id or nil
349-
end, {vconst.BUCKET.ACTIVE})
346+
return storage:exec(function()
347+
return _G.get_first_bucket()
348+
end)
350349
end
351350

352351
--

test/storage-luatest/garbage_collector_test.lua

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
11
local t = require('luatest')
22
local vtest = require('test.luatest_helpers.vtest')
3+
local vutil = require('vshard.util')
34
local wait_timeout = 120
45

5-
local test_group = t.group('bucket_gc', {
6-
{engine = 'memtx'}, {engine = 'vinyl'}
7-
})
6+
local group_config = {{engine = 'memtx'}, {engine = 'vinyl'}}
7+
8+
if vutil.feature.memtx_mvcc then
9+
table.insert(group_config, {
10+
engine = 'memtx', memtx_use_mvcc_engine = true
11+
})
12+
table.insert(group_config, {
13+
engine = 'vinyl', memtx_use_mvcc_engine = true
14+
})
15+
end
16+
17+
local test_group = t.group('bucket_gc', group_config)
18+
819
local cfg_template = {
920
sharding = {
1021
{
@@ -18,9 +29,12 @@ local cfg_template = {
1829
},
1930
bucket_count = 20
2031
}
21-
local cluster_cfg = vtest.config_new(cfg_template)
32+
local cluster_cfg
2233

2334
test_group.before_all(function(g)
35+
cfg_template.memtx_use_mvcc_engine = g.params.memtx_use_mvcc_engine
36+
cluster_cfg = vtest.config_new(cfg_template)
37+
2438
vtest.storage_new(g, cluster_cfg)
2539
vtest.storage_bootstrap(g, cluster_cfg)
2640
vtest.storage_exec_each_master(g, function(engine)
@@ -137,3 +151,51 @@ test_group.test_basic = function(g)
137151
ivshard.storage.internal.collect_bucket_garbage_fiber, nil)
138152
end)
139153
end
154+
155+
test_group.test_yield_before_send_commit = function(g)
156+
t.run_only_if(cluster_cfg.memtx_use_mvcc_engine)
157+
158+
g.replica_1_a:exec(function(timeout)
159+
local s = box.space.test
160+
local bucket_space = box.space._bucket
161+
local bid = _G.get_first_bucket()
162+
ilt.assert_not_equals(bid, nil, 'get any bucket')
163+
164+
-- Fill a bucket with some data.
165+
local tuple_count = 10
166+
box.begin()
167+
for i = 1, tuple_count do
168+
s:replace{i, bid}
169+
end
170+
box.commit()
171+
172+
-- Start its "sending" but yield before commit.
173+
local is_send_blocked = true
174+
local f_send = ifiber.new(function()
175+
box.begin()
176+
bucket_space:update({bid}, {{'=', 2, ivconst.BUCKET.SENT}})
177+
while is_send_blocked do
178+
ifiber.sleep(0.01)
179+
end
180+
box.commit()
181+
end)
182+
f_send:set_joinable(true)
183+
ifiber.sleep(0.01)
184+
ilt.assert_equals(bucket_space:get{bid}.status, ivconst.BUCKET.ACTIVE,
185+
'sending is not visible yet')
186+
is_send_blocked = false
187+
ilt.assert(f_send:join(), 'long send succeeded')
188+
189+
-- Bucket GC should react on commit. Not wakeup on replace, notice no
190+
-- changes, and go to sleep.
191+
_G.wait_bucket_gc(timeout)
192+
ilt.assert_equals(s:count(), 0, 'no garbage data')
193+
194+
-- Restore the bucket for next tests.
195+
ivshard.storage.bucket_force_create(bid)
196+
s:truncate()
197+
end, {wait_timeout})
198+
199+
-- Ensure the replica received all that and didn't break.
200+
g.replica_1_b:wait_vclock_of(g.replica_1_a)
201+
end

vshard/storage/init.lua

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,22 @@ local function bucket_generation_increment()
291291
M.bucket_generation_cond:broadcast()
292292
end
293293

294+
local function bucket_on_replace_f()
295+
local f = bucket_generation_increment
296+
local f_del = f
297+
-- One transaction can affect many buckets. Do not create a trigger for each
298+
-- bucket. Instead create it only first time. For that the trigger replaces
299+
-- itself when installed not first time.
300+
-- Although in case of hot reload during changing _bucket the function
301+
-- pointer will change and more than one trigger could be installed.
302+
-- Shouldn't matter anyway.
303+
if not pcall(box.on_commit, f, f) then
304+
box.on_commit(f)
305+
f_del = nil
306+
end
307+
box.on_rollback(f, f_del)
308+
end
309+
294310
local function bucket_generation_wait(timeout)
295311
return fiber_cond_wait(M.bucket_generation_cond, timeout)
296312
end
@@ -430,8 +446,8 @@ local function schema_install_triggers()
430446
'_bucket: %s', err)
431447
end
432448
end
433-
_bucket:on_replace(bucket_generation_increment)
434-
M.bucket_on_replace = bucket_generation_increment
449+
_bucket:on_replace(bucket_on_replace_f)
450+
M.bucket_on_replace = bucket_on_replace_f
435451
end
436452

437453
local function schema_install_on_replace(_, new)

vshard/util.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,10 @@ local feature = {
284284
msgpack_object = lmsgpack.object ~= nil,
285285
netbox_return_raw = version_is_at_least(2, 10, 0, 'beta', 2, 86),
286286
multilisten = luri.parse_many ~= nil,
287+
-- It appeared earlier but at 2.9.0 there is a strange bug about an immortal
288+
-- tuple - after :delete(pk) it still stays in the space. That makes the
289+
-- feature barely working, can be treated like it didn't exist.
290+
memtx_mvcc = version_is_at_least(2, 10, 0, nil, 0, 0)
287291
}
288292

289293
return {

0 commit comments

Comments
 (0)