Skip to content

Commit

Permalink
MB-7882 Simple btree optimizations
Browse files Browse the repository at this point in the history
This change removes some inefficiencies in the btree module:

1) Building unnecessary tuples when the extract and assemble
   functions are the default ones (as is the case for views).
   For this case, just don't build any tuples nor do any
   assemble/extract function calls;

2) Traversing a list of KVs and applying the assemble function
   to build a new list - this is waste of time and cpu when
   the assemble function is the default one (as in the case for
   views);

3) Traversing the same list 2 times and reversing an accumulator
   when encoding a kp node. This can be done in a single pass
   with the list comprehension expression and avoiding the need
   to keep a list accumulator and reverse it at the end.

This was found it profiling data from fprof. Nearly no impact on
view updates (due to existing pipelining), however view compaction
had gains of about 6% regarding time. Very small impact as well
on query response time.

Change-Id: I0b9b8e013deede1bdba86f4f1ba2d4cc052bbbb4
Reviewed-on: http://review.couchbase.org/25353
Reviewed-by: Volker Mische <volker.mische@gmail.com>
Tested-by: Filipe David Borba Manana <fdmanana@gmail.com>
  • Loading branch information
fdmanana committed Mar 28, 2013
1 parent 88623b5 commit b2549c8
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 54 deletions.
88 changes: 51 additions & 37 deletions src/couchdb/couch_btree.erl
Expand Up @@ -35,11 +35,15 @@
-define(MAX_RED_SIZE, ((1 bsl ?RED_BITS) - 1)).


