From 5accc10b2465a2ad9680fba9d51a84a908055018 Mon Sep 17 00:00:00 2001 From: ndiezel0 Date: Thu, 30 May 2024 12:06:37 +0500 Subject: [PATCH] Fix pins unrandomizing (#134) * Add extra test to pins * Fix pins unrandomizing * Fix pins finally * Add test and docs --- apps/routing/src/hg_routing.erl | 118 ++++++++++++++++++++++---------- doc/index.md | 4 +- doc/route_pins.md | 35 ++++++++++ 3 files changed, 118 insertions(+), 39 deletions(-) create mode 100644 doc/route_pins.md diff --git a/apps/routing/src/hg_routing.erl b/apps/routing/src/hg_routing.erl index ec92a8c..84c62e6 100644 --- a/apps/routing/src/hg_routing.erl +++ b/apps/routing/src/hg_routing.erl @@ -349,20 +349,31 @@ select_better_route({LeftScore, _} = Left, {RightScore, _} = Right) -> RightPin = RightScore#domain_PaymentRouteScores.route_pin, Res = case {LeftPin, RightPin} of - _ when LeftPin /= ?ZERO, RightPin /= ?ZERO, LeftPin == RightPin -> + _ when LeftPin /= ?ZERO, RightPin /= ?ZERO, RightPin == LeftPin -> select_better_pinned_route(Left, Right); _ -> select_better_regular_route(Left, Right) end, Res. -select_better_pinned_route({LeftScore0, _Route1} = Left, {RightScore0, _Route2} = Right) -> +select_better_pinned_route({LeftScore0, LeftRoute} = Left, {RightScore0, RightRoute} = Right) -> LeftScore1 = LeftScore0#domain_PaymentRouteScores{ - random_condition = 0 + random_condition = 0, + route_pin = erlang:phash2({ + LeftScore0#domain_PaymentRouteScores.route_pin, + hg_route:provider_ref(LeftRoute), + hg_route:terminal_ref(LeftRoute) + }) }, RightScore1 = RightScore0#domain_PaymentRouteScores{ - random_condition = 0 + random_condition = 0, + route_pin = erlang:phash2({ + RightScore0#domain_PaymentRouteScores.route_pin, + hg_route:provider_ref(RightRoute), + hg_route:terminal_ref(RightRoute) + }) }, + case max(LeftScore1, RightScore1) of LeftScore1 -> Left; @@ -868,58 +879,93 @@ pin_random_test() -> Route2 = {hg_route:new(?prv(2), ?trm(2), 50, 1, Pin), Scores}, lists:foldl( fun(_I, Acc) -> - BalancedRoutes = balance_routes([Route1, Route2]), - ScoredRoutes = score_routes(BalancedRoutes), - {{_, ChosenScoredRoute}, _IdealRoute} = find_best_routes(ScoredRoutes), + {ST, _} = ShuffledRoute = shuffle_routes([Route1, Route2]), case Acc of undefined -> - ChosenScoredRoute; + ShuffledRoute; + {ST, _} -> + ShuffledRoute; _ -> - ChosenTerminal = hg_route:terminal_ref(ChosenScoredRoute), - AccTerminal = hg_route:terminal_ref(Acc), - _ = ?assertEqual(ChosenTerminal, AccTerminal), - ChosenScoredRoute + error({ShuffledRoute, Acc}) end end, undefined, lists:seq(0, 1000) ). +-spec diff_pin_test() -> _. +diff_pin_test() -> + Pin = #{ + email => <<"example@mail.com">> + }, + Scores = {{alive, 0.0}, {normal, 0.0}}, + Route1 = {hg_route:new(?prv(1), ?trm(1), 50, 33, Pin), Scores}, + Route2 = {hg_route:new(?prv(1), ?trm(2), 50, 33, Pin), Scores}, + Route3 = {hg_route:new(?prv(1), ?trm(3), 50, 33, Pin#{client_ip => <<"IP">>}), Scores}, + {I1, I2, I3} = lists:foldl( + fun(_I, {Iter1, Iter2, Iter3}) -> + {ST, _} = shuffle_routes([Route1, Route2, Route3]), + case ST of + ?trm(1) -> + {Iter1 + 1, Iter2, Iter3}; + ?trm(2) -> + {Iter1, Iter2 + 1, Iter3}; + ?trm(3) -> + {Iter1, Iter2, Iter3 + 1} + end + end, + {0, 0, 0}, + lists:seq(0, 1000) + ), + case {I1, I2} of + {0, S} when S > 400 -> + true; + {S, 0} when S > 400 -> + true; + SomethingElse -> + error({{i1, i2}, SomethingElse}) + end, + case I3 of + _ when I3 > 300 -> + true; + _ -> + error({i3, I3}) + end. + -spec pin_weight_test() -> _. pin_weight_test() -> Pin0 = #{ email => <<"example@mail.com">> }, Pin1 = #{ - email => <<"example2@mail.com">> + email => <<"example1@mail.com">> }, - Scores = {{alive, 0.0}, {normal, 0.0}}, - Route1 = {hg_route:new(?prv(1), ?trm(1), 50, 1, Pin0), Scores}, - Route2 = {hg_route:new(?prv(2), ?trm(2), 50, 1, Pin1), Scores}, - {_, DiffTimes} = lists:foldl( - fun(_I, {Acc, Iter}) -> - BalancedRoutes = balance_routes([Route1, Route2]), - ScoredRoutes = score_routes(BalancedRoutes), - {{_, ChosenScoredRoute}, _IdealRoute} = find_best_routes(ScoredRoutes), - case Acc of - undefined -> - {ChosenScoredRoute, Iter}; + Scores1 = {{alive, 0.0}, {normal, 0.0}}, + Scores2 = {{alive, 0.0}, {normal, 0.0}}, + Route1 = {hg_route:new(?prv(1), ?trm(1), 50, 1, Pin0, ?fd_overrides(true)), Scores1}, + Route2 = {hg_route:new(?prv(1), ?trm(2), 50, 1, Pin0, ?fd_overrides(true)), Scores2}, + Route3 = {hg_route:new(?prv(1), ?trm(1), 50, 1, Pin1, ?fd_overrides(true)), Scores1}, + Route4 = {hg_route:new(?prv(1), ?trm(2), 50, 1, Pin1, ?fd_overrides(true)), Scores2}, + true = lists:foldl( + fun(_I, _A) -> + {ShuffledRoute1, _} = shuffle_routes([Route1, Route2]), + {ShuffledRoute2, _} = shuffle_routes([Route3, Route4]), + case true of + _ when ShuffledRoute1 == ?trm(1), ShuffledRoute2 == ?trm(2) -> + true; _ -> - ChosenTerminal = hg_route:terminal_ref(ChosenScoredRoute), - case hg_route:terminal_ref(Acc) of - ChosenTerminal -> - {Acc, Iter}; - _ -> - {Acc, Iter + 1} - end + error({ShuffledRoute1, ShuffledRoute2}) end end, - {undefined, 0}, + true, lists:seq(0, 1000) - ), - ?assertNotEqual(0, DiffTimes), - ?assertEqual(true, DiffTimes > 300), - ?assertEqual(true, DiffTimes < 700). + ). + +shuffle_routes(Routes) -> + BalancedRoutes = balance_routes(Routes), + ScoredRoutes = score_routes(BalancedRoutes), + {{_, ChosenScoredRoute}, _IdealRoute} = find_best_routes(ScoredRoutes), + {hg_route:terminal_ref(ChosenScoredRoute), ChosenScoredRoute}. -spec balance_routes_test_() -> [testcase()]. balance_routes_test_() -> diff --git a/doc/index.md b/doc/index.md index 223f7b8..c6bbec8 100644 --- a/doc/index.md +++ b/doc/index.md @@ -1,5 +1,3 @@ # Документация -1. [Общее описание](overview.md) -1. [Установка](install.md) -1. [Первоначалная настройка](configuration.md) +1. [Работа пинов](route_pins.md) diff --git a/doc/route_pins.md b/doc/route_pins.md new file mode 100644 index 0000000..b62040b --- /dev/null +++ b/doc/route_pins.md @@ -0,0 +1,35 @@ +# Пины роутов + +## Какая задача + +У нас есть 2 и более роутов с одинаковым приоритетом +и какой-то там разбивкой по весу. Например 3 роута с весами 33:33:33. + +К нам приходит плательщик. Он оплачивает какую-то услугу +и этот платеж проходит через конкретный терминал конкретного провайдера. +Проще говоря он выбрал один из кандидатов (роутов) из списка с одинаковым приоритетом. + +Теперь мы хотим чтобы этот плательщик в будущем ходим через тот же самый роут. + +Плательщика определяем каким-то там способом. + +## Решение + +Мы в каждом роут кандидате можем указать +[список характеристик](https://github.com/valitydev/damsel/blob/master/proto/domain.thrift#L2850-L2856) +по которым мы будем определять какой именно плательщик к нам пришел. + +Когда к нам приходит запрос на проведение платежа, то мы собираем все указанные +в конкретном кандидате характеристики и вычисляем хэш этих характеристик. +Этот хэш учитывается при сортировке роутов по самым желаемым. + +Если как в примере выше у нас 3 роут кандидата с одинаковым весом +и список характеристик (например смотрим только на имейл) совпадает, +то мы лочим роут с этим значением характеристики. +Все последующие платежи с этими значениями будут проходить по тому роуту, что был использован +в первой операции. Соответственно вес у нас в одном приоритете становится 100:0:0. + +Если же один из этих роутов имеет другой набор характеристик, например имейл и IP адрес клиента, то он участвует +в локе пинов с роутами у которых такой же набор характеристик. В данном примере, так как он один, то распределение +становится 66:0:33. Если бы был еще один роут с тем же приоритетом и набором характеристик имейл и IP, то +распределение было бы 50:0:50:0