WIP Speed up many consumers on QQ
The idea is to avoid the expensive priority_queue:member/2 call by having each consumer remembering itself whether it's currently in the service_queue. Alternative: Changing the priority_queue implementation to use a map for fast membership checks turns out to be expensive because adding to and removing from a map is slow. Benchmarks: Start broker with tweaked config as described in https://github.com/ansd/message_selector_perf Start client with 900 consumers on a single session: ``` _build/default/bin/message_selector_perf many_receivers false 900 400000 ``` sending and receiving settled. Prior to this commit: ``` 400000 messages sent and received across 900 receivers in 11646 ms end-to-end throughput: 34346.6 msgs/s ``` with ~8% CPU time being spent in `priority_queue:member/2` After this commit: ``` 400000 messages sent and received across 900 receivers in 10132 ms end-to-end throughput: 39478.9 msgs/s ``` => 15% higher end-to-end throughput
This commit is contained in:
parent
4c34155886
commit
5d0ff8e709
|
@ -1491,7 +1491,7 @@ activate_next_consumer(#?STATE{consumers = Cons0,
|
||||||
{undefined, {NextCKey, #consumer{cfg = NextCCfg} = NextC}} ->
|
{undefined, {NextCKey, #consumer{cfg = NextCCfg} = NextC}} ->
|
||||||
Remaining = tl(Waiting0),
|
Remaining = tl(Waiting0),
|
||||||
%% TODO: can this happen?
|
%% TODO: can this happen?
|
||||||
Consumer = case maps:get(NextCKey, Cons0, undefined) of
|
Consumer0 = case maps:get(NextCKey, Cons0, undefined) of
|
||||||
undefined ->
|
undefined ->
|
||||||
NextC;
|
NextC;
|
||||||
Existing ->
|
Existing ->
|
||||||
|
@ -1500,12 +1500,12 @@ activate_next_consumer(#?STATE{consumers = Cons0,
|
||||||
%% with the new config
|
%% with the new config
|
||||||
Existing#consumer{cfg = NextCCfg}
|
Existing#consumer{cfg = NextCCfg}
|
||||||
end,
|
end,
|
||||||
#?STATE{service_queue = ServiceQueue} = State0,
|
#?STATE{service_queue = ServiceQueue0} = State0,
|
||||||
ServiceQueue1 = maybe_queue_consumer(NextCKey,
|
{Consumer, ServiceQueue} = maybe_queue_consumer(NextCKey,
|
||||||
Consumer,
|
Consumer0,
|
||||||
ServiceQueue),
|
ServiceQueue0),
|
||||||
State = State0#?STATE{consumers = Cons0#{NextCKey => Consumer},
|
State = State0#?STATE{consumers = Cons0#{NextCKey => Consumer},
|
||||||
service_queue = ServiceQueue1,
|
service_queue = ServiceQueue,
|
||||||
waiting_consumers = Remaining},
|
waiting_consumers = Remaining},
|
||||||
Effects = consumer_update_active_effects(State, Consumer,
|
Effects = consumer_update_active_effects(State, Consumer,
|
||||||
true, single_active,
|
true, single_active,
|
||||||
|
@ -1513,21 +1513,21 @@ activate_next_consumer(#?STATE{consumers = Cons0,
|
||||||
{State, Effects};
|
{State, Effects};
|
||||||
{{ActiveCKey, ?CONSUMER_PRIORITY(ActivePriority) =
|
{{ActiveCKey, ?CONSUMER_PRIORITY(ActivePriority) =
|
||||||
#consumer{checked_out = ActiveChecked} = Active},
|
#consumer{checked_out = ActiveChecked} = Active},
|
||||||
{NextCKey, ?CONSUMER_PRIORITY(WaitingPriority) = Consumer}}
|
{NextCKey, ?CONSUMER_PRIORITY(WaitingPriority) = Consumer0}}
|
||||||
when WaitingPriority > ActivePriority andalso
|
when WaitingPriority > ActivePriority andalso
|
||||||
map_size(ActiveChecked) == 0 ->
|
map_size(ActiveChecked) == 0 ->
|
||||||
Remaining = tl(Waiting0),
|
Remaining = tl(Waiting0),
|
||||||
%% the next consumer is a higher priority and should take over
|
%% the next consumer is a higher priority and should take over
|
||||||
%% and this consumer does not have any pending messages
|
%% and this consumer does not have any pending messages
|
||||||
#?STATE{service_queue = ServiceQueue} = State0,
|
#?STATE{service_queue = ServiceQueue0} = State0,
|
||||||
ServiceQueue1 = maybe_queue_consumer(NextCKey,
|
{Consumer, ServiceQueue} = maybe_queue_consumer(NextCKey,
|
||||||
Consumer,
|
Consumer0,
|
||||||
ServiceQueue),
|
ServiceQueue0),
|
||||||
Cons1 = Cons0#{NextCKey => Consumer},
|
Cons1 = Cons0#{NextCKey => Consumer},
|
||||||
Cons = maps:remove(ActiveCKey, Cons1),
|
Cons = maps:remove(ActiveCKey, Cons1),
|
||||||
Waiting = add_waiting({ActiveCKey, Active}, Remaining),
|
Waiting = add_waiting({ActiveCKey, Active}, Remaining),
|
||||||
State = State0#?STATE{consumers = Cons,
|
State = State0#?STATE{consumers = Cons,
|
||||||
service_queue = ServiceQueue1,
|
service_queue = ServiceQueue,
|
||||||
waiting_consumers = Waiting},
|
waiting_consumers = Waiting},
|
||||||
Effects = consumer_update_active_effects(State, Consumer,
|
Effects = consumer_update_active_effects(State, Consumer,
|
||||||
true, single_active,
|
true, single_active,
|
||||||
|
@ -2140,7 +2140,8 @@ checkout_one(#{system_time := Ts} = Meta, ExpiredMsg0, InitState0, Effects0) ->
|
||||||
{Msg, State0} ->
|
{Msg, State0} ->
|
||||||
%% there are consumers waiting to be serviced
|
%% there are consumers waiting to be serviced
|
||||||
%% process consumer checkout
|
%% process consumer checkout
|
||||||
case maps:get(ConsumerKey, Cons0) of
|
#{ConsumerKey := Con0} = Cons0,
|
||||||
|
case Con0 of
|
||||||
#consumer{credit = Credit,
|
#consumer{credit = Credit,
|
||||||
status = Status}
|
status = Status}
|
||||||
when Credit =:= 0 orelse
|
when Credit =:= 0 orelse
|
||||||
|
@ -2150,9 +2151,11 @@ checkout_one(#{system_time := Ts} = Meta, ExpiredMsg0, InitState0, Effects0) ->
|
||||||
%% or when higher priority single active consumers
|
%% or when higher priority single active consumers
|
||||||
%% take over, recurse without consumer in service
|
%% take over, recurse without consumer in service
|
||||||
%% queue
|
%% queue
|
||||||
checkout_one(Meta, ExpiredMsg,
|
Con1 = Con0#consumer{queued = false},
|
||||||
InitState#?STATE{service_queue = SQ1},
|
Cons = Cons0#{ConsumerKey := Con1},
|
||||||
Effects1);
|
State = InitState#?STATE{consumers = Cons,
|
||||||
|
service_queue = SQ1},
|
||||||
|
checkout_one(Meta, ExpiredMsg, State, Effects1);
|
||||||
#consumer{checked_out = Checked0,
|
#consumer{checked_out = Checked0,
|
||||||
next_msg_id = Next,
|
next_msg_id = Next,
|
||||||
credit = Credit,
|
credit = Credit,
|
||||||
|
@ -2166,7 +2169,8 @@ checkout_one(#{system_time := Ts} = Meta, ExpiredMsg0, InitState0, Effects0) ->
|
||||||
Con = Con0#consumer{checked_out = Checked,
|
Con = Con0#consumer{checked_out = Checked,
|
||||||
next_msg_id = Next + 1,
|
next_msg_id = Next + 1,
|
||||||
credit = Credit - 1,
|
credit = Credit - 1,
|
||||||
delivery_count = DelCnt},
|
delivery_count = DelCnt,
|
||||||
|
queued = false},
|
||||||
Size = get_header(size, get_msg_header(Msg)),
|
Size = get_header(size, get_msg_header(Msg)),
|
||||||
State1 =
|
State1 =
|
||||||
State0#?STATE{service_queue = SQ1,
|
State0#?STATE{service_queue = SQ1,
|
||||||
|
@ -2180,10 +2184,11 @@ checkout_one(#{system_time := Ts} = Meta, ExpiredMsg0, InitState0, Effects0) ->
|
||||||
empty ->
|
empty ->
|
||||||
{nochange, ExpiredMsg, InitState, Effects1}
|
{nochange, ExpiredMsg, InitState, Effects1}
|
||||||
end;
|
end;
|
||||||
{{value, _ConsumerId}, SQ1} ->
|
{{value, _ConsumerKey}, SQ1} ->
|
||||||
|
%%TODO set queued=false and replace this consumer in the waiting_consumers (if it exists)
|
||||||
|
State = InitState#?STATE{service_queue = SQ1},
|
||||||
%% consumer was not active but was queued, recurse
|
%% consumer was not active but was queued, recurse
|
||||||
checkout_one(Meta, ExpiredMsg,
|
checkout_one(Meta, ExpiredMsg, State, Effects1);
|
||||||
InitState#?STATE{service_queue = SQ1}, Effects1);
|
|
||||||
{empty, _} ->
|
{empty, _} ->
|
||||||
case rabbit_fifo_q:len(Messages0) of
|
case rabbit_fifo_q:len(Messages0) of
|
||||||
0 ->
|
0 ->
|
||||||
|
@ -2271,28 +2276,24 @@ update_or_remove_con(_Meta, ConsumerKey,
|
||||||
State#?STATE{consumers = maps:remove(ConsumerKey, Cons),
|
State#?STATE{consumers = maps:remove(ConsumerKey, Cons),
|
||||||
waiting_consumers = add_waiting({ConsumerKey, Con}, Waiting)};
|
waiting_consumers = add_waiting({ConsumerKey, Con}, Waiting)};
|
||||||
update_or_remove_con(_Meta, ConsumerKey,
|
update_or_remove_con(_Meta, ConsumerKey,
|
||||||
#consumer{} = Con,
|
#consumer{} = Con0,
|
||||||
#?STATE{consumers = Cons,
|
#?STATE{consumers = Cons,
|
||||||
service_queue = ServiceQueue} = State) ->
|
service_queue = ServiceQueue0} = State) ->
|
||||||
|
{Con, ServiceQueue} = maybe_queue_consumer(ConsumerKey, Con0, ServiceQueue0),
|
||||||
State#?STATE{consumers = maps:put(ConsumerKey, Con, Cons),
|
State#?STATE{consumers = maps:put(ConsumerKey, Con, Cons),
|
||||||
service_queue = maybe_queue_consumer(ConsumerKey, Con,
|
service_queue = ServiceQueue}.
|
||||||
ServiceQueue)}.
|
|
||||||
|
|
||||||
maybe_queue_consumer(Key, #consumer{credit = Credit,
|
maybe_queue_consumer(Key,
|
||||||
|
#consumer{queued = false,
|
||||||
status = up,
|
status = up,
|
||||||
cfg = #consumer_cfg{priority = P}},
|
credit = Credit,
|
||||||
|
cfg = #consumer_cfg{priority = P}} = Consumer,
|
||||||
ServiceQueue)
|
ServiceQueue)
|
||||||
when Credit > 0 ->
|
when Credit > 0 ->
|
||||||
% TODO: queue:member could surely be quite expensive, however the practical
|
{Consumer#consumer{queued = true},
|
||||||
% number of unique consumers may not be large enough for it to matter
|
priority_queue:in(Key, P, ServiceQueue)};
|
||||||
case priority_queue:member(Key, ServiceQueue) of
|
maybe_queue_consumer(_Key, Consumer, ServiceQueue) ->
|
||||||
true ->
|
{Consumer, ServiceQueue}.
|
||||||
ServiceQueue;
|
|
||||||
false ->
|
|
||||||
priority_queue:in(Key, P, ServiceQueue)
|
|
||||||
end;
|
|
||||||
maybe_queue_consumer(_Key, _Consumer, ServiceQueue) ->
|
|
||||||
ServiceQueue.
|
|
||||||
|
|
||||||
update_consumer(Meta, ConsumerKey, {Tag, Pid}, ConsumerMeta,
|
update_consumer(Meta, ConsumerKey, {Tag, Pid}, ConsumerMeta,
|
||||||
{Life, Mode} = Spec, Priority,
|
{Life, Mode} = Spec, Priority,
|
||||||
|
@ -2413,16 +2414,18 @@ credit_active_consumer(
|
||||||
DeliveryCountSnd, Cfg),
|
DeliveryCountSnd, Cfg),
|
||||||
%% grant the credit
|
%% grant the credit
|
||||||
Con1 = Con0#consumer{credit = LinkCreditSnd},
|
Con1 = Con0#consumer{credit = LinkCreditSnd},
|
||||||
ServiceQueue = maybe_queue_consumer(ConsumerKey, Con1, ServiceQueue0),
|
{Con2, ServiceQueue} = maybe_queue_consumer(ConsumerKey,
|
||||||
|
Con1,
|
||||||
|
ServiceQueue0),
|
||||||
State1 = State0#?STATE{service_queue = ServiceQueue,
|
State1 = State0#?STATE{service_queue = ServiceQueue,
|
||||||
consumers = maps:update(ConsumerKey, Con1, Cons0)},
|
consumers = maps:update(ConsumerKey, Con2, Cons0)},
|
||||||
{State2, ok, Effects} = checkout(Meta, State0, State1, []),
|
{State2, ok, Effects} = checkout(Meta, State0, State1, []),
|
||||||
|
|
||||||
#?STATE{consumers = Cons1 = #{ConsumerKey := Con2}} = State2,
|
#?STATE{consumers = Cons1 = #{ConsumerKey := Con3}} = State2,
|
||||||
#consumer{cfg = #consumer_cfg{pid = CPid,
|
#consumer{cfg = #consumer_cfg{pid = CPid,
|
||||||
tag = CTag},
|
tag = CTag},
|
||||||
credit = PostCred,
|
credit = PostCred,
|
||||||
delivery_count = PostDeliveryCount} = Con2,
|
delivery_count = PostDeliveryCount} = Con3,
|
||||||
Available = messages_ready(State2),
|
Available = messages_ready(State2),
|
||||||
case credit_api_v2(Cfg) of
|
case credit_api_v2(Cfg) of
|
||||||
true ->
|
true ->
|
||||||
|
@ -2431,7 +2434,7 @@ credit_active_consumer(
|
||||||
true ->
|
true ->
|
||||||
AdvancedDeliveryCount = add(PostDeliveryCount, PostCred),
|
AdvancedDeliveryCount = add(PostDeliveryCount, PostCred),
|
||||||
ZeroCredit = 0,
|
ZeroCredit = 0,
|
||||||
Con = Con2#consumer{delivery_count = AdvancedDeliveryCount,
|
Con = Con3#consumer{delivery_count = AdvancedDeliveryCount,
|
||||||
credit = ZeroCredit},
|
credit = ZeroCredit},
|
||||||
Cons = maps:update(ConsumerKey, Con, Cons1),
|
Cons = maps:update(ConsumerKey, Con, Cons1),
|
||||||
State3 = State2#?STATE{consumers = Cons},
|
State3 = State2#?STATE{consumers = Cons},
|
||||||
|
@ -2461,7 +2464,7 @@ credit_active_consumer(
|
||||||
case Drain of
|
case Drain of
|
||||||
true ->
|
true ->
|
||||||
AdvancedDeliveryCount = PostDeliveryCount + PostCred,
|
AdvancedDeliveryCount = PostDeliveryCount + PostCred,
|
||||||
Con = Con2#consumer{delivery_count = AdvancedDeliveryCount,
|
Con = Con3#consumer{delivery_count = AdvancedDeliveryCount,
|
||||||
credit = 0},
|
credit = 0},
|
||||||
Cons = maps:update(ConsumerKey, Con, Cons1),
|
Cons = maps:update(ConsumerKey, Con, Cons1),
|
||||||
State = State2#?STATE{consumers = Cons},
|
State = State2#?STATE{consumers = Cons},
|
||||||
|
|
|
@ -138,7 +138,9 @@
|
||||||
%% decremented for each delivery
|
%% decremented for each delivery
|
||||||
credit = 0 :: non_neg_integer(),
|
credit = 0 :: non_neg_integer(),
|
||||||
%% AMQP 1.0 §2.6.7
|
%% AMQP 1.0 §2.6.7
|
||||||
delivery_count :: rabbit_queue_type:delivery_count()
|
delivery_count :: rabbit_queue_type:delivery_count(),
|
||||||
|
%% Is this consumer in service_queue ?
|
||||||
|
queued = false :: boolean()
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-type consumer() :: #consumer{}.
|
-type consumer() :: #consumer{}.
|
||||||
|
|
Loading…
Reference in New Issue