This is an automated email from the ASF dual-hosted git repository. vatamane pushed a commit to branch view-btree-cache in repository https://gitbox.apache.org/repos/asf/couchdb.git
commit ff56e6d850f58552c96c01d27a088952ade9bc22 Author: Nick Vatamaniuc <[email protected]> AuthorDate: Thu Jan 29 00:14:53 2026 -0500 Use BTree cache for views In #5625 we introduced a BTree cache for dbs, and since view files also use BTrees, let's use a cache for them as well. This should help with concurrent view queries. I test the improvments I used k6 for benchmarking with a 1M doc, q=16 db, and a view with 10M rows. With a 400 rps request arrival rate saw more than a 50% improvement in average, p90, and p95 latencies: * Average: 14 -> 7 msec * Median: 12 -> 7 msec * P90 21 -> 10 msec * P95 27 -> 12 msec * Max 120 -> 77 msec Since view results could have large kp node values due to custom reduce functions, add max term limit for cache entries. To avoid adding yet another config value use a few multiple of BTree chunk size. This should help avoid config values clashing if users raise the chunk size but forget to raise their cache term limit (it would render the cache unusuable suddenly). More detailed test results along with a test at a higher depth of 4 to show further improvements. ``` BENCH_RATE=400 BENCH_DURATION=5m BENCH_VIEW_LIMIT=10 k6 run ./k6_couchdb_constant_arrival_view_query.js HTTP http_req_duration..............: avg=14.65ms min=2.08ms med=12.7ms max=120.08ms p(90)=20.53ms p(95)=26.78ms { expected_response:true }...: avg=14.65ms min=2.08ms med=12.7ms max=120.08ms p(90)=20.53ms p(95)=26.78ms { name:view_get }............: avg=14.65ms min=2.93ms med=12.7ms max=120.08ms p(90)=20.53ms p(95)=26.78ms http_req_failed................: 0.00% 0 out of 120002 http_reqs......................: 120002 399.891866/s ``` ``` $ BENCH_RATE=400 BENCH_DURATION=5m BENCH_VIEW_LIMIT=10 k6 run ./k6_couchdb_constant_arrival_view_query.js HTTP http_req_duration..............: avg=7.45ms min=2.38ms med=6.9ms max=77.78ms p(90)=10.21ms p(95)=11.5ms { expected_response:true }...: avg=7.45ms min=2.38ms med=6.9ms max=77.78ms p(90)=10.21ms p(95)=11.5ms { name:view_get }............: avg=7.45ms min=2.38ms med=6.9ms max=77.78ms p(90)=10.21ms p(95)=11.49ms http_req_failed................: 0.00% 0 out of 120004 http_reqs......................: 120004 399.799018/s ``` ``` $ BENCH_RATE=400 BENCH_DURATION=5m BENCH_VIEW_LIMIT=10 k6 run ./k6_couchdb_constant_arrival_view_query.js HTTP http_req_duration..............: avg=6.21ms min=2.22ms med=5.78ms max=77ms p(90)=8.4ms p(95)=9.5ms { expected_response:true }...: avg=6.21ms min=2.22ms med=5.78ms max=77ms p(90)=8.4ms p(95)=9.5ms { name:view_get }............: avg=6.21ms min=2.22ms med=5.78ms max=77ms p(90)=8.4ms p(95)=9.5ms http_req_failed................: 0.00% 0 out of 120003 http_reqs......................: 120003 399.931891/s ``` --- rel/overlay/etc/default.ini | 4 ++++ src/couch/src/couch_bt_engine_cache.erl | 20 ++++++++++++++------ src/couch/src/couch_btree.erl | 1 + src/couch_mrview/src/couch_mrview_util.erl | 11 +++++++++-- 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/rel/overlay/etc/default.ini b/rel/overlay/etc/default.ini index 69aae2ad8..d599656b1 100644 --- a/rel/overlay/etc/default.ini +++ b/rel/overlay/etc/default.ini @@ -178,6 +178,10 @@ view_index_dir = {{view_index_dir}} ; caching set the value to 0 ;db_btree_cache_depth = 3 +; Cache view btree nodes up to this depth only. Works like db_btree_cache_depth +; but for map-reduce (view) b-trees. +;view_btree_cache_depth = 3 + [purge] ; Allowed maximum number of accumulated revisions in one purge request ;max_revisions_number = infinity diff --git a/src/couch/src/couch_bt_engine_cache.erl b/src/couch/src/couch_bt_engine_cache.erl index a39564957..0b3d02704 100644 --- a/src/couch/src/couch_bt_engine_cache.erl +++ b/src/couch/src/couch_bt_engine_cache.erl @@ -13,6 +13,7 @@ -module(couch_bt_engine_cache). -include_lib("stdlib/include/ms_transform.hrl"). +-include_lib("couch/include/couch_db.hrl"). % Main API % @@ -52,7 +53,7 @@ -define(MISSES, misses). -define(FULL, full). --record(cache, {tid, max_size}). +-record(cache, {tid, max_size, max_term}). % Main API @@ -60,11 +61,11 @@ insert(Key, Term) -> insert(Key, Term, 1). insert(Key, Term, Priority) when is_integer(Priority) -> - Priority1 = min(?MAX_PRIORITY, max(0, Priority)), case get_cache(Key) of - #cache{tid = Tid, max_size = Max} -> - case ets:info(Tid, memory) < Max of + #cache{tid = Tid, max_size = Max, max_term = MaxTerm} -> + case ets:info(Tid, memory) < Max andalso ?term_size(Term) =< MaxTerm of true -> + Priority1 = min(?MAX_PRIORITY, max(0, Priority)), case ets:insert_new(Tid, {Key, Priority1, Term}) of true -> true; @@ -107,11 +108,13 @@ info() -> Caches when is_tuple(Caches) -> SizeMem = [info(C) || C <- tuple_to_list(Caches)], MaxMem = [Max || #cache{max_size = Max} <- tuple_to_list(Caches)], + #cache{max_term = MaxTerm} = element(1, Caches), {Sizes, Mem} = lists:unzip(SizeMem), #{ size => lists:sum(Sizes), memory => lists:sum(Mem), max_memory => lists:sum(MaxMem) * wordsize(), + max_term => MaxTerm, full => sample_metric(?FULL), hits => sample_metric(?HITS), misses => sample_metric(?MISSES), @@ -207,8 +210,13 @@ new() -> Opts = [public, {write_concurrency, true}, {read_concurrency, true}], Max0 = round(max_size() / wordsize() / shard_count()), % Some per-table overhead for the table metadata - Max = Max0 + round(250 * 1024 / wordsize()), - #cache{tid = ets:new(?MODULE, Opts), max_size = Max}. + MaxSize = Max0 + round(250 * 1024 / wordsize()), + MaxTerm = couch_btree:get_chunk_size() * 4, + #cache{ + tid = ets:new(?MODULE, Opts), + max_size = MaxSize, + max_term = MaxTerm + }. get_cache(Term) -> case persistent_term:get(?PTERM_KEY, undefined) of diff --git a/src/couch/src/couch_btree.erl b/src/couch/src/couch_btree.erl index 1519b1fbc..1c349d002 100644 --- a/src/couch/src/couch_btree.erl +++ b/src/couch/src/couch_btree.erl @@ -17,6 +17,7 @@ -export([fold_reduce/4, lookup/2, set_options/2]). -export([is_btree/1, get_state/1, get_fd/1, get_reduce_fun/1]). -export([extract/2, assemble/3, less/3]). +-export([get_chunk_size/0]). -include_lib("couch/include/couch_db.hrl"). diff --git a/src/couch_mrview/src/couch_mrview_util.erl b/src/couch_mrview/src/couch_mrview_util.erl index 5405e8db8..494f5b3ec 100644 --- a/src/couch_mrview/src/couch_mrview_util.erl +++ b/src/couch_mrview/src/couch_mrview_util.erl @@ -61,6 +61,8 @@ (C >= $A andalso C =< $F)) ). +-define(DEFAULT_BTREE_CACHE_DEPTH, 3). + -include_lib("couch/include/couch_db.hrl"). -include_lib("couch_mrview/include/couch_mrview.hrl"). @@ -369,7 +371,8 @@ init_state(Db, Fd, State, Header) -> }} = maybe_update_header(Header), IdBtOpts = [ - {compression, couch_compress:get_compression_method()} + {compression, couch_compress:get_compression_method()}, + {cache_depth, btree_cache_depth()} ], {ok, IdBtree} = couch_btree:open(IdBtreeState, Fd, IdBtOpts), @@ -394,7 +397,8 @@ open_view(_Db, Fd, Lang, ViewState, View) -> ViewBtOpts = [ {less, LessFun}, {reduce, ReduceFun}, - {compression, Compression} + {compression, Compression}, + {cache_depth, btree_cache_depth()} ], {ok, Btree} = couch_btree:open(BTState, Fd, ViewBtOpts), @@ -1365,3 +1369,6 @@ compact_on_collator_upgrade() -> commit_on_header_upgrade() -> config:get_boolean("view_upgrade", "commit_on_header_upgrade", true). + +btree_cache_depth() -> + config:get_integer("bt_engine_cache", "view_btree_cache_depth", ?DEFAULT_BTREE_CACHE_DEPTH).
