You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by ii...@apache.org on 2025/06/12 13:24:56 UTC
(couchdb) 02/03: Add QUERY_CARDINALITY_LIMIT to group_by
This is an automated email from the ASF dual-hosted git repository.
iilyak pushed a commit to branch couch-stats-resource-tracker-http-api
in repository https://gitbox.apache.org/repos/asf/couchdb.git
commit b2b4132e70b1fd4d2864739a5f9cb6a5da8abd19
Author: ILYA Khlopotov <ii...@apache.org>
AuthorDate: Tue Jun 10 12:13:07 2025 -0700
Add QUERY_CARDINALITY_LIMIT to group_by
---
.../src/couch_stats_resource_tracker.hrl | 2 +
src/couch_stats/src/csrt_query.erl | 46 ++++++++++++++--------
2 files changed, 31 insertions(+), 17 deletions(-)
diff --git a/src/couch_stats/src/couch_stats_resource_tracker.hrl b/src/couch_stats/src/couch_stats_resource_tracker.hrl
index 97e38781c..b3b50d7bd 100644
--- a/src/couch_stats/src/couch_stats_resource_tracker.hrl
+++ b/src/couch_stats/src/couch_stats_resource_tracker.hrl
@@ -35,6 +35,8 @@
-define(ROWS_READ, rows_read).
-define(FRPC_CHANGES_RETURNED, changes_returned).
+-define(QUERY_CARDINALITY_LIMIT, 10_000).
+
%% Mapping of couch_stat metric names to #rctx{} field names.
%% These are used for fields that we inc a counter on.
-define(STATS_TO_KEYS, #{
diff --git a/src/couch_stats/src/csrt_query.erl b/src/couch_stats/src/csrt_query.erl
index 62b03d695..c46345cd3 100644
--- a/src/couch_stats/src/csrt_query.erl
+++ b/src/couch_stats/src/csrt_query.erl
@@ -118,32 +118,44 @@ count_by(KeyFun) ->
group_by(KeyFun, ValFun) ->
group_by(KeyFun, ValFun, fun erlang:'+'/2).
+group_by(KeyFun, ValFun, AggFun) ->
+ group_by(KeyFun, ValFun, AggFun, ?QUERY_CARDINALITY_LIMIT).
+
%% eg: group_by(mfa, docs_read).
%% eg: group_by(fun(#rctx{mfa=MFA,docs_read=DR}) -> {MFA, DR} end, ioq_calls).
%% eg: ^^ or: group_by([mfa, docs_read], ioq_calls).
%% eg: group_by([username, dbname, mfa], docs_read).
%% eg: group_by([username, dbname, mfa], ioq_calls).
%% eg: group_by([username, dbname, mfa], js_filters).
-group_by(KeyL, ValFun, AggFun) when is_list(KeyL) ->
+group_by(KeyL, ValFun, AggFun, Limit) when is_list(KeyL) ->
KeyFun = fun(Ele) -> list_to_tuple([field(Ele, Key) || Key <- KeyL]) end,
- group_by(KeyFun, ValFun, AggFun);
-group_by(Key, ValFun, AggFun) when is_atom(Key) ->
- group_by(curry_field(Key), ValFun, AggFun);
-group_by(KeyFun, Val, AggFun) when is_atom(Val) ->
- group_by(KeyFun, curry_field(Val), AggFun);
-group_by(KeyFun, ValFun, AggFun) ->
+ group_by(KeyFun, ValFun, AggFun, Limit);
+group_by(Key, ValFun, AggFun, Limit) when is_atom(Key) ->
+ group_by(curry_field(Key), ValFun, AggFun, Limit);
+group_by(KeyFun, Val, AggFun, Limit) when is_atom(Val) ->
+ group_by(KeyFun, curry_field(Val), AggFun, Limit);
+group_by(KeyFun, ValFun, AggFun, Limit) ->
FoldFun = fun(Ele, Acc) ->
- Key = KeyFun(Ele),
- Val = ValFun(Ele),
- CurrVal = maps:get(Key, Acc, 0),
- case AggFun(CurrVal, Val) of
- 0 ->
- Acc;
- NewVal ->
- maps:put(Key, NewVal, Acc)
- end
+ case maps:size(Acc) =< Limit of
+ true ->
+ Key = KeyFun(Ele),
+ Val = ValFun(Ele),
+ CurrVal = maps:get(Key, Acc, 0),
+ case AggFun(CurrVal, Val) of
+ 0 ->
+ Acc;
+ NewVal ->
+ maps:put(Key, NewVal, Acc)
+ end;
+ false ->
+ throw({limit, Acc})
+ end
end,
- ets:foldl(FoldFun, #{}, ?CSRT_ETS).
+ try
+ {ok, ets:foldl(FoldFun, #{}, ?CSRT_ETS)}
+ catch throw:{limit, Acc} ->
+ {limit, Acc}
+ end.
%% Sorts largest first
sorted(Map) when is_map(Map) ->