Skip to content

Commit 8493487

Browse files
committed
WIP Decrease memory usage of queue_type state
Prior to this commit, 1 MQTT publisher publishing to 1 Million target classic queues requires around 680 MB of process memory. After this commit, it requires around 290 MB of process memory. This commit requires feature flag classic_queue_type_delivery_support and introduces a new one called no_queue_name_in_classic_queue_client. Instead of storing the binary queue name 4 times, this commit now stores it only 1 time. The monitor_registry is removed since only classic queue clients monitor their classic queue server processes. The classic queue client does not store the queue name anymore. Instead the queue name is included in messages handled by the classic queue client. Storing the queue name in the record ctx was unnecessary. More potential future memory optimisations: * Use ets continuations when looking up destination queues * Only fetch ETS columns that are necessary instead of whole queue records * Do not hold the same vhost binary in memory many times. Instead, maintain a mapping. * Remove unnecessary tuple fields.
1 parent f83bb0d commit 8493487

16 files changed

+165
-281
lines changed

deps/rabbit/src/rabbit_amqqueue.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1745,7 +1745,7 @@ basic_get(Q, NoAck, LimiterPid, CTag, QStates) ->
17451745
non_neg_integer(), rabbit_types:ctag(), boolean(),
17461746
rabbit_framing:amqp_table(), any(), rabbit_types:username(),
17471747
rabbit_queue_type:state()) ->
1748-
{ok, rabbit_queue_type:state(), rabbit_queue_type:actions()} |
1748+
{ok, rabbit_queue_type:state()} |
17491749
{error, term()} |
17501750
{protocol_error, Type :: atom(), Reason :: string(), Args :: term()}.
17511751
basic_consume(Q, NoAck, ChPid, LimiterPid,

deps/rabbit/src/rabbit_channel.erl

Lines changed: 5 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -743,27 +743,6 @@ handle_cast({mandatory_received, _MsgSeqNo}, State) ->
743743
%% NB: don't call noreply/1 since we don't want to send confirms.
744744
noreply_coalesce(State);
745745

746-
handle_cast({reject_publish, _MsgSeqNo, QPid} = Evt, State) ->
747-
%% For backwards compatibility
748-
QRef = rabbit_queue_type:find_name_from_pid(QPid, State#ch.queue_states),
749-
case QRef of
750-
undefined ->
751-
%% ignore if no queue could be found for the given pid
752-
noreply(State);
753-
_ ->
754-
handle_cast({queue_event, QRef, Evt}, State)
755-
end;
756-
757-
handle_cast({confirm, _MsgSeqNo, QPid} = Evt, State) ->
758-
%% For backwards compatibility
759-
QRef = rabbit_queue_type:find_name_from_pid(QPid, State#ch.queue_states),
760-
case QRef of
761-
undefined ->
762-
%% ignore if no queue could be found for the given pid
763-
noreply(State);
764-
_ ->
765-
handle_cast({queue_event, QRef, Evt}, State)
766-
end;
767746
handle_cast({queue_event, QRef, Evt},
768747
#ch{queue_states = QueueStates0} = State0) ->
769748
case rabbit_queue_type:handle_event(QRef, Evt, QueueStates0) of
@@ -786,11 +765,6 @@ handle_cast({queue_event, QRef, Evt},
786765
rabbit_misc:protocol_error(Type, Reason, ReasonArgs)
787766
end.
788767

789-
handle_info({ra_event, {Name, _} = From, Evt}, State) ->
790-
%% For backwards compatibility
791-
QRef = find_queue_name_from_quorum_name(Name, State#ch.queue_states),
792-
handle_cast({queue_event, QRef, {From, Evt}}, State);
793-
794768
handle_info({bump_credit, Msg}, State) ->
795769
%% A rabbit_amqqueue_process is granting credit to our channel. If
796770
%% our channel was being blocked by this process, and no other
@@ -811,11 +785,11 @@ handle_info(emit_stats, State) ->
811785
%% stats timer.
812786
{noreply, send_confirms_and_nacks(State1), hibernate};
813787

814-
handle_info({'DOWN', _MRef, process, QPid, Reason},
788+
handle_info({{'DOWN', QName}, _MRef, process, QPid, Reason},
815789
#ch{queue_states = QStates0,
816790
queue_monitors = _QMons} = State0) ->
817791
credit_flow:peer_down(QPid),
818-
case rabbit_queue_type:handle_down(QPid, Reason, QStates0) of
792+
case rabbit_queue_type:handle_down(QPid, QName, Reason, QStates0) of
819793
{ok, QState1, Actions} ->
820794
State1 = State0#ch{queue_states = QState1},
821795
State = handle_queue_actions(Actions, State1),
@@ -1802,7 +1776,7 @@ basic_consume(QueueName, NoAck, ConsumerPrefetch, ActualConsumerTag,
18021776
Username, QueueStates0),
18031777
Q}
18041778
end) of
1805-
{{ok, QueueStates, Actions}, Q} when ?is_amqqueue(Q) ->
1779+
{{ok, QueueStates}, Q} when ?is_amqqueue(Q) ->
18061780
rabbit_global_counters:consumer_created(amqp091),
18071781
CM1 = maps:put(
18081782
ActualConsumerTag,
@@ -1811,10 +1785,9 @@ basic_consume(QueueName, NoAck, ConsumerPrefetch, ActualConsumerTag,
18111785

18121786
State1 = State#ch{consumer_mapping = CM1,
18131787
queue_states = QueueStates},
1814-
State2 = handle_queue_actions(Actions, State1),
18151788
{ok, case NoWait of
1816-
true -> consumer_monitor(ActualConsumerTag, State2);
1817-
false -> State2
1789+
true -> consumer_monitor(ActualConsumerTag, State1);
1790+
false -> State1
18181791
end};
18191792
{{error, exclusive_consume_unavailable} = E, _Q} ->
18201793
E;
@@ -2885,20 +2858,6 @@ handle_queue_actions(Actions, #ch{} = State0) ->
28852858

28862859
end, State0, Actions).
28872860

2888-
find_queue_name_from_quorum_name(Name, QStates) ->
2889-
Fun = fun(K, _V, undefined) ->
2890-
{ok, Q} = rabbit_amqqueue:lookup(K),
2891-
case amqqueue:get_pid(Q) of
2892-
{Name, _} ->
2893-
amqqueue:get_name(Q);
2894-
_ ->
2895-
undefined
2896-
end;
2897-
(_, _, Acc) ->
2898-
Acc
2899-
end,
2900-
rabbit_queue_type:fold_state(Fun, undefined, QStates).
2901-
29022861
maybe_increase_global_publishers(#ch{publishing_mode = true} = State0) ->
29032862
State0;
29042863
maybe_increase_global_publishers(State0) ->

deps/rabbit/src/rabbit_classic_queue.erl

Lines changed: 77 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,20 @@
44
-include("amqqueue.hrl").
55
-include_lib("rabbit_common/include/rabbit.hrl").
66

7+
%% TODO possible to use sets / maps instead of lists?
8+
%% Check performance with QoS 1 and 1 million target queues.
79
-record(msg_status, {pending :: [pid()],
810
confirmed = [] :: [pid()]}).
911

1012
-define(STATE, ?MODULE).
11-
-record(?STATE, {pid :: undefined | pid(), %% the current master pid
12-
qref :: term(), %% TODO
13-
unconfirmed = #{} ::
14-
#{non_neg_integer() => #msg_status{}}}).
13+
-record(?STATE, {
14+
%% the current master pid
15+
pid :: undefined | pid(),
16+
%% undefined if feature flag no_queue_name_in_classic_queue_client enabled
17+
qref :: term(),
18+
unconfirmed = #{} :: #{non_neg_integer() => #msg_status{}},
19+
monitored = #{} :: #{pid() => ok}
20+
}).
1521

1622

1723
-opaque state() :: #?STATE{}.
@@ -156,9 +162,14 @@ stat(Q) ->
156162

157163
-spec init(amqqueue:amqqueue()) -> {ok, state()}.
158164
init(Q) when ?amqqueue_is_classic(Q) ->
159-
QName = amqqueue:get_name(Q),
165+
QRef = case rabbit_feature_flags:is_enabled(no_queue_name_in_classic_queue_client) of
166+
true ->
167+
undefined;
168+
false ->
169+
amqqueue:get_name(Q)
170+
end,
160171
{ok, #?STATE{pid = amqqueue:get_pid(Q),
161-
qref = QName}}.
172+
qref = QRef}}.
162173

163174
-spec close(state()) -> ok.
164175
close(_State) ->
@@ -174,7 +185,7 @@ update(Q, #?STATE{pid = Pid} = State) when ?amqqueue_is_classic(Q) ->
174185
State#?STATE{pid = NewPid}
175186
end.
176187

177-
consume(Q, Spec, State) when ?amqqueue_is_classic(Q) ->
188+
consume(Q, Spec, State0) when ?amqqueue_is_classic(Q) ->
178189
QPid = amqqueue:get_pid(Q),
179190
QRef = amqqueue:get_name(Q),
180191
#{no_ack := NoAck,
@@ -194,9 +205,9 @@ consume(Q, Spec, State) when ?amqqueue_is_classic(Q) ->
194205
ExclusiveConsume, Args, OkMsg, ActingUser},
195206
infinity]}) of
196207
ok ->
197-
%% ask the host process to monitor this pid
198208
%% TODO: track pids as they change
199-
{ok, State#?STATE{pid = QPid}, [{monitor, QPid, QRef}]};
209+
State = ensure_monitor(QPid, QRef, State0),
210+
{ok, State#?STATE{pid = QPid}};
200211
Err ->
201212
Err
202213
end.
@@ -233,8 +244,10 @@ credit(CTag, Credit, Drain, State) ->
233244
[{credit, ChPid, CTag, Credit, Drain}]}),
234245
{State, []}.
235246

236-
handle_event({confirm, MsgSeqNos, Pid}, #?STATE{qref = QRef,
237-
unconfirmed = U0} = State) ->
247+
handle_event({confirm, MsgSeqNos, Pid}, #?STATE{qref = QRef} = State) ->
248+
%% backwards compatibility when feature flag no_queue_name_in_classic_queue_client disabled
249+
handle_event({confirm, MsgSeqNos, Pid, QRef}, State);
250+
handle_event({confirm, MsgSeqNos, Pid, QRef}, #?STATE{unconfirmed = U0} = State) ->
238251
%% confirms should never result in rejections
239252
{Unconfirmed, ConfirmedSeqNos, []} =
240253
settle_seq_nos(MsgSeqNos, Pid, U0, confirm),
@@ -247,17 +260,20 @@ handle_event({confirm, MsgSeqNos, Pid}, #?STATE{qref = QRef,
247260
{ok, State#?STATE{unconfirmed = Unconfirmed}, Actions};
248261
handle_event({deliver, _, _, _} = Delivery, #?STATE{} = State) ->
249262
{ok, State, [Delivery]};
250-
handle_event({reject_publish, SeqNo, _QPid},
251-
#?STATE{qref = QRef,
252-
unconfirmed = U0} = State) ->
263+
handle_event({reject_publish, SeqNo, QPid}, #?STATE{qref = QRef} = State) ->
264+
%% backwards compatibility when feature flag no_queue_name_in_classic_queue_client disabled
265+
handle_event({reject_publish, SeqNo, QPid, QRef}, State);
266+
handle_event({reject_publish, SeqNo, _QPid, QRef},
267+
#?STATE{unconfirmed = U0} = State) ->
253268
%% It does not matter which queue rejected the message,
254269
%% if any queue did, it should not be confirmed.
255270
{U, Rejected} = reject_seq_no(SeqNo, U0),
256271
Actions = [{rejected, QRef, Rejected}],
257272
{ok, State#?STATE{unconfirmed = U}, Actions};
258-
handle_event({down, Pid, Info}, #?STATE{qref = QRef,
259-
pid = MasterPid,
260-
unconfirmed = U0} = State0) ->
273+
handle_event({down, Pid, QRef, Info}, #?STATE{monitored = Monitored,
274+
pid = MasterPid,
275+
unconfirmed = U0} = State0) ->
276+
State = State0#?STATE{monitored = maps:remove(Pid, Monitored)},
261277
Actions0 = case Pid =:= MasterPid of
262278
true ->
263279
[{queue_down, QRef}];
@@ -279,7 +295,7 @@ handle_event({down, Pid, Info}, #?STATE{qref = QRef,
279295
Actions = settlement_action(
280296
settled, QRef, Settled,
281297
settlement_action(rejected, QRef, Rejected, Actions0)),
282-
{ok, State0#?STATE{unconfirmed = Unconfirmed}, Actions};
298+
{ok, State#?STATE{unconfirmed = Unconfirmed}, Actions};
283299
true ->
284300
%% any abnormal exit should be considered a full reject of the
285301
%% oustanding message ids - If the message didn't get to all
@@ -294,7 +310,7 @@ handle_event({down, Pid, Info}, #?STATE{qref = QRef,
294310
end
295311
end, [], U0),
296312
U = maps:without(MsgIds, U0),
297-
{ok, State0#?STATE{unconfirmed = U},
313+
{ok, State#?STATE{unconfirmed = U},
298314
[{rejected, QRef, MsgIds} | Actions0]}
299315
end;
300316
handle_event({send_drained, _} = Action, State) ->
@@ -319,7 +335,7 @@ deliver(Qs0, #delivery{flow = Flow,
319335
Msg = Msg0#basic_message{id = rabbit_guid:gen()},
320336
Delivery = Delivery0#delivery{message = Msg},
321337

322-
{MPids, SPids, Qs, Actions} = qpids(Qs0, Confirm, MsgNo),
338+
{MPids, SPids, Qs} = qpids(Qs0, Confirm, MsgNo),
323339
case Flow of
324340
%% Here we are tracking messages sent by the rabbit_channel
325341
%% process. We are accessing the rabbit_channel process
@@ -333,7 +349,7 @@ deliver(Qs0, #delivery{flow = Flow,
333349
SMsg = {deliver, Delivery, true},
334350
delegate:invoke_no_result(MPids, {gen_server2, cast, [MMsg]}),
335351
delegate:invoke_no_result(SPids, {gen_server2, cast, [SMsg]}),
336-
{Qs, Actions}.
352+
{Qs, []}.
337353

338354

339355
-spec dequeue(NoAck :: boolean(), LimiterPid :: pid(),
@@ -381,14 +397,16 @@ purge(Q) when ?is_amqqueue(Q) ->
381397

382398
qpids(Qs, Confirm, MsgNo) ->
383399
lists:foldl(
384-
fun ({Q, S0}, {MPidAcc, SPidAcc, Qs0, Actions0}) ->
400+
fun ({Q, S0}, {MPidAcc, SPidAcc, Qs0}) ->
385401
QPid = amqqueue:get_pid(Q),
386402
SPids = amqqueue:get_slave_pids(Q),
387403
QRef = amqqueue:get_name(Q),
388-
Actions = [{monitor, QPid, QRef}
389-
| [{monitor, P, QRef} || P <- SPids]] ++ Actions0,
404+
S1 = ensure_monitor(QPid, QRef, S0),
405+
S2 = lists:foldl(fun(SPid, Acc) ->
406+
ensure_monitor(SPid, QRef, Acc)
407+
end, S1, SPids),
390408
%% confirm record only if necessary
391-
S = case S0 of
409+
S = case S2 of
392410
#?STATE{unconfirmed = U0} ->
393411
Rec = [QPid | SPids],
394412
U = case Confirm of
@@ -397,14 +415,14 @@ qpids(Qs, Confirm, MsgNo) ->
397415
true ->
398416
U0#{MsgNo => #msg_status{pending = Rec}}
399417
end,
400-
S0#?STATE{pid = QPid,
418+
S2#?STATE{pid = QPid,
401419
unconfirmed = U};
402420
stateless ->
403-
S0
421+
S2
404422
end,
405423
{[QPid | MPidAcc], SPidAcc ++ SPids,
406-
[{Q, S} | Qs0], Actions}
407-
end, {[], [], [], []}, Qs).
424+
[{Q, S} | Qs0]}
425+
end, {[], [], []}, Qs).
408426

409427
%% internal-ish
410428
-spec wait_for_promoted_or_stopped(amqqueue:amqqueue()) ->
@@ -521,59 +539,43 @@ update_msg_status(confirm, Pid, #msg_status{pending = P,
521539
update_msg_status(down, Pid, #msg_status{pending = P} = S) ->
522540
S#msg_status{pending = lists:delete(Pid, P)}.
523541

542+
ensure_monitor(_, _, State = stateless) ->
543+
State;
544+
ensure_monitor(Pid, _, State = #?STATE{monitored = Monitored})
545+
when is_map_key(Pid, Monitored) ->
546+
State;
547+
ensure_monitor(Pid, QName, State = #?STATE{monitored = Monitored}) ->
548+
_ = erlang:monitor(process, Pid, [{tag, {'DOWN', QName}}]),
549+
State#?STATE{monitored = Monitored#{Pid => ok}}.
550+
524551
%% part of channel <-> queue api
525552
confirm_to_sender(Pid, QName, MsgSeqNos) ->
526-
%% the stream queue included the queue type refactoring and thus requires
527-
%% a different message format
528-
case rabbit_queue_type:is_supported() of
529-
true ->
530-
gen_server:cast(Pid,
531-
{queue_event, QName,
532-
{confirm, MsgSeqNos, self()}});
533-
false ->
534-
gen_server2:cast(Pid, {confirm, MsgSeqNos, self()})
535-
end.
553+
Msg = case rabbit_feature_flags:is_enabled(no_queue_name_in_classic_queue_client) of
554+
true ->
555+
{confirm, MsgSeqNos, self(), QName};
556+
false ->
557+
{confirm, MsgSeqNos, self()}
558+
end,
559+
gen_server:cast(Pid, {queue_event, QName, Msg}).
536560

537561
send_rejection(Pid, QName, MsgSeqNo) ->
538-
case rabbit_queue_type:is_supported() of
539-
true ->
540-
gen_server:cast(Pid, {queue_event, QName,
541-
{reject_publish, MsgSeqNo, self()}});
542-
false ->
543-
gen_server2:cast(Pid, {reject_publish, MsgSeqNo, self()})
544-
end.
562+
Msg = case rabbit_feature_flags:is_enabled(no_queue_name_in_classic_queue_client) of
563+
true ->
564+
{reject_publish, MsgSeqNo, self(), QName};
565+
false ->
566+
{reject_publish, MsgSeqNo, self()}
567+
end,
568+
gen_server:cast(Pid, {queue_event, QName, Msg}).
545569

546570
deliver_to_consumer(Pid, QName, CTag, AckRequired, Message) ->
547-
case has_classic_queue_type_delivery_support() of
548-
true ->
549-
Deliver = {deliver, CTag, AckRequired, [Message]},
550-
Evt = {queue_event, QName, Deliver},
551-
gen_server:cast(Pid, Evt);
552-
false ->
553-
Deliver = {deliver, CTag, AckRequired, Message},
554-
gen_server2:cast(Pid, Deliver)
555-
end.
571+
Deliver = {deliver, CTag, AckRequired, [Message]},
572+
Evt = {queue_event, QName, Deliver},
573+
gen_server:cast(Pid, Evt).
556574

557575
send_drained(Pid, QName, CTagCredits) ->
558-
case has_classic_queue_type_delivery_support() of
559-
true ->
560-
gen_server:cast(Pid, {queue_event, QName,
561-
{send_drained, CTagCredits}});
562-
false ->
563-
gen_server2:cast(Pid, {send_drained, CTagCredits})
564-
end.
576+
gen_server:cast(Pid, {queue_event, QName,
577+
{send_drained, CTagCredits}}).
565578

566579
send_credit_reply(Pid, QName, Len) when is_integer(Len) ->
567-
case rabbit_queue_type:is_supported() of
568-
true ->
569-
gen_server:cast(Pid, {queue_event, QName,
570-
{send_credit_reply, Len}});
571-
false ->
572-
gen_server2:cast(Pid, {send_credit_reply, Len})
573-
end.
574-
575-
has_classic_queue_type_delivery_support() ->
576-
%% some queue_events were missed in the initial queue_type implementation
577-
%% this feature flag enables those and completes the initial queue type
578-
%% API for classic queues
579-
rabbit_feature_flags:is_enabled(classic_queue_type_delivery_support).
580+
gen_server:cast(Pid, {queue_event, QName,
581+
{send_credit_reply, Len}}).

deps/rabbit/src/rabbit_core_ff.erl

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,18 @@
108108
{classic_queue_type_delivery_support,
109109
#{desc => "Bug fix for classic queue deliveries using mixed versions",
110110
doc_url => "https://github.com/rabbitmq/rabbitmq-server/issues/5931",
111-
stability => stable,
111+
%%TODO remove compatibility code
112+
stability => required,
112113
depends_on => [stream_queue]
113114
}}).
114115

116+
-rabbit_feature_flag(
117+
{no_queue_name_in_classic_queue_client,
118+
#{desc => "Remove queue name from classic queue type client to save memory",
119+
stability => stable,
120+
depends_on => [classic_queue_type_delivery_support]
121+
}}).
122+
115123
%% -------------------------------------------------------------------
116124
%% Direct exchange routing v2.
117125
%% -------------------------------------------------------------------

0 commit comments

Comments
 (0)