From 4eeff27f91a5beb922645bf856308e53481531d6 Mon Sep 17 00:00:00 2001 From: Pascal Grosch Date: Tue, 6 Oct 2020 15:42:43 +0200 Subject: [PATCH] Added specs and documentation to riak_core_claim + format --- src/riak_core_claim.erl | 247 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 223 insertions(+), 24 deletions(-) diff --git a/src/riak_core_claim.erl b/src/riak_core_claim.erl index 5edbaa1cc..46d57b16d 100644 --- a/src/riak_core_claim.erl +++ b/src/riak_core_claim.erl @@ -49,6 +49,8 @@ -module(riak_core_claim). +-type ring() :: riak_core_ring:riak_core_ring(). + -export([claim/1, claim/3, claim_until_balanced/2, claim_until_balanced/4]). @@ -66,8 +68,21 @@ -define(DEF_TARGET_N, 4). +%% @doc Run the claim algorithm for the complete ring. +%% @param Ring Ring the algorithm is run on. +%% @returns The ring after the claim algorithm has been applied. +-spec claim(Ring :: ring()) -> ring(). + claim(Ring) -> claim(Ring, want, choose). +%% @doc Run the claim algorithm for the complete ring. +%% @param Ring Ring the algorithm is run on. +%% @param Mode1 ignored. +%% @param Mode2 ignored. +%% @returns The ring after the claim algorithm has been applied. +-spec claim(Ring :: ring(), Mode1 :: any(), + Mode2 :: any()) -> ring(). + claim(Ring, _, _) -> Members = riak_core_ring:claiming_members(Ring), lists:foldl(fun (Node, Ring0) -> @@ -75,9 +90,25 @@ claim(Ring, _, _) -> end, Ring, Members). +%% @doc Apply the claim algorithm until a given node owns enough partitions. +%% @param Ring Ring the algorithm is applied to. +%% @param Node Node name of the node to be balanced. +%% @returns The balanced ring. +-spec claim_until_balanced(Ring :: ring(), + Node :: term()) -> ring(). + claim_until_balanced(Ring, Node) -> claim_until_balanced(Ring, Node, want, choose). +%% @doc Apply the claim algorithm until a given node owns enough partitions. +%% @param Ring Ring the algorithm is applied to. +%% @param Node Node name of the node to be balanced. +%% @param want Fixed guard. +%% @param choose Fixed guard +%% @returns The balanced ring. +-spec claim_until_balanced(Ring :: ring(), + Node :: term(), want, choose) -> ring(). + claim_until_balanced(Ring, Node, want, choose) -> NeedsIndexes = wants_claim_v2(Ring, Node), case NeedsIndexes of @@ -91,27 +122,67 @@ claim_until_balanced(Ring, Node, want, choose) -> %% Claim Function Implementations %% =================================================================== -%% @spec default_choose_claim(riak_core_ring()) -> riak_core_ring() -%% @doc Choose a partition at random. +%% @doc Choose a partition at random for the local node. +%% @param Ring Ring to claim on. +%% @returns Updated ring. +-spec default_choose_claim(Ring :: ring()) -> ring(). + default_choose_claim(Ring) -> default_choose_claim(Ring, node()). +%% @doc Choose a partition for a given node at random. +%% @param Ring Ring to claim on. +%% @param Node Node to claim for. +%% @returns Updated ring. +-spec default_choose_claim(Ring :: ring(), + Node :: term()) -> ring(). + default_choose_claim(Ring, Node) -> choose_claim_v2(Ring, Node). +%% @doc Choose a partition for a given node according to the given parameters. +%% @param Ring Ring to claim on. +%% @param Node Node to claim for. +%% @param Params Parameters to consider. +%% @returns Updated ring. +-spec default_choose_claim(Ring :: ring(), + Node :: term(), Params :: [term()]) -> ring(). + default_choose_claim(Ring, Node, Params) -> choose_claim_v2(Ring, Node, Params). -%% @spec default_wants_claim(riak_core_ring()) -> {yes, integer()} | no %% @doc Want a partition if we currently have less than floor(ringsize/nodes). +%% @param Ring Ring to claim on. +%% @returns `{yes, Difference}' or `no'. +-spec default_wants_claim(Ring :: ring()) -> {yes, + integer()} | + no. + default_wants_claim(Ring) -> default_wants_claim(Ring, node()). +%% @doc Like {@link default_wants_claim/1} with a given node. +%% @param Node Node to decide balance for. +-spec default_wants_claim(Ring :: ring(), + Node :: term()) -> {yes, integer()} | no. + default_wants_claim(Ring, Node) -> wants_claim_v2(Ring, Node). +%% @doc Decide if the local node needs more partitions. +%% @param Ring Ring to claim on. +%% @returns `{yes, Difference}' or `no'. +-spec wants_claim_v2(Ring :: ring()) -> {yes, + integer()} | + no. + wants_claim_v2(Ring) -> wants_claim_v2(Ring, node()). +%% @doc Like {@link wants_claim_v2/1} for another node. +%% @param Node Node to decide balance for. +-spec wants_claim_v2(Ring :: ring(), + Node :: term()) -> {yes, integer()} | no. + wants_claim_v2(Ring, Node) -> Active = riak_core_ring:claiming_members(Ring), Owners = riak_core_ring:all_owners(Ring), @@ -125,9 +196,17 @@ wants_claim_v2(Ring, Node) -> true -> {yes, Avg - Count} end. -%% Provide default choose parameters if none given +%% @doc Provide default choose parameters if none given +-spec default_choose_params() -> [term()]. + default_choose_params() -> default_choose_params([]). +%% @doc Provide default NVal if it is not contained in the given parameters. +%% @param Params List of claim parameters. +%% @returns List of claim parameters containing target NVal. +-spec default_choose_params(Params :: + term()) -> [term()]. + default_choose_params(Params) -> case proplists:get_value(target_n_val, Params) of undefined -> @@ -137,12 +216,27 @@ default_choose_params(Params) -> _ -> Params end. +%% @doc Choose a partition the local node should claim. +%% @param Ring Ring to claim on. +%% @returns Updated ring. +-spec choose_claim_v2(Ring :: ring()) -> ring(). + choose_claim_v2(Ring) -> choose_claim_v2(Ring, node()). +%% @doc Like {@link choose_claim_v2/1} with a specified node. +%% @param Node Specified node that claims a partition. +-spec choose_claim_v2(Ring :: ring(), + Node :: term()) -> ring(). + choose_claim_v2(Ring, Node) -> Params = default_choose_params(), choose_claim_v2(Ring, Node, Params). +%% @doc Like {@link choose_claim_v2/2} with specified parameters. +%% @param Params0 Claim parameter list. +-spec choose_claim_v2(Ring :: ring(), Node :: term(), + Params0 :: [term()]) -> ring(). + choose_claim_v2(Ring, Node, Params0) -> Params = default_choose_params(Params0), %% Active::[node()] @@ -322,11 +416,26 @@ increase_takes([{Node, Own, Delta} | Rest], N, Max, Acc) increase_takes([NodeDelta | Rest], N, Max, Acc) -> increase_takes(Rest, N, Max, [NodeDelta | Acc]). +%% @doc Check if the given ring can provide enough owners for each node to meet +%% the target NVal. +%% @param Ring Ring to check. +%% @param TargetN NVal to check. +%% @returns Boolean indicating if the ring meets the requirement. +-spec meets_target_n(Ring :: ring(), + TargetN :: pos_integer()) -> boolean(). + meets_target_n(Ring, TargetN) -> Owners = lists:keysort(1, riak_core_ring:all_owners(Ring)), meets_target_n(Owners, TargetN, 0, [], []). +%% @private +%% @doc Helper function for {@link meets_target_n/2}. +-spec meets_target_n(Owners :: [{integer(), term()}], + TargetN :: pos_integer(), Index :: non_neg_integer(), + First :: [{integer(), term()}], + Last :: [{integer(), term()}]) -> boolean(). + meets_target_n([{Part, Node} | Rest], TargetN, Index, First, Last) -> case lists:keytake(Node, 1, Last) of @@ -355,11 +464,22 @@ meets_target_n([], TargetN, Index, First, Last) -> Last), {true, [Part || {_, _, Part} <- Violations]}. -%% Claim diversify tries to build a perfectly diverse ownership list that meets -%% target N. It uses wants to work out which nodes want partitions, but does -%% not honor the counts currently. The algorithm incrementally builds the ownership -%% list, updating the adjacency matrix needed to compute the diversity score as each -%% node is added and uses it to drive the selection of the next nodes. +%% @doc Claim diversify tries to build a perfectly diverse ownership list that +%% meets target N. It uses wants to work out which nodes want partitions, +%% but does not honor the counts currently. The algorithm incrementally +%% builds the ownership list, updating the adjacency matrix needed to +%% compute the diversity score as each node is added and uses it to drive +%% the selection of the next nodes. +%% @param Wants List of Node names and the respective number of partition they +%% want to claim. +%% @param Owners List of indices and the name of their owning node. +%% @param Params Parameters. +%% @returns New owner list and a list of attributes, in this case `diversified'. +-spec claim_diversify(Wants :: [{term(), integer()}], + Owners :: [{integer(), term()}], + Params :: [term()]) -> {[{integer(), term()}], + [atom()]}. + claim_diversify(Wants, Owners, Params) -> TN = proplists:get_value(target_n_val, Params, ?DEF_TARGET_N), @@ -370,8 +490,18 @@ claim_diversify(Wants, Owners, Params) -> Claiming, TN), {NewOwners, [diversified]}. -%% Claim nodes in seq a,b,c,a,b,c trying to handle the wraparound -%% case to meet target N +%% @doc Claim nodes in seq a,b,c,a,b,c trying to handle the wraparound case to +%% meet target N +%% @param Wants List of Node names and the respective number of partition they +%% want to claim. +%% @param Owners List of indices and the name of their owning node. +%% @param Params Parameters. +%% @returns Diagonalized list of owners and a list of attributes, in this case +%% `diagonalized'. +-spec claim_diagonal(Wants :: [{term(), integer()}], + Owners :: [{integer(), term()}], + Params :: [term()]) -> {[term()], [atom()]}. + claim_diagonal(Wants, Owners, Params) -> TN = proplists:get_value(target_n_val, Params, ?DEF_TARGET_N), @@ -498,6 +628,14 @@ backfill_ring(RingSize, Nodes, Remaining, Acc) -> backfill_ring(RingSize, Nodes, Remaining - 1, [Nodes | Acc]). +%% @doc Rebalance the expected load on nodes using a diagonal stripe. +%% @param Ring :: Ring to rebalance. +%% @param Node :: Node to rebalance from. +%% @returns Rebalanced ring. +%% @see diagonal_stripe/2. +-spec claim_rebalance_n(Ring :: ring(), + Node :: term()) -> ring(). + claim_rebalance_n(Ring, Node) -> Nodes = lists:usort([Node | riak_core_ring:claiming_members(Ring)]), @@ -507,6 +645,14 @@ claim_rebalance_n(Ring, Node) -> end, Ring, Zipped). +%% @doc Creates a diagonal stripw of the given nodes over the partitions of the +%% ring. +%% @param Ring Ring on which the stripes are built. +%% @param Nodes Nodes that are to be distributed. +%% @returns List of indices and assigned nodes. +-spec diagonal_stripe(Ring :: ring(), + Nodes :: [term()]) -> [{integer(), term()}]. + diagonal_stripe(Ring, Nodes) -> %% diagonal stripes guarantee most disperse data Partitions = lists:sort([I @@ -520,20 +666,39 @@ diagonal_stripe(Ring, Nodes) -> 1, length(Partitions))), Zipped. +%% @doc Choose a random partition for the local node. +%% @param Ring Ring to claim on. +%% @returns Updated ring. +-spec random_choose_claim(Ring :: ring()) -> ring(). + random_choose_claim(Ring) -> random_choose_claim(Ring, node()). +%% @doc Like {@link random_choose_claim/1} with a specified node. +%% @param Node Node to choose a partition for. +-spec random_choose_claim(Ring :: ring(), + Node :: term()) -> ring(). + random_choose_claim(Ring, Node) -> random_choose_claim(Ring, Node, []). +%% @doc Like {@link random_choose_claim/2} with specified parameters. +%% @param Params List of parameters, currently ignored. +-spec random_choose_claim(Ring :: ring(), + Node :: term(), Params :: [term()]) -> ring(). + random_choose_claim(Ring, Node, _Params) -> riak_core_ring:transfer_node(riak_core_ring:random_other_index(Ring), Node, Ring). -%% @spec never_wants_claim(riak_core_ring()) -> no %% @doc For use by nodes that should not claim any partitions. +-spec never_wants_claim(ring()) -> no. + never_wants_claim(_) -> no. +%% @doc For use by nodes that should not claim any partitions. +-spec never_wants_claim(ring(), term()) -> no. + never_wants_claim(_, _) -> no. %% =================================================================== @@ -563,7 +728,6 @@ find_violations(Ring, TargetN) -> lists:reverse(Bad). %% @private -%% %% @doc Counts up the number of partitions owned by each node. -spec get_counts([node()], [{integer(), _}]) -> [{node(), non_neg_integer()}]. @@ -580,6 +744,12 @@ get_counts(Nodes, Ring) -> dict:to_list(Counts). %% @private +%% @doc Add default delta values for all owners to the delta list. +-spec add_default_deltas(IdxOwners :: [{integer(), + term()}], + Deltas :: [{term(), integer()}], + Default :: integer()) -> [{term(), integer()}]. + add_default_deltas(IdxOwners, Deltas, Default) -> {_, Owners} = lists:unzip(IdxOwners), Owners2 = lists:usort(Owners), @@ -587,9 +757,15 @@ add_default_deltas(IdxOwners, Deltas, Default) -> lists:ukeysort(1, Deltas ++ Defaults). %% @private -%% -%% @doc Filter out candidate indices that would violate target_n given -%% a node's current partition ownership. +%% @doc Filter out candidate indices that would violate target_n given a node's +%% current partition ownership. +-spec prefilter_violations(Ring :: ring(), + Node :: term(), AllIndices :: [{term(), integer()}], + Indices :: [{term(), integer()}], + TargetN :: pos_integer(), + RingSize :: non_neg_integer()) -> [{term(), + integer()}]. + prefilter_violations(Ring, Node, AllIndices, Indices, TargetN, RingSize) -> CurrentIndices = riak_core_ring:indices(Ring, Node), @@ -603,7 +779,6 @@ prefilter_violations(Ring, Node, AllIndices, Indices, CurrentNth)]. %% @private -%% %% @doc Select indices from a given candidate set, according to two %% goals. %% @@ -614,6 +789,12 @@ prefilter_violations(Ring, Node, AllIndices, Indices, %% expected ownership. In other words, if A owns 5 partitions and %% the desired ownership is 3, then we try to claim at most 2 partitions %% from A. +-spec select_indices(Owners :: [], + Deltas :: [{term(), integer()}], + Indices :: [{term(), integer()}], + TargetN :: pos_integer(), + RingSize :: pos_integer()) -> [integer()]. + select_indices(_Owners, _Deltas, [], _TargetN, _RingSize) -> []; @@ -653,8 +834,11 @@ select_indices(Owners, Deltas, Indices, TargetN, lists:reverse(Claim). %% @private -%% %% @doc Determine if two positions in the ring meet target_n spacing. +-spec spaced_by_n(Ntha :: integer(), NthB :: integer(), + TargetN :: pos_integer(), + RingSize :: pos_integer()) -> boolean(). + spaced_by_n(NthA, NthB, TargetN, RingSize) -> case NthA > NthB of true -> @@ -664,8 +848,13 @@ spaced_by_n(NthA, NthB, TargetN, RingSize) -> end, (NFwd >= TargetN) and (NBack >= TargetN). -%% For each node in wants, work out how many more partition each node wants (positive) or is -%% overloaded by (negative) compared to what it owns. +%% @doc For each node in wants, work out how many more partition each node wants +%% (positive) or is overloaded by (negative) compared to what it owns. +%% @param Wants List of node names and their target number of partitions. +%% @param Owns List of node names and their actual number of partitions. +-spec wants_owns_diff(Wants :: [{term(), integer()}], + Owns :: [{term(), integer()}]) -> [{term(), integer()}]. + wants_owns_diff(Wants, Owns) -> [case lists:keyfind(N, 1, Owns) of {N, O} -> {N, W - O}; @@ -673,8 +862,12 @@ wants_owns_diff(Wants, Owns) -> end || {N, W} <- Wants]. -%% Given a ring, work out how many partition each wants to be -%% considered balanced +%% @doc Given a ring, work out how many partition each wants to be +%% considered balanced. +%% @param Ring Ring to figure out wants for. +%% @returns List of node names and the number of wanted partitions. +-spec wants(Ring :: ring()) -> [{term(), integer()}]. + wants(Ring) -> Active = lists:sort(riak_core_ring:claiming_members(Ring)), @@ -686,8 +879,11 @@ wants(Ring) -> lists:sort(ActiveWants ++ InactiveWants). %% @private -%% Given a number of nodes and ring size, return a list of +%% @doc Given a number of nodes and ring size, return a list of %% desired ownership, S long that add up to Q +-spec wants_counts(S :: non_neg_integer(), + Q :: non_neg_integer()) -> [integer()]. + wants_counts(S, Q) -> Max = roundup(Q / S), case S * Max - Q of @@ -697,7 +893,10 @@ wants_counts(S, Q) -> lists:duplicate(S - X, Max) end. -%% Round up to next whole integer - ceil +%% @private +%% @doc Round up to next whole integer - ceil +-spec roundup(float()) -> integer(). + roundup(I) when I >= 0 -> T = erlang:trunc(I), case I - T of