Skip to content

Commit ff77094

Browse files
committed
Fix query limit
Previously, we set a default limit that was an effective infinity (2^28). It seems back in the 32 bit days that was the Erlang's largest small integer [1]. However, that turned out to be too low and it surprised a user when it truncated their all_docs output skipping some of the data. Fix that by increasing the limit to a larger "infinity" (highest 64 bit Erlang small integer [1]). We did have a "query_limit" config parameter to customize the limit, however that turned out to be broken and did not take effect when the user tried it for all_docs, so fix that as well. Fix that and use a test to ensure the limit gets reduced appropriately. To make the setting more user friendly, allow `infinity` as the value. Also, in the case of all_docs, we validated args and applied the limit check twice: once in the coordinator and another time on each worker, which wasted CPU resources and made things a bit confusing. To fix that, remove the validation from the common worker code in couch_mrview and validate once: either on the coordinator side, or local (port 5986) callback, right in the http callback. [1] https://www.erlang.org/doc/system/memory.html Fix #5176
1 parent 5facb7b commit ff77094

File tree

9 files changed

+95
-24
lines changed

9 files changed

+95
-24
lines changed

rel/overlay/etc/default.ini

+2-2
Original file line numberDiff line numberDiff line change
@@ -512,8 +512,8 @@ authentication_db = _users
512512
; Timeout for how long a response from a busy view group server can take.
513513
; "infinity" is also a valid configuration value.
514514
;group_info_timeout = 5000
515-
;query_limit = 268435456
516-
;partition_query_limit = 268435456
515+
;query_limit = infinity
516+
;partition_query_limit = infinity
517517

518518
; Configure what to use as the db tag when selecting design doc couchjs
519519
; processes. The choices are:

src/chttpd/src/chttpd_show.erl

+2-1
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,8 @@ handle_view_list(Req, Db, DDoc, LName, {ViewDesignName, ViewName}, Keys) ->
264264
},
265265
case ViewName of
266266
<<"_all_docs">> ->
267-
fabric:all_docs(Db, Options, CB, Acc, QueryArgs);
267+
QueryArgs1 = fabric_util:validate_all_docs_args(Db, QueryArgs),
268+
fabric:all_docs(Db, Options, CB, Acc, QueryArgs1);
268269
_ ->
269270
fabric:query_view(
270271
Db,

src/chttpd/test/eunit/chttpd_view_test.erl

+69-3
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@
4848

4949
setup() ->
5050
Hashed = couch_passwords:hash_admin_password(?PASS),
51-
ok = config:set("admins", ?USER, ?b2l(Hashed), _Persist = false),
51+
ok = config:set("admins", ?USER, ?b2l(Hashed), false),
52+
ok = config:set("query_server_config", "query_limit", "100", false),
5253
Db = ?tempdb(),
5354
ok = create_db(Db),
5455
ok = create_docs(Db),
@@ -57,7 +58,8 @@ setup() ->
5758

5859
teardown(Db) ->
5960
ok = fabric:delete_db(Db),
60-
ok = config:delete("admins", ?USER, _Persist = false).
61+
ok = config:delete("admins", ?USER, false),
62+
ok = config:delete("query_server_config", "query_limit", false).
6163

6264
view_test_() ->
6365
{
@@ -111,7 +113,11 @@ all_docs_test_() ->
111113
?TDEF_FE(t_all_docs_with_key_non_existent_docs),
112114
?TDEF_FE(t_all_docs_with_keys_non_existent_docs),
113115
?TDEF_FE(t_all_docs_with_key_deleted_docs),
114-
?TDEF_FE(t_all_docs_with_keys_deleted_docs)
116+
?TDEF_FE(t_all_docs_with_keys_deleted_docs),
117+
?TDEF_FE(t_all_docs_with_limit),
118+
?TDEF_FE(t_all_docs_with_skip),
119+
?TDEF_FE(t_all_docs_with_limit_and_skip),
120+
?TDEF_FE(t_all_docs_with_configured_query_limit)
115121
]
116122
}
117123
}
@@ -370,6 +376,66 @@ t_view_map_reduce_with_keys_and_group(Db) ->
370376
?assertEqual(Res, Res1),
371377
?assertEqual(Code, Code1).
372378

379+
t_all_docs_with_limit(Db) ->
380+
{Code, Res} = req(get, url(Db, "_all_docs", "limit=2")),
381+
?assertEqual(200, Code),
382+
?assertMatch(
383+
#{
384+
<<"offset">> := 0,
385+
<<"total_rows">> := 4,
386+
<<"rows">> := [
387+
#{<<"id">> := <<"_design/ddoc">>},
388+
#{<<"id">> := <<"a">>}
389+
]
390+
},
391+
Res
392+
).
393+
394+
t_all_docs_with_skip(Db) ->
395+
{Code, Res} = req(get, url(Db, "_all_docs", "skip=2")),
396+
?assertEqual(200, Code),
397+
?assertMatch(
398+
#{
399+
<<"offset">> := 2,
400+
<<"total_rows">> := 4,
401+
<<"rows">> := [
402+
#{<<"id">> := <<"b">>},
403+
#{<<"id">> := <<"c">>}
404+
]
405+
},
406+
Res
407+
).
408+
409+
t_all_docs_with_limit_and_skip(Db) ->
410+
{Code, Res} = req(get, url(Db, "_all_docs", "skip=2&limit=1")),
411+
?assertEqual(200, Code),
412+
?assertMatch(
413+
#{
414+
<<"offset">> := 2,
415+
<<"total_rows">> := 4,
416+
<<"rows">> := [
417+
#{<<"id">> := <<"b">>}
418+
]
419+
},
420+
Res
421+
).
422+
423+
t_all_docs_with_configured_query_limit(Db) ->
424+
config:set("query_server_config", "query_limit", "2", false),
425+
{Code, Res} = req(get, url(Db, "_all_docs")),
426+
?assertEqual(200, Code),
427+
?assertMatch(
428+
#{
429+
<<"offset">> := 0,
430+
<<"total_rows">> := 4,
431+
<<"rows">> := [
432+
#{<<"id">> := <<"_design/ddoc">>},
433+
#{<<"id">> := <<"a">>}
434+
]
435+
},
436+
Res
437+
).
438+
373439
%%%%%%%%%%%%%%%%%%%% Utility Functions %%%%%%%%%%%%%%%%%%%%
374440
url(Db) ->
375441
Addr = config:get("chttpd", "bind_address", "127.0.0.1"),

src/couch_mrview/include/couch_mrview.hrl

+2-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@
5454
view_states=nil
5555
}).
5656

57-
-define(MAX_VIEW_LIMIT, 16#10000000).
57+
% Largest 64 bit small int: 2^59-1
58+
-define(MAX_VIEW_LIMIT, 576460752303423487).
5859

5960
-record(mrargs, {
6061
view_type,

src/couch_mrview/src/couch_mrview.erl

+4-4
Original file line numberDiff line numberDiff line change
@@ -279,20 +279,20 @@ query_all_docs(Db, Args) ->
279279
query_all_docs(Db, Args, fun default_cb/2, []).
280280

281281
query_all_docs(Db, Args, Callback, Acc) when is_list(Args) ->
282-
query_all_docs(Db, to_mrargs(Args), Callback, Acc);
282+
Args1 = couch_mrview_util:validate_all_docs_args(Db, to_mrargs(Args)),
283+
query_all_docs(Db, Args1, Callback, Acc);
283284
query_all_docs(Db, Args0, Callback, Acc) ->
284285
Sig = couch_util:with_db(Db, fun(WDb) ->
285286
{ok, Info} = couch_db:get_db_info(WDb),
286287
couch_index_util:hexsig(couch_hash:md5_hash(?term_to_bin(Info)))
287288
end),
288289
Args1 = Args0#mrargs{view_type = map},
289-
Args2 = couch_mrview_util:validate_all_docs_args(Db, Args1),
290290
{ok, Acc1} =
291-
case Args2#mrargs.preflight_fun of
291+
case Args1#mrargs.preflight_fun of
292292
PFFun when is_function(PFFun, 2) -> PFFun(Sig, Acc);
293293
_ -> {ok, Acc}
294294
end,
295-
all_docs_fold(Db, Args2, Callback, Acc1).
295+
all_docs_fold(Db, Args1, Callback, Acc1).
296296

297297
query_view(Db, DDoc, VName) ->
298298
Args = #mrargs{extra = [{view_row_map, true}]},

src/couch_mrview/src/couch_mrview_http.erl

+3-2
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ do_all_docs_req(Req, Db, Keys, NS) ->
229229
ETagFun = fun(Sig, Acc0) ->
230230
check_view_etag(Sig, Acc0, Req)
231231
end,
232-
Args = Args1#mrargs{preflight_fun = ETagFun},
232+
Args2 = Args1#mrargs{preflight_fun = ETagFun},
233233
{ok, Resp} = couch_httpd:etag_maybe(Req, fun() ->
234234
Max = chttpd:chunked_response_buffer_size(),
235235
VAcc0 = #vacc{db = Db, req = Req, threshold = Max},
@@ -241,7 +241,8 @@ do_all_docs_req(Req, Db, Keys, NS) ->
241241
),
242242
IsAdmin = is_admin(Db),
243243
Callback = get_view_callback(DbName, UsersDbName, IsAdmin),
244-
couch_mrview:query_all_docs(Db, Args, Callback, VAcc0)
244+
Args3 = couch_mrview_util:validate_all_docs_args(Db, Args2),
245+
couch_mrview:query_all_docs(Db, Args3, Callback, VAcc0)
245246
end),
246247
case is_record(Resp, vacc) of
247248
true -> {ok, Resp#vacc.resp};

src/couch_mrview/src/couch_mrview_show.erl

+4-3
Original file line numberDiff line numberDiff line change
@@ -218,15 +218,16 @@ handle_view_list(Req, Db, DDoc, LName, VDDoc, VName, Keys) ->
218218
false -> {ok, Acc0#lacc{etag = ETag}}
219219
end
220220
end,
221-
Args = Args0#mrargs{preflight_fun = ETagFun},
221+
Args1 = Args0#mrargs{preflight_fun = ETagFun},
222222
couch_httpd:etag_maybe(Req, fun() ->
223223
couch_query_servers:with_ddoc_proc(Db, DDoc, fun(QServer) ->
224224
Acc = #lacc{db = Db, req = Req, qserver = QServer, lname = LName},
225225
case VName of
226226
<<"_all_docs">> ->
227-
couch_mrview:query_all_docs(Db, Args, fun list_cb/2, Acc);
227+
Args2 = couch_mrview_util:validate_all_docs_args(Db, Args1),
228+
couch_mrview:query_all_docs(Db, Args2, fun list_cb/2, Acc);
228229
_ ->
229-
couch_mrview:query_view(Db, VDDoc, VName, Args, fun list_cb/2, Acc)
230+
couch_mrview:query_view(Db, VDDoc, VName, Args1, fun list_cb/2, Acc)
230231
end
231232
end)
232233
end).

src/couch_mrview/src/couch_mrview_util.erl

+7-7
Original file line numberDiff line numberDiff line change
@@ -522,11 +522,11 @@ apply_limit(ViewPartitioned, Args) ->
522522
{false, _} -> "query_limit"
523523
end,
524524

525-
MaxLimit = config:get_integer(
526-
"query_server_config",
527-
LimitType,
528-
?MAX_VIEW_LIMIT
529-
),
525+
MaxLimit =
526+
case config:get("query_server_config", LimitType, "infinity") of
527+
"infinity" -> ?MAX_VIEW_LIMIT;
528+
Val -> list_to_integer(Val)
529+
end,
530530

531531
% Set the highest limit possible if a user has not
532532
% specified a limit
@@ -545,7 +545,7 @@ apply_limit(ViewPartitioned, Args) ->
545545
end.
546546

547547
validate_all_docs_args(Db, Args0) ->
548-
Args = validate_args(Args0),
548+
Args = validate_args(Args0#mrargs{view_type = map}),
549549

550550
DbPartitioned = couch_db:is_partitioned(Db),
551551
Partition = get_extra(Args, partition),
@@ -557,7 +557,7 @@ validate_all_docs_args(Db, Args0) ->
557557
Args1 = apply_limit(true, Args),
558558
apply_all_docs_partition(Args1, Partition);
559559
_ ->
560-
Args
560+
apply_limit(false, Args)
561561
end.
562562

563563
validate_args(Args) ->

src/mango/src/mango_cursor_view.erl

+2-1
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,8 @@ execute(#cursor{db = Db, index = Idx, execution_stats = Stats} = Cursor0, UserFu
246246
case mango_idx:def(Idx) of
247247
all_docs ->
248248
CB = fun ?MODULE:handle_all_docs_message/2,
249-
fabric:all_docs(Db, DbOpts, CB, Cursor, Args);
249+
Args1 = fabric_util:validate_all_docs_args(Db, Args),
250+
fabric:all_docs(Db, DbOpts, CB, Cursor, Args1);
250251
_ ->
251252
CB = fun ?MODULE:handle_message/2,
252253
% Normal view

0 commit comments

Comments
 (0)