Skip to content

Commit 4d761a0

Browse files
feat: By-Weight strategy for route selection
1 parent 2f181c7 commit 4d761a0

File tree

2 files changed

+47
-4
lines changed

2 files changed

+47
-4
lines changed

src/dev_router.erl

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,10 @@ routes(M1, M2, Opts) ->
7575
%% If we have a route that has multiple resolving nodes, check
7676
%% the load distribution strategy and choose a node. Supported strategies:
7777
%% <pre>
78-
%% All: Return all nodes (default).
79-
%% Random: Distribute load evenly across all nodes, non-deterministically.
78+
%% All: Return all nodes (default).
79+
%% Random: Distribute load evenly across all nodes, non-deterministically.
8080
%% By-Base: According to the base message's hashpath.
81+
%% By-Weight: According to the node's `weight' key.
8182
%% Nearest: According to the distance of the node's wallet address to the
8283
%% base message's hashpath.
8384
%% </pre>
@@ -246,6 +247,19 @@ choose(0, _, _, _, _) -> [];
246247
choose(N, <<"Random">>, _, Nodes, _Opts) ->
247248
Node = lists:nth(rand:uniform(length(Nodes)), Nodes),
248249
[Node | choose(N - 1, <<"Random">>, nop, lists:delete(Node, Nodes), _Opts)];
250+
choose(N, <<"By-Weight">>, _, Nodes, Opts) ->
251+
NodesWithWeight =
252+
[
253+
{ Node, hb_util:int(hb_ao:get(<<"weight">>, Node, Opts)) }
254+
||
255+
Node <- Nodes
256+
],
257+
Node = hb_util:weighted_random(NodesWithWeight),
258+
[
259+
Node
260+
|
261+
choose(N - 1, <<"By-Weight">>, nop, lists:delete(Node, Nodes), Opts)
262+
];
249263
choose(N, <<"By-Base">>, Hashpath, Nodes, Opts) when is_binary(Hashpath) ->
250264
choose(N, <<"By-Base">>, binary_to_bignum(Hashpath), Nodes, Opts);
251265
choose(N, <<"By-Base">>, HashInt, Nodes, Opts) ->
@@ -352,6 +366,17 @@ dynamic_routes_provider_test() ->
352366
hb_http:get(Node, <<"/~router@1.0/routes/1/node">>, #{})
353367
).
354368

369+
weighted_random_strategy_test() ->
370+
Nodes =
371+
[
372+
#{ <<"host">> => <<"1">>, <<"weight">> => 1 },
373+
#{ <<"host">> => <<"2">>, <<"weight">> => 99 }
374+
],
375+
SimRes = simulate(1000, 1, Nodes, <<"By-Weight">>),
376+
[One, _] = simulation_distribution(SimRes, Nodes),
377+
?assert(One < 20),
378+
?assert(One > 5).
379+
355380
strategy_suite_test_() ->
356381
lists:map(
357382
fun(Strategy) ->

src/hb_util.erl

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
-export([print_trace/4, trace_macro_helper/5, print_trace_short/4]).
1818
-export([ok/1, ok/2, until/1, until/2, until/3]).
1919
-export([format_trace_short/1, is_hb_module/1, is_hb_module/2, all_hb_modules/0]).
20-
-export([count/2, mean/1, stddev/1, variance/1]).
20+
-export([count/2, mean/1, stddev/1, variance/1, weighted_random/1]).
2121
-include("include/hb.hrl").
2222

2323
%%% Simple type coercion functions, useful for quickly turning inputs from the
@@ -709,4 +709,22 @@ stddev(List) ->
709709

710710
variance(List) ->
711711
Mean = mean(List),
712-
lists:sum([ math:pow(X - Mean, 2) || X <- List ]) / length(List).
712+
lists:sum([ math:pow(X - Mean, 2) || X <- List ]) / length(List).
713+
714+
%% @doc Shuffle a list.
715+
shuffle(List) ->
716+
[ Y || {_, Y} <- lists:sort([ {rand:uniform(), X} || X <- List]) ].
717+
718+
%% @doc Return a random element from a list, weighted by the values in the list.
719+
weighted_random(List) ->
720+
TotalWeight = lists:sum([ Weight || {_, Weight} <- List ]),
721+
Normalized = [ {Item, Weight / TotalWeight} || {Item, Weight} <- List ],
722+
Shuffled = shuffle(Normalized),
723+
pick_weighted(Shuffled, rand:uniform()).
724+
725+
pick_weighted([], _) ->
726+
error(empty_list);
727+
pick_weighted([{Item, Weight}|_Rest], Remaining) when Remaining < Weight ->
728+
Item;
729+
pick_weighted([{_Item, Weight}|Rest], Remaining) ->
730+
pick_weighted(Rest, Remaining - Weight).

0 commit comments

Comments
 (0)