Skip to content

Commit 7965277

Browse files
committed
Log empty or nil select calls
Unlimited select calls on user spaces tend to be dangerous. A critical log entry containing the current stack traceback is created upon unlimited select calls — a user can explicitly request a full scan though by passing fullscan = true to select's options table argument in which a case a log entry will not be created. Tarantool has a similar implementation[1]. 1. tarantool/tarantool#7064 Part of #276
1 parent 20023a9 commit 7965277

File tree

13 files changed

+308
-26
lines changed

13 files changed

+308
-26
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
1010
### Added
1111

1212
### Changed
13+
* Behaviour of unlimited select calls: a critical log entry containing the
14+
current stack traceback is created upon such function calls — an user can
15+
explicitly request a full scan though by passing fullscan=true to select's
16+
options table argument in which a case a log entry will not be created (#276).
1317

1418
### Fixed
1519

README.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ It can be used to convert received tuples to objects via `crud.unflatten_rows` f
2525
For example:
2626

2727
```lua
28-
res, err = crud.select('customers')
28+
res, err = crud.select('customers', nil, {first = 2})
2929
res
3030
---
3131
- metadata:
@@ -399,6 +399,8 @@ where:
399399
if full primary key equal condition is specified
400400
* `timeout` (`?number`) - `vshard.call` timeout (in seconds)
401401
* `fields` (`?table`) - field names for getting only a subset of fields
402+
* `fullscan` (`?boolean`) - if `true` then a critical log entry will be skipped
403+
on unlimited `select`
402404
* `mode` (`?string`, `read` or `write`) - if `write` is specified then `select` is
403405
performed on master
404406
* `prefer_replica` (`?boolean`) - if `true` then the preferred target is one of
@@ -445,8 +447,9 @@ See more examples of select queries [here.](https://github.com/tarantool/crud/bl
445447
### Pairs
446448

447449
You can iterate across a distributed space using the `crud.pairs` function.
448-
Its arguments are the same as [`crud.select`](#select) arguments,
449-
but negative `first` values aren't allowed.
450+
Its arguments are the same as [`crud.select`](#select) arguments except
451+
`fullscan` (it does not exist because `crud.pairs` does not generate a critical
452+
log entry on empty or nil conditions) and negative `first` values aren't allowed.
450453
User could pass use_tomap flag (false by default) to iterate over flat tuples or objects.
451454

452455
**Example:**
@@ -582,7 +585,7 @@ Returns number or nil with error.
582585
Using `memtx`:
583586

584587
```lua
585-
#crud.select('customers')
588+
#crud.select('customers', nil, {fullscan = true})
586589
---
587590
- 5
588591
...

crud/ratelimit.lua

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
-- Mostly it's a copy-paste from tarantool/tarantool log.lua:
2+
-- https://github.com/tarantool/tarantool/blob/master/src/lua/log.lua
3+
--
4+
-- We have three reasons for the copy-paste:
5+
-- 1. Tarantool has not log.crit() (a function for logging with CRIT level).
6+
-- 2. Only new versions of Tarantool have Ratelimit type.
7+
-- 3. We want own copy of Ratelimit in case the implementation in Tarantool
8+
-- changes. Less pain between Tarantool versions.
9+
local ffi = require('ffi')
10+
11+
local S_CRIT = ffi.C.S_CRIT
12+
local S_WARN = ffi.C.S_WARN
13+
14+
local function say(level, fmt, ...)
15+
if ffi.C.log_level < level then
16+
-- don't waste cycles on debug.getinfo()
17+
return
18+
end
19+
local type_fmt = type(fmt)
20+
local format = "%s"
21+
if select('#', ...) ~= 0 then
22+
local stat
23+
stat, fmt = pcall(string.format, fmt, ...)
24+
if not stat then
25+
error(fmt, 3)
26+
end
27+
elseif type_fmt == 'table' then
28+
-- An implementation in tarantool/tarantool supports encoding a table in
29+
-- JSON, but it requires more dependencies from FFI. So we just deleted
30+
-- it because we don't need such encoding in the module.
31+
error("table not supported", 3)
32+
elseif type_fmt ~= 'string' then
33+
fmt = tostring(fmt)
34+
end
35+
36+
local debug = require('debug')
37+
local frame = debug.getinfo(3, "Sl")
38+
local line, file = 0, 'eval'
39+
if type(frame) == 'table' then
40+
line = frame.currentline or 0
41+
file = frame.short_src or frame.src or 'eval'
42+
end
43+
44+
ffi.C._say(level, file, line, nil, format, fmt)
45+
end
46+
47+
local Ratelimit = {
48+
interval = 60,
49+
burst = 10,
50+
emitted = 0,
51+
suppressed = 0,
52+
start = 0,
53+
}
54+
55+
function Ratelimit:new(object)
56+
object = object or {}
57+
setmetatable(object, self)
58+
self.__index = self
59+
return object
60+
end
61+
62+
function Ratelimit:check()
63+
local clock = require('clock')
64+
65+
local now = clock.monotonic()
66+
local saved_suppressed = 0
67+
if now > self.start + self.interval then
68+
saved_suppressed = self.suppressed
69+
self.suppressed = 0
70+
self.emitted = 0
71+
self.start = now
72+
end
73+
74+
if self.emitted < self.burst then
75+
self.emitted = self.emitted + 1
76+
return saved_suppressed, true
77+
end
78+
self.suppressed = self.suppressed + 1
79+
return saved_suppressed, false
80+
end
81+
82+
function Ratelimit:log_check(lvl)
83+
local suppressed, ok = self:check()
84+
if lvl >= S_WARN and suppressed > 0 then
85+
say(S_WARN, '%d messages suppressed due to rate limiting', suppressed)
86+
end
87+
return ok
88+
end
89+
90+
function Ratelimit:log(lvl, fmt, ...)
91+
if self:log_check(lvl) then
92+
say(lvl, fmt, ...)
93+
end
94+
end
95+
96+
local function log_ratelimited_closure(lvl)
97+
return function(self, fmt, ...)
98+
self:log(lvl, fmt, ...)
99+
end
100+
end
101+
102+
Ratelimit.log_crit = log_ratelimited_closure(S_CRIT)
103+
104+
return Ratelimit

crud/select/compat/common.lua

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,36 @@
1+
local ratelimit = require('crud.ratelimit')
2+
local check_select_args_rl = ratelimit:new()
3+
14
local common = {}
25

36
common.SELECT_FUNC_NAME = '_crud.select_on_storage'
47
common.DEFAULT_BATCH_SIZE = 100
58

9+
common.check_select_args = function(space_name, index_id, user_conditions, opts)
10+
if index_id ~= 0 then
11+
return
12+
end
13+
14+
if opts.first ~= nil and math.abs(opts.first) <= 1000 then
15+
return
16+
end
17+
18+
if opts.fullscan ~= nil and opts.fullscan == true then
19+
return
20+
end
21+
22+
if user_conditions ~= nil then
23+
for _, v in ipairs(user_conditions) do
24+
local it = v[1]
25+
if it ~= nil and type(it) == 'string' and (it == '=' or it == '==') then
26+
return
27+
end
28+
end
29+
end
30+
31+
local rl = check_select_args_rl
32+
local traceback = debug.traceback()
33+
rl:log_crit("unlimited `select` call on space=%s\n %s", space_name, traceback)
34+
end
35+
636
return common

crud/select/compat/select.lua

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ local function build_select_iterator(space_name, user_conditions, opts)
178178
end
179179

180180
return {
181+
plan = plan,
181182
tuples_limit = tuples_limit,
182183
merger = merger,
183184
space_format = filtered_space_format,
@@ -252,14 +253,16 @@ local function select_module_call_xc(space_name, user_conditions, opts)
252253
checks('string', '?table', {
253254
after = '?table|cdata',
254255
first = '?number',
255-
timeout = '?number',
256256
batch_size = '?number',
257257
bucket_id = '?number|cdata',
258258
force_map_call = '?boolean',
259259
fields = '?table',
260+
fullscan = '?boolean',
261+
262+
mode = '?vshard_call_mode',
260263
prefer_replica = '?boolean',
261264
balance = '?boolean',
262-
mode = '?vshard_call_mode',
265+
timeout = '?number',
263266
})
264267

265268
opts = opts or {}
@@ -292,6 +295,7 @@ local function select_module_call_xc(space_name, user_conditions, opts)
292295
if err ~= nil then
293296
return nil, err
294297
end
298+
common.check_select_args(space_name, iter.plan.index_id, user_conditions, opts)
295299

296300
local tuples = {}
297301

crud/select/compat/select_old.lua

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,7 @@ local function select_module_call_xc(space_name, user_conditions, opts)
332332
if err ~= nil then
333333
return nil, err
334334
end
335+
common.check_select_args(space_name, iter.plan.index_id, user_conditions, opts)
335336

336337
local tuples = {}
337338

@@ -366,14 +367,16 @@ function select_module.call(space_name, user_conditions, opts)
366367
checks('string', '?table', {
367368
after = '?table',
368369
first = '?number',
369-
timeout = '?number',
370370
batch_size = '?number',
371371
bucket_id = '?number|cdata',
372372
force_map_call = '?boolean',
373373
fields = '?table',
374+
fullscan = '?boolean',
375+
376+
mode = '?vshard_call_mode',
374377
prefer_replica = '?boolean',
375378
balance = '?boolean',
376-
mode = '?vshard_call_mode',
379+
timeout = '?number',
377380
})
378381

379382
return sharding.wrap_method(select_module_call_xc, space_name, user_conditions, opts)

doc/pairs.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
# Pairs examples
22

33
With ``crud.pairs``, you can iterate across a distributed space.
4-
The arguments are the same as [``crud.select``](https://github.com/tarantool/crud/blob/master/doc/select.md), except of the ``use_tomap`` parameter.
4+
The arguments are the same as [``crud.select``](https://github.com/tarantool/crud/blob/master/doc/select.md)
5+
arguments except ``fullscan`` (it does not exist because ``crud.pairs`` does
6+
not generate a critical log entry on empty or nil conditions) and negative
7+
``first`` values aren't allowed.
8+
User could pass ``use_tomap`` flag (false by default) to iterate over flat tuples or objects.
59
Below are examples that may help you.
610
Examples schema is similar to the [select documentation](select.md/#examples-space-format)
711

doc/select.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ Using ``after``, we can get the objects after specified tuple.
249249
**Example:**
250250

251251
```lua
252-
res, err = crud.select('developers', nil, { after = res.rows[3] })
252+
res, err = crud.select('developers', nil, { after = res.rows[3], first = 5 })
253253
res
254254
---
255255
- metadata:

test/integration/read_calls_strategies_test.lua

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ pgroup.test_select = function(g)
7878
local _, err = g.cluster.main_server.net_box:call('crud.select', {'customers', nil, {
7979
mode = g.params.mode,
8080
balance = g.params.balance,
81-
prefer_replica = g.params.prefer_replica
81+
prefer_replica = g.params.prefer_replica,
82+
fullscan = true
8283
}})
8384
t.assert_equals(err, nil)
8485
local vshard_calls = g.get_vshard_calls('call_impl')

test/integration/reload_test.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ function g.test_router()
9393

9494
g.highload_fiber:cancel()
9595

96-
local result, err = g.router.net_box:call('crud.select', {'customers'})
96+
local result, err = g.router.net_box:call('crud.select', {'customers', nil, {fullscan = true}})
9797
t.assert_equals(err, nil)
9898
t.assert_items_include(result.rows, g.insertions_passed)
9999
end
@@ -122,7 +122,7 @@ function g.test_storage()
122122

123123
g.highload_fiber:cancel()
124124

125-
local result, err = g.router.net_box:call('crud.select', {'customers'})
125+
local result, err = g.router.net_box:call('crud.select', {'customers', nil, {fullscan = true}})
126126
t.assert_equals(err, nil)
127127
t.assert_items_include(result.rows, g.insertions_passed)
128128
end

0 commit comments

Comments
 (0)