@@ -45,7 +45,13 @@ local constants = {
45
45
-- factor for recalculation of vinyl space size
46
46
default_vinyl_assumed_space_len_factor = 2 ,
47
47
-- default function on full scan
48
- default_on_full_scan = function (...) end
48
+ default_on_full_scan = function (...) end ,
49
+ -- default function for process_while
50
+ default_process_while = function (...) return true end ,
51
+ -- default iterating over the loop will go in ascending index
52
+ iterator = " GE" ,
53
+ -- default atomic_iteration is false, batch of items doesn't include in one transaction
54
+ atomic_iteration = false
49
55
}
50
56
51
57
-- ========================================================================= --
@@ -57,10 +63,10 @@ local constants = {
57
63
-- ------------------------------------------------------------------------- --
58
64
59
65
-- get all fields in primary key(composite possible) from tuple
60
- local function construct_key (space_id , tuple )
66
+ local function construct_key (expire_index , tuple )
61
67
return fun .map (
62
68
function (x ) return tuple [x .fieldno ] end ,
63
- box . space [ space_id ]. index [ 0 ] .parts
69
+ expire_index .parts
64
70
):totable ()
65
71
end
66
72
@@ -69,95 +75,132 @@ local function expiration_process(task, tuple)
69
75
task .checked_tuples_count = task .checked_tuples_count + 1
70
76
if task .is_tuple_expired (task .args , tuple ) then
71
77
task .expired_tuples_count = task .expired_tuples_count + 1
72
- task .process_expired_tuple (task .space_id , task .args , tuple )
78
+ task .process_expired_tuple (task .space_id , task .args , tuple , task )
73
79
end
74
80
end
75
81
76
82
-- stop for some time
77
- local function suspend_basic (scan_space , task , len )
83
+ local function suspend_basic (task , len )
78
84
local delay = (task .tuples_per_iteration * task .full_scan_time )
79
85
delay = math.min (delay / len , task .iteration_delay )
80
86
fiber .sleep (delay )
81
87
end
82
88
83
- local function suspend (scan_space , task )
89
+ local function suspend (task )
84
90
-- Return the number of tuples in the space
85
- local space_len = scan_space :len ()
91
+ local space_len = task . expire_index :len ()
86
92
if space_len > 0 then
87
- suspend_basic (scan_space , task , space_len )
93
+ suspend_basic (task , space_len )
88
94
end
89
95
end
90
96
91
97
-- delete with some suspend and some tuples limit
92
- local function tree_index_iter (scan_space , task )
98
+ local function tree_index_iter (task )
93
99
-- iteration with GT iterator
94
- local params = {iterator = ' GT ' , limit = task .tuples_per_iteration }
100
+ local params = {iterator = task . iterator , limit = task .tuples_per_iteration }
95
101
local last_id
96
- local tuples = scan_space . index [ 0 ] :select ({} , params )
102
+ local tuples = task . expire_index :select (task . start_key , params )
97
103
while # tuples > 0 do
98
104
last_id = tuples [# tuples ]
105
+ if task .atomic_iteration then
106
+ box .begin ()
107
+ end
99
108
for _ , tuple in ipairs (tuples ) do
109
+ if not task :process_while () then goto done end
100
110
expiration_process (task , tuple )
101
111
end
102
- suspend (scan_space , task )
103
- local key = construct_key (scan_space .id , last_id )
112
+ if task .atomic_iteration then
113
+ box .commit ()
114
+ end
115
+ suspend (task )
116
+ local key = construct_key (task .expire_index , last_id )
104
117
-- select all greater then last key
105
- tuples = scan_space .index [0 ]:select (key , params )
118
+ tuples = task .expire_index :select (key , {iterator = ' GT' , limit = task .tuples_per_iteration })
119
+ end
120
+ :: done::
121
+ if task .atomic_iteration then
122
+ box .commit ()
106
123
end
107
-
108
124
end
109
125
110
- local function hash_index_iter (scan_space , task )
126
+ local function hash_index_iter (task )
111
127
-- iteration for hash index
112
128
local checked_tuples_count = 0
113
- for _ , tuple in scan_space .index [0 ]:pairs (nil , {iterator = box .index .ALL }) do
129
+ if task .atomic_iteration then
130
+ box .begin ()
131
+ end
132
+ for _ , tuple in task .expire_index :pairs (nil , {iterator = box .index .ALL }) do
114
133
checked_tuples_count = checked_tuples_count + 1
134
+ if not task :process_while () then break end
115
135
expiration_process (task , tuple )
116
136
-- find out if the worker can go to sleep
117
137
if checked_tuples_count >= task .tuples_per_iteration then
138
+ if task .atomic_iteration then
139
+ box .commit ()
140
+ end
118
141
checked_tuples_count = 0
119
- suspend (scan_space , task )
142
+ suspend (task )
143
+ if task .atomic_iteration then
144
+ box .begin ()
145
+ end
120
146
end
121
147
end
148
+ if task .atomic_iteration then
149
+ box .commit ()
150
+ end
122
151
end
123
152
124
153
local function default_do_worker_iteration (task )
125
- local scan_space = box .space [task .space_id ]
126
- local index_type = scan_space .index [0 ].type
154
+ local index_type = task .expire_index .type
127
155
128
156
-- full index scan loop
129
157
if index_type == ' HASH' then
130
- hash_index_iter (scan_space , task )
158
+ hash_index_iter (task )
131
159
else
132
- tree_index_iter (scan_space , task )
160
+ tree_index_iter (task )
133
161
end
134
162
end
135
163
136
164
local function vinyl_do_worker_iteration (task )
137
- local scan_space = box .space [task .space_id ]
138
-
139
165
local checked_tuples_count = 0
140
166
local space_len = task .vinyl_assumed_space_len
141
167
142
- local params = {iterator = ' GT ' , limit = task .tuples_per_iteration }
143
- local tuples = scan_space . index [ 0 ] :select ({} , params )
168
+ local params = {iterator = task . iterator , limit = task .tuples_per_iteration }
169
+ local tuples = task . expire_index :select (task . start_key , params )
144
170
while true do
145
171
local tuple_cnt = # tuples
146
172
if tuple_cnt == 0 then
147
173
break
148
174
end
149
175
local last_id = nil
176
+ if task .atomic_iteration then
177
+ box .begin ()
178
+ end
150
179
for _ , tuple in ipairs (tuples ) do
151
180
last_id = tuple
181
+ if not task :process_while () then
182
+ checked_tuples_count = checked_tuples_count + tuple_cnt
183
+ if checked_tuples_count > space_len then
184
+ space_len = task .vinyl_assumed_space_len_factor * space_len
185
+ end
186
+ goto done
187
+ end
152
188
expiration_process (task , tuple )
153
189
end
190
+ if task .atomic_iteration then
191
+ box .commit ()
192
+ end
154
193
checked_tuples_count = checked_tuples_count + tuple_cnt
155
194
if checked_tuples_count > space_len then
156
195
space_len = task .vinyl_assumed_space_len_factor * space_len
157
196
end
158
- local key = construct_key (scan_space .id , last_id )
159
- suspend_basic (scan_space , task , space_len )
160
- tuples = scan_space .index [0 ]:select (key , params )
197
+ local key = construct_key (task .expire_index , last_id )
198
+ suspend_basic (task , space_len )
199
+ tuples = task .expire_index :select (key , {iterator = " GT" , limit = task .tuples_per_iteration })
200
+ end
201
+ :: done::
202
+ if task .atomic_iteration then
203
+ box .commit ()
161
204
end
162
205
task .vinyl_assumed_space_len = checked_tuples_count
163
206
end
@@ -267,6 +310,8 @@ local function create_task(name)
267
310
is_tuple_expired = nil ,
268
311
process_expired_tuple = nil ,
269
312
args = nil ,
313
+ expire_index = nil ,
314
+ start_key = nil ,
270
315
iteration_delay = constants .max_delay ,
271
316
full_scan_delay = constants .max_delay ,
272
317
tuples_per_iteration = constants .default_tuples_per_iteration ,
@@ -276,7 +321,10 @@ local function create_task(name)
276
321
on_full_scan_error = constants .default_on_full_scan ,
277
322
on_full_scan_success = constants .default_on_full_scan ,
278
323
on_full_scan_start = constants .default_on_full_scan ,
279
- on_full_scan_complete = constants .default_on_full_scan
324
+ on_full_scan_complete = constants .default_on_full_scan ,
325
+ process_while = constants .default_process_while ,
326
+ iterator = constants .iterator ,
327
+ atomic_iteration = constants .atomic_iteration
280
328
}, { __index = Task_methods })
281
329
return task
282
330
end
@@ -296,8 +344,9 @@ local function get_task(name)
296
344
end
297
345
298
346
-- default process_expired_tuple function
299
- local function default_tuple_drop (space_id , args , tuple )
300
- box .space [space_id ]:delete (construct_key (space_id , tuple ))
347
+ -- task as parameter was added to preserve backward compatibility
348
+ local function default_tuple_drop (space_id , args , tuple , task )
349
+ task .expire_index :delete (construct_key (task .expire_index , tuple ))
301
350
end
302
351
303
352
315
364
-- options = { -- (table with named options)
316
365
-- * process_expired_tuple -- applied to expired tuples, receives
317
366
-- (space_id, args, tuple) as arguments
367
+ -- * index -- name of index, need to have an index on this name (default primary index)
368
+ -- * atomic_iteration -- boolean, true if we want put all items from batch in one transaction
369
+ -- * iterator -- iterator which can be used at the given index
370
+ -- * start_key -- must be the same data type as index field or fields (default nil)
371
+ -- * process_while -- call function before check is tuple expired, if false the iteration will be stopped
318
372
-- * on_full_scan_start -- call function on starting full scan iteration
319
373
-- * on_full_scan_complete -- call function on complete full scan iteration
320
374
-- * on_full_scan_success -- call function on success full scan iteration
@@ -366,21 +420,108 @@ local function expirationd_run_task(name, space_id, is_tuple_expired, options)
366
420
end
367
421
task .process_expired_tuple = options .process_expired_tuple or default_tuple_drop
368
422
423
+ -- validate index
424
+ local expire_index = box .space [space_id ].index [0 ]
425
+ if options .index then
426
+ local index_exists = false
427
+ for _ , index in pairs (box .space [space_id ].index ) do
428
+ if index .name == options .index then
429
+ expire_index = index
430
+ index_exists = true
431
+ end
432
+ end
433
+ if not index_exists then
434
+ error (" Index with name " .. options .index .. " not exists" )
435
+ end
436
+ end
437
+ task .expire_index = expire_index
438
+
439
+ -- check iterator
440
+ if options .iterator ~= nil then
441
+ if type (options .iterator ) ~= ' string' then
442
+ error (" Invalid type of iterator, expected string" )
443
+ end
444
+ if expire_index .type == ' TREE' then
445
+ if options .iterator ~= ' ALL' and
446
+ options .iterator ~= ' EQ' and
447
+ options .iterator ~= ' REQ' and
448
+ options .iterator ~= ' GT' and
449
+ options .iterator ~= ' GE' and
450
+ options .iterator ~= ' LT' and
451
+ options .iterator ~= ' LE' then
452
+ error (" Invalid iterator for TREE index, expected {ALL, EQ, REQ, GT, GE, LT, LE}" )
453
+ end
454
+ elseif expire_index .type == ' HASH' then
455
+ if options .iterator ~= ' ALL' and
456
+ options .iterator ~= ' EQ' and
457
+ options .iterator ~= ' GT' then
458
+ error (" Invalid iterator for HASH index, expected {ALL, EQ, GT}" )
459
+ end
460
+ end
461
+ task .iterator = options .iterator
462
+ else
463
+ if expire_index .type == ' HASH' then
464
+ task .iterator = " ALL"
465
+ end
466
+ end
467
+
468
+ -- check start_key
469
+ if options .start_key then
470
+ if type (options .start_key ) == " table" then
471
+ if # expire_index .parts ~= # options .start_key then
472
+ error (" Wrong number of elements in start_key table, expected: " .. # expire_index .parts )
473
+ end
474
+ for i = 1 , # expire_index .parts do
475
+ log .info (expire_index .parts [i ].type )
476
+ if expire_index .parts [i ].type ~= type (options .start_key [i ]) then
477
+ error (" Wrong type in " .. i ..
478
+ " element of start_key table, expected: " .. expire_index .parts [i ].type ..
479
+ " , got: " .. type (options .start_key [i ]))
480
+ end
481
+ end
482
+ else
483
+ if # expire_index .parts ~= 1 then
484
+ error (" Wrong number of elements in start_key table, expected: " .. # expire_index .parts )
485
+ end
486
+ if expire_index .parts [1 ].type ~= type (options .start_key ) then
487
+ error (" Wrong type of start_key" )
488
+ end
489
+ end
490
+ task .start_key = options .start_key
491
+ end
492
+
493
+
494
+ -- check process_while
495
+ if options .process_while ~= nil then
496
+ if type (options .process_while ) ~= ' function' then
497
+ error (" Invalid type of process_while is not function" )
498
+ end
499
+ task .process_while = options .process_while
500
+ end
501
+
502
+ -- check transaction
503
+ if options .atomic_iteration ~= nil then
504
+ if type (options .atomic_iteration ) ~= ' boolean' then
505
+ error (" Invalid type of atomic_iteration, expected boolean" )
506
+ end
507
+ task .atomic_iteration = options .atomic_iteration
508
+ end
509
+
369
510
-- check expire and process after expiration handler's arguments
370
511
task .args = options .args
371
512
372
513
-- check tuples per iteration (not required)
373
514
if options .tuples_per_iteration ~= nil then
374
515
if options .tuples_per_iteration <= 0 then
375
- error (" invalid tuples per iteration parameter" )
516
+ error (" Invalid tuples per iteration parameter" )
376
517
end
377
518
task .tuples_per_iteration = options .tuples_per_iteration
378
519
end
379
520
380
521
-- check full scan time
381
522
if options .full_scan_time ~= nil then
382
523
if options .full_scan_time <= 0 then
383
- error (" invalid full scan time" )
524
+ error (" Invalid full scan time" )
384
525
end
385
526
task .full_scan_time = options .full_scan_time
386
527
end
0 commit comments