extract(#btree{extract_kv=Extract}, Value) ->
extract(#btree{extract_kv = identity}, Value) ->
Value;
extract(#btree{extract_kv = Extract}, Value) ->
Extract(Value).

assemble(#btree{assemble_kv=Assemble}, Key, Value) ->
Assemble(Key, Value).
assemble(#btree{assemble_kv = identity}, KeyValue) ->
KeyValue;
assemble(#btree{assemble_kv = Assemble}, KeyValue) ->
Assemble(KeyValue).

less(#btree{less=Less}, A, B) ->
Less(A, B).
Expand Down Expand Up @@ -318,7 +322,7 @@ lookup_kvnode(_Bt, NodeTuple, LowerBound, Keys, Output) when tuple_size(NodeTupl
{ok, lists:reverse(Output, [{Key, not_found} || Key <- Keys])};
lookup_kvnode(Bt, NodeTuple, LowerBound, [LookupKey | RestLookupKeys], Output) ->
N = find_first_gteq(Bt, NodeTuple, LowerBound, tuple_size(NodeTuple), LookupKey),
{Key, Value} = element(N, NodeTuple),
KV = {Key, _Value} = element(N, NodeTuple),
case less(Bt, LookupKey, Key) of
true ->
% LookupKey is less than Key
Expand All @@ -330,7 +334,7 @@ lookup_kvnode(Bt, NodeTuple, LowerBound, [LookupKey | RestLookupKeys], Output) -
lookup_kvnode(Bt, NodeTuple, N+1, RestLookupKeys, [{LookupKey, not_found} | Output]);
false ->
% LookupKey is equal to Key
lookup_kvnode(Bt, NodeTuple, N, RestLookupKeys, [{LookupKey, {ok, assemble(Bt, LookupKey, Value)}} | Output])
lookup_kvnode(Bt, NodeTuple, N, RestLookupKeys, [{LookupKey, {ok, assemble(Bt, KV)}} | Output])
end
end.

Expand Down Expand Up @@ -409,8 +413,10 @@ reduce_node(#btree{reduce=nil, binary_mode = BinMode}, _NodeType, _NodeList) ->
if BinMode -> <<>>; true -> [] end;
reduce_node(#btree{reduce=R}, kp_node, NodeList) ->
R(rereduce, [element(2, Node) || {_K, Node} <- NodeList]);
reduce_node(#btree{reduce=R, assemble_kv=identity}, kv_node, NodeList) ->
R(reduce, NodeList);
reduce_node(#btree{reduce=R}=Bt, kv_node, NodeList) ->
R(reduce, [assemble(Bt, K, V) || {K, V} <- NodeList]).
R(reduce, [assemble(Bt, KV) || KV <- NodeList]).

reduce_tree_size(kv_node, NodeSize, _KvList) ->
NodeSize;
Expand Down Expand Up @@ -447,24 +453,29 @@ decode_node(Type, Binary, Acc) ->
decode_node(Type, Rest, [{binary:copy(K), Val} | Acc]).

encode_node(kv_node, Kvs) ->
couch_compress:compress(encode_node_iolist(Kvs, [1]));
Bin = [encode_node_kv(K, V) || {K, V} <- Kvs],
couch_compress:compress([1 | Bin]);
encode_node(kp_node, Kvs) ->
% convert the value to binary
Kvs2 = lists:map(fun({K,{Pointer, Reduction, SubtreeSize}}) ->
RedSize = iolist_size(Reduction),
case RedSize > ?MAX_RED_SIZE of
true ->
throw({error, {reduction_too_long, Reduction}});
false ->
ok
end,
{K, [<<Pointer:?POINTER_BITS, SubtreeSize:?TREE_SIZE_BITS, RedSize:?RED_BITS>>, Reduction]}
end, Kvs),
couch_compress:compress(encode_node_iolist(Kvs2, [0])).
KvBins = [
begin
RedSize = iolist_size(Reduction),
case RedSize > ?MAX_RED_SIZE of
true ->
throw({error, {reduction_too_long, Reduction}});
false ->
ok
end,
V = [
<<Pointer:?POINTER_BITS, SubtreeSize:?TREE_SIZE_BITS, RedSize:?RED_BITS>>,
Reduction
],
encode_node_kv(K, V)
end
|| {K, {Pointer, Reduction, SubtreeSize}} <- Kvs
],
couch_compress:compress([0 | KvBins]).

encode_node_iolist([], Acc) ->
lists:reverse(Acc);
encode_node_iolist([{K, V}|RestKvs], Acc) ->
encode_node_kv(K, V) ->
SizeK = erlang:iolist_size(K),
SizeV = erlang:iolist_size(V),
case SizeK > ?MAX_KEY_SIZE of
Expand All @@ -475,9 +486,7 @@ encode_node_iolist([{K, V}|RestKvs], Acc) ->
true -> throw({error, {value_too_long, K, SizeV}});
false -> ok
end,
Bin = [<<SizeK:?KEY_BITS, SizeV:?VALUE_BITS>>, K, V],
encode_node_iolist(RestKvs, [Bin|Acc]).

[<<SizeK:?KEY_BITS, SizeV:?VALUE_BITS>>, K, V].

write_node(#btree{fd = Fd, binary_mode = BinMode} = Bt, NodeType, NodeList) ->
% split up nodes into smaller sizes
Expand Down Expand Up @@ -607,7 +616,7 @@ modify_kvnode(Bt, NodeTuple, LowerBound, [{ActionType, ActionKey, ActionValue} |
end;
modify_kvnode(Bt, NodeTuple, LowerBound, [{ActionType, ActionKey, ActionValue} | RestActions], AccNode, QueryOutput, Acc) ->
N = find_first_gteq(Bt, NodeTuple, LowerBound, tuple_size(NodeTuple), ActionKey),
{Key, Value} = element(N, NodeTuple),
KV = {Key, _Value} = element(N, NodeTuple),
ResultNode = bounded_tuple_to_revlist(NodeTuple, LowerBound, N - 1, AccNode),
case less(Bt, ActionKey, Key) of
true ->
Expand Down Expand Up @@ -638,7 +647,7 @@ modify_kvnode(Bt, NodeTuple, LowerBound, [{ActionType, ActionKey, ActionValue} |
remove ->
modify_kvnode(Bt, NodeTuple, N+1, RestActions, ResultNode, QueryOutput, Acc);
modify ->
OldValue = assemble(Bt, Key, Value),
OldValue = assemble(Bt, KV),
ModFun = ActionValue,
{ResultNode2, ResultValue2, Acc2} =
modify_value(Bt, ModFun, OldValue, Acc, ResultNode),
Expand All @@ -647,10 +656,10 @@ modify_kvnode(Bt, NodeTuple, LowerBound, [{ActionType, ActionKey, ActionValue} |
fetch ->
% ActionKey is equal to the Key, insert into the QueryOuput, but re-process the node
% since an identical action key can follow it.
modify_kvnode(Bt, NodeTuple, N, RestActions, ResultNode, [{ok, assemble(Bt, Key, Value)} | QueryOutput], Acc)
modify_kvnode(Bt, NodeTuple, N, RestActions, ResultNode, [{ok, assemble(Bt, KV)} | QueryOutput], Acc)
end;
true ->
modify_kvnode(Bt, NodeTuple, N + 1, [{ActionType, ActionKey, ActionValue} | RestActions], [{Key, Value} | ResultNode], QueryOutput, Acc)
modify_kvnode(Bt, NodeTuple, N + 1, [{ActionType, ActionKey, ActionValue} | RestActions], [KV | ResultNode], QueryOutput, Acc)
end
end.

Expand Down Expand Up @@ -698,9 +707,9 @@ reduce_stream_kv_node(Bt, Dir, KVs, KeyStart, InEndRangeFun,
reduce_stream_kv_node2(_Bt, [], GroupedKey, GroupedKVsAcc, GroupedRedsAcc,
_KeyGroupFun, _Fun, _FilterFun, Acc) ->
{ok, Acc, GroupedRedsAcc, GroupedKVsAcc, GroupedKey};
reduce_stream_kv_node2(Bt, [{Key, Value}| RestKVs], GroupedKey, GroupedKVsAcc,
reduce_stream_kv_node2(Bt, [{Key, _Value} = KV | RestKVs], GroupedKey, GroupedKVsAcc,
GroupedRedsAcc, KeyGroupFun, Fun, FilterFun, Acc) ->
AssembledValue = assemble(Bt, Key, Value),
AssembledValue = assemble(Bt, KV),
case GroupedKey of
undefined ->
case FilterFun(value, AssembledValue) of
Expand Down Expand Up @@ -929,17 +938,22 @@ stream_kv_node(Bt, Reds, KVs, StartKey, InRange, Dir, Fun, Acc) ->
fun({Key, _}) -> less(Bt, StartKey, Key) end
end,
{LTKVs, GTEKVs} = lists:splitwith(DropFun, KVs),
AssembleLTKVs = [assemble(Bt,K,V) || {K,V} <- LTKVs],
case Bt#btree.assemble_kv of
identity ->
AssembleLTKVs = LTKVs;
_ ->
AssembleLTKVs = [assemble(Bt, KV) || KV <- LTKVs]
end,
stream_kv_node2(Bt, Reds, AssembleLTKVs, GTEKVs, InRange, Dir, Fun, Acc).

stream_kv_node2(_Bt, _Reds, _PrevKVs, [], _InRange, _Dir, _Fun, Acc) ->
{ok, Acc};
stream_kv_node2(Bt, Reds, PrevKVs, [{K,V} | RestKVs], InRange, Dir, Fun, Acc) ->
stream_kv_node2(Bt, Reds, PrevKVs, [{K, _V} = KV | RestKVs], InRange, Dir, Fun, Acc) ->
case InRange(K) of
false ->
{stop, {PrevKVs, Reds}, Acc};
true ->
AssembledKV = assemble(Bt, K, V),
AssembledKV = assemble(Bt, KV),
case Fun(value, AssembledKV, {PrevKVs, Reds}, Acc) of
{ok, Acc2} ->
stream_kv_node2(Bt, Reds, [AssembledKV | PrevKVs], RestKVs, InRange, Dir, Fun, Acc2);
Expand Down Expand Up @@ -991,15 +1005,15 @@ kv_guided_purge(Bt, KvList, GuideFun, GuideAcc) ->
kv_guided_purge(Bt, [], _GuideFun, GuideAcc, ResultKvList) ->
{ok, lists:reverse(ResultKvList), GuideAcc, Bt, ok};

kv_guided_purge(Bt, [{Key, Value} | Rest] = KvList, GuideFun, GuideAcc, ResultKvList) ->
AssembledKv = assemble(Bt, Key, Value),
kv_guided_purge(Bt, [KV | Rest] = KvList, GuideFun, GuideAcc, ResultKvList) ->
AssembledKv = assemble(Bt, KV),
case GuideFun(value, AssembledKv, GuideAcc) of
{stop, GuideAcc2} ->
{ok, lists:reverse(ResultKvList, KvList), GuideAcc2, Bt, stop};
{purge, GuideAcc2} ->
kv_guided_purge(Bt, Rest, GuideFun, GuideAcc2, ResultKvList);
{keep, GuideAcc2} ->
kv_guided_purge(Bt, Rest, GuideFun, GuideAcc2, [{Key, Value} | ResultKvList])
kv_guided_purge(Bt, Rest, GuideFun, GuideAcc2, [KV | ResultKvList])
end.


Expand Down
21 changes: 14 additions & 7 deletions src/couchdb/couch_btree_copy.erl
Expand Up @@ -141,20 +141,24 @@ apply_options([{kp_chunk_threshold, Threshold} | Rest], Acc) ->
apply_options(Rest, Acc#acc{kp_chunk_threshold = Threshold}).


extract(#acc{btree = #btree{extract_kv = identity}}, Value) ->
Value;
extract(#acc{btree = #btree{extract_kv = Extract}}, Value) ->
Extract(Value).


assemble(#acc{btree = #btree{assemble_kv = Assemble}}, Key, Value) ->
Assemble(Key, Value).
assemble(#acc{btree = #btree{assemble_kv = identity}}, KeyValue) ->
KeyValue;
assemble(#acc{btree = #btree{assemble_kv = Assemble}}, KeyValue) ->
Assemble(KeyValue).


before_leaf_write(#acc{before_kv_write = nil} = Acc, KVs) ->
{KVs, Acc};
before_leaf_write(#acc{before_kv_write = Fun, user_acc = UserAcc0} = Acc, KVs) ->
{NewKVs, NewUserAcc} = lists:mapfoldl(
fun({K, V}, UAcc) ->
Item = assemble(Acc, K, V),
fun({K, _V} = Kv, UAcc) ->
Item = assemble(Acc, Kv),
{NewItem, UAcc2} = Fun(Item, UAcc),
{K, _NewValue} = NewKV = extract(Acc, NewItem),
{NewKV, UAcc2}
Expand Down Expand Up @@ -317,9 +321,12 @@ flush_leaf(KVs, #acc{btree = Btree} = Acc) ->
true -> <<>>
end;
_ ->
Items = lists:map(
fun({K, V}) -> assemble(Acc2, K, V) end,
NewKVs),
case Btree#btree.assemble_kv of
identity ->
Items = NewKVs;
_ ->
Items = [assemble(Acc2, Kv) || Kv <- NewKVs]
end,
couch_btree:final_reduce(Btree, {Items, []})
end,
{ok, LeafState} = write_leaf(Acc2, NewKVs, Red),
Expand Down
4 changes: 2 additions & 2 deletions src/couchdb/couch_db.hrl
Expand Up @@ -283,8 +283,8 @@
-record(btree, {
fd,
root,
extract_kv = fun({_Key, _Value} = KV) -> KV end,
assemble_kv = fun(Key, Value) -> {Key, Value} end,
extract_kv = identity, % fun({_Key, _Value} = KV) -> KV end,
assemble_kv = identity, % fun({Key, Value}) -> {Key, Value} end,
less = fun(A, B) -> A < B end,
reduce = nil,
kv_chunk_threshold = 16#4ff,
Expand Down
16 changes: 8 additions & 8 deletions src/couchdb/couch_db_updater.erl
Expand Up @@ -393,7 +393,7 @@ btree_by_seq_split(#doc_info{id=Id, local_seq=Seq, rev={RevPos, RevId},
Id/binary,RevId/binary>>,
{<<Seq:48>>, Val}.

btree_by_seq_join(<<Seq:48>>, Val) ->
btree_by_seq_join({<<Seq:48>>, Val}) ->
<<SizeId:12,SizeBody:28,Deleted:1,Bp:47,RevPos0:48,Meta:8,
Id:SizeId/binary,RevId/binary>> = Val,
% It is possible when upgrading from 1.8.x to 2.0.0 to create items
Expand All @@ -415,7 +415,7 @@ btree_by_id_split(#doc_info{id=Id, local_seq=Seq, rev={RevPos,RevId},
Val = <<Seq:48,0:4,Size:28,DeletedBit:1,Bp:47,RevPos:48,Meta:8,RevId/binary>>,
{Id, Val}.

btree_by_id_join(Id, Bin) ->
btree_by_id_join({Id, Bin}) ->
<<Seq:48,Size:32,DeletedBit:1,Bp:47,RevPos0:48,Meta:8,RevId/binary>> = Bin,
% It is possible when upgrading from 1.8.x to 2.0.0 to create items
% on disk with a revision number of 0, which is not a valid revision
Expand Down Expand Up @@ -516,15 +516,15 @@ populate_db_from_header(Db, NewHeader) ->
end,
{ok, IdBtree} = couch_btree:open(Header#db_header.docinfo_by_id_btree_state,
Db#db.fd,
[{split, fun(X) -> btree_by_id_split(X) end},
{join, fun(X,Y) -> btree_by_id_join(X,Y) end},
{reduce, fun(X,Y) -> btree_by_id_reduce(X,Y) end},
[{split, fun btree_by_id_split/1},
{join, fun btree_by_id_join/1},
{reduce, fun btree_by_id_reduce/2},
{binary_mode, true}]),
{ok, SeqBtree} = couch_btree:open(Header#db_header.docinfo_by_seq_btree_state,
Db#db.fd,
[{split, fun(X) -> btree_by_seq_split(X) end},
{join, fun(X,Y) -> btree_by_seq_join(X,Y) end},
{reduce, fun(X,Y) -> btree_by_seq_reduce(X,Y) end},
[{split, fun btree_by_seq_split/1},
{join, fun btree_by_seq_join/1},
{reduce, fun btree_by_seq_reduce/2},
{less, Less},
{binary_mode, true}]),
{ok, LocalDocsBtree} = couch_btree:open(Header#db_header.local_docs_btree_state,
Expand Down

0 comments on commit b2549c8

Please sign in to comment.