Skip to content

Commit 990bc9a

Browse files
Merge pull request #300 from rabbitmq/rabbitmq-server-295
improvements for BQ:purge
2 parents b647c30 + 647ae31 commit 990bc9a

File tree

2 files changed

+175
-73
lines changed

2 files changed

+175
-73
lines changed

src/rabbit_queue_index.erl

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
-module(rabbit_queue_index).
1818

19-
-export([erase/1, init/3, recover/6,
19+
-export([erase/1, init/3, reset_state/1, recover/6,
2020
terminate/2, delete_and_terminate/1,
2121
pre_publish/7, flush_pre_publish_cache/2,
2222
publish/6, deliver/2, ack/2, sync/1, needs_sync/1, flush/1,
@@ -224,6 +224,7 @@
224224
-type(shutdown_terms() :: [term()] | 'non_clean_shutdown').
225225

226226
-spec(erase/1 :: (rabbit_amqqueue:name()) -> 'ok').
227+
-spec(reset_state/1 :: (qistate()) -> qistate()).
227228
-spec(init/3 :: (rabbit_amqqueue:name(),
228229
on_sync_fun(), on_sync_fun()) -> qistate()).
229230
-spec(recover/6 :: (rabbit_amqqueue:name(), shutdown_terms(), boolean(),
@@ -261,10 +262,19 @@
261262

262263
erase(Name) ->
263264
#qistate { dir = Dir } = blank_state(Name),
264-
case rabbit_file:is_dir(Dir) of
265-
true -> rabbit_file:recursive_delete([Dir]);
266-
false -> ok
267-
end.
265+
erase_index_dir(Dir).
266+
267+
%% used during variable queue purge when there are no pending acks
268+
reset_state(#qistate{ dir = Dir,
269+
on_sync = OnSyncFun,
270+
on_sync_msg = OnSyncMsgFun,
271+
journal_handle = JournalHdl }) ->
272+
ok = erase_index_dir(Dir),
273+
ok = case JournalHdl of
274+
undefined -> ok;
275+
_ -> file_handle_cache:close(JournalHdl)
276+
end,
277+
blank_state_dir_funs(Dir, OnSyncFun, OnSyncMsgFun).
268278

269279
init(Name, OnSyncFun, OnSyncMsgFun) ->
270280
State = #qistate { dir = Dir } = blank_state(Name),
@@ -507,20 +517,31 @@ all_queue_directory_names(Dir) ->
507517
%% startup and shutdown
508518
%%----------------------------------------------------------------------------
509519

520+
erase_index_dir(Dir) ->
521+
case rabbit_file:is_dir(Dir) of
522+
true -> rabbit_file:recursive_delete([Dir]);
523+
false -> ok
524+
end.
525+
510526
blank_state(QueueName) ->
511527
blank_state_dir(
512528
filename:join(queues_dir(), queue_name_to_dir_name(QueueName))).
513529

514530
blank_state_dir(Dir) ->
531+
blank_state_dir_funs(Dir,
532+
fun (_) -> ok end,
533+
fun (_) -> ok end).
534+
535+
blank_state_dir_funs(Dir, OnSyncFun, OnSyncMsgFun) ->
515536
{ok, MaxJournal} =
516537
application:get_env(rabbit, queue_index_max_journal_entries),
517538
#qistate { dir = Dir,
518539
segments = segments_new(),
519540
journal_handle = undefined,
520541
dirty_count = 0,
521542
max_journal_entries = MaxJournal,
522-
on_sync = fun (_) -> ok end,
523-
on_sync_msg = fun (_) -> ok end,
543+
on_sync = OnSyncFun,
544+
on_sync_msg = OnSyncMsgFun,
524545
unconfirmed = gb_sets:new(),
525546
unconfirmed_msg = gb_sets:new(),
526547
pre_publish_cache = [],

src/rabbit_variable_queue.erl

Lines changed: 147 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -533,38 +533,29 @@ terminate(_Reason, State) ->
533533
%% the only difference between purge and delete is that delete also
534534
%% needs to delete everything that's been delivered and not ack'd.
535535
delete_and_terminate(_Reason, State) ->
536-
%% TODO: there is no need to interact with qi at all - which we do
537-
%% as part of 'purge' and 'purge_pending_ack', other than
538-
%% deleting it.
539-
{_PurgeCount, State1} = purge(State),
540-
State2 = #vqstate { index_state = IndexState,
541-
msg_store_clients = {MSCStateP, MSCStateT} } =
542-
purge_pending_ack(false, State1),
543-
IndexState1 = rabbit_queue_index:delete_and_terminate(IndexState),
536+
%% Normally when we purge messages we interact with the qi by
537+
%% issues delivers and acks for every purged message. In this case
538+
%% we don't need to do that, so we just delete the qi.
539+
State1 = purge_and_index_reset(State),
540+
State2 = #vqstate { msg_store_clients = {MSCStateP, MSCStateT} } =
541+
purge_pending_ack_delete_and_terminate(State1),
544542
case MSCStateP of
545543
undefined -> ok;
546544
_ -> rabbit_msg_store:client_delete_and_terminate(MSCStateP)
547545
end,
548546
rabbit_msg_store:client_delete_and_terminate(MSCStateT),
549-
a(State2 #vqstate { index_state = IndexState1,
550-
msg_store_clients = undefined }).
547+
a(State2 #vqstate { msg_store_clients = undefined }).
551548

552549
delete_crashed(#amqqueue{name = QName}) ->
553550
ok = rabbit_queue_index:erase(QName).
554551

555-
purge(State = #vqstate { q4 = Q4,
556-
len = Len }) ->
557-
%% TODO: when there are no pending acks, which is a common case,
558-
%% we could simply wipe the qi instead of issuing delivers and
559-
%% acks for all the messages.
560-
State1 = remove_queue_entries(Q4, State),
561-
562-
State2 = #vqstate { q1 = Q1 } =
563-
purge_betas_and_deltas(State1 #vqstate { q4 = ?QUEUE:new() }),
564-
565-
State3 = remove_queue_entries(Q1, State2),
566-
567-
{Len, a(State3 #vqstate { q1 = ?QUEUE:new() })}.
552+
purge(State = #vqstate { len = Len }) ->
553+
case is_pending_ack_empty(State) of
554+
true ->
555+
{Len, purge_and_index_reset(State)};
556+
false ->
557+
{Len, purge_when_pending_acks(State)}
558+
end.
568559

569560
purge_acks(State) -> a(purge_pending_ack(false, State)).
570561

@@ -754,10 +745,8 @@ len(#vqstate { len = Len }) -> Len.
754745

755746
is_empty(State) -> 0 == len(State).
756747

757-
depth(State = #vqstate { ram_pending_ack = RPA,
758-
disk_pending_ack = DPA,
759-
qi_pending_ack = QPA }) ->
760-
len(State) + gb_trees:size(RPA) + gb_trees:size(DPA) + gb_trees:size(QPA).
748+
depth(State) ->
749+
len(State) + count_pending_acks(State).
761750

762751
set_ram_duration_target(
763752
DurationTarget, State = #vqstate {
@@ -1072,7 +1061,7 @@ maybe_write_delivered(false, _SeqId, IndexState) ->
10721061
maybe_write_delivered(true, SeqId, IndexState) ->
10731062
rabbit_queue_index:deliver([SeqId], IndexState).
10741063

1075-
betas_from_index_entries(List, TransientThreshold, RPA, DPA, QPA, IndexState) ->
1064+
betas_from_index_entries(List, TransientThreshold, RPA, DPA, QPA, DelsAndAcksFun, State) ->
10761065
{Filtered, Delivers, Acks, RamReadyCount, RamBytes} =
10771066
lists:foldr(
10781067
fun ({_MsgOrId, SeqId, _MsgProps, IsPersistent, IsDelivered} = M,
@@ -1095,9 +1084,7 @@ betas_from_index_entries(List, TransientThreshold, RPA, DPA, QPA, IndexState) ->
10951084
end
10961085
end
10971086
end, {?QUEUE:new(), [], [], 0, 0}, List),
1098-
{Filtered, RamReadyCount, RamBytes,
1099-
rabbit_queue_index:ack(
1100-
Acks, rabbit_queue_index:deliver(Delivers, IndexState))}.
1087+
{Filtered, RamReadyCount, RamBytes, DelsAndAcksFun(Delivers, Acks, State)}.
11011088
%% [0] We don't increase RamBytes here, even though it pertains to
11021089
%% unacked messages too, since if HaveMsg then the message must have
11031090
%% been stored in the QI, thus the message must have been in
@@ -1323,25 +1310,75 @@ remove(AckRequired, MsgStatus = #msg_status {
13231310
State2 #vqstate {out_counter = OutCount + 1,
13241311
index_state = IndexState2})}.
13251312

1326-
purge_betas_and_deltas(State = #vqstate { q3 = Q3 }) ->
1313+
%%----------------------------------------------------------------------------
1314+
%% Helpers for Public API purge/1 function
1315+
%%----------------------------------------------------------------------------
1316+
1317+
%% The difference between purge_when_pending_acks/1
1318+
%% vs. purge_and_index_reset/1 is that the first one issues a deliver
1319+
%% and an ack to the queue index for every message that's being
1320+
%% removed, while the later just resets the queue index state.
1321+
purge_when_pending_acks(State) ->
1322+
State1 = purge1(process_delivers_and_acks_fun(deliver_and_ack), State),
1323+
a(State1).
1324+
1325+
purge_and_index_reset(State) ->
1326+
State1 = purge1(process_delivers_and_acks_fun(none), State),
1327+
a(reset_qi_state(State1)).
1328+
1329+
%% This function removes messages from each of {q1, q2, q3, q4}.
1330+
%%
1331+
%% With remove_queue_entries/3 q1 and q4 are emptied, while q2 and q3
1332+
%% are specially handled by purge_betas_and_deltas/2.
1333+
%%
1334+
%% purge_betas_and_deltas/2 loads messages from the queue index,
1335+
%% filling up q3 and in some cases moving messages form q2 to q3 while
1336+
%% reseting q2 to an empty queue (see maybe_deltas_to_betas/2). The
1337+
%% messages loaded into q3 are removed by calling
1338+
%% remove_queue_entries/3 until there are no more messages to be read
1339+
%% from the queue index. Messages are read in batches from the queue
1340+
%% index.
1341+
purge1(AfterFun, State = #vqstate { q4 = Q4}) ->
1342+
State1 = remove_queue_entries(Q4, AfterFun, State),
1343+
1344+
State2 = #vqstate {q1 = Q1} =
1345+
purge_betas_and_deltas(AfterFun, State1#vqstate{q4 = ?QUEUE:new()}),
1346+
1347+
State3 = remove_queue_entries(Q1, AfterFun, State2),
1348+
1349+
a(State3#vqstate{q1 = ?QUEUE:new()}).
1350+
1351+
reset_qi_state(State = #vqstate{index_state = IndexState}) ->
1352+
State#vqstate{index_state =
1353+
rabbit_queue_index:reset_state(IndexState)}.
1354+
1355+
is_pending_ack_empty(State) ->
1356+
count_pending_acks(State) =:= 0.
1357+
1358+
count_pending_acks(#vqstate { ram_pending_ack = RPA,
1359+
disk_pending_ack = DPA,
1360+
qi_pending_ack = QPA }) ->
1361+
gb_trees:size(RPA) + gb_trees:size(DPA) + gb_trees:size(QPA).
1362+
1363+
purge_betas_and_deltas(DelsAndAcksFun, State = #vqstate { q3 = Q3 }) ->
13271364
case ?QUEUE:is_empty(Q3) of
13281365
true -> State;
1329-
false -> State1 = remove_queue_entries(Q3, State),
1330-
purge_betas_and_deltas(maybe_deltas_to_betas(
1366+
false -> State1 = remove_queue_entries(Q3, DelsAndAcksFun, State),
1367+
purge_betas_and_deltas(DelsAndAcksFun,
1368+
maybe_deltas_to_betas(
1369+
DelsAndAcksFun,
13311370
State1#vqstate{q3 = ?QUEUE:new()}))
13321371
end.
13331372

1334-
remove_queue_entries(Q, State = #vqstate{index_state = IndexState,
1335-
msg_store_clients = MSCState}) ->
1373+
remove_queue_entries(Q, DelsAndAcksFun,
1374+
State = #vqstate{msg_store_clients = MSCState}) ->
13361375
{MsgIdsByStore, Delivers, Acks, State1} =
13371376
?QUEUE:foldl(fun remove_queue_entries1/2,
13381377
{orddict:new(), [], [], State}, Q),
13391378
ok = orddict:fold(fun (IsPersistent, MsgIds, ok) ->
13401379
msg_store_remove(MSCState, IsPersistent, MsgIds)
13411380
end, ok, MsgIdsByStore),
1342-
IndexState1 = rabbit_queue_index:ack(
1343-
Acks, rabbit_queue_index:deliver(Delivers, IndexState)),
1344-
State1#vqstate{index_state = IndexState1}.
1381+
DelsAndAcksFun(Delivers, Acks, State1).
13451382

13461383
remove_queue_entries1(
13471384
#msg_status { msg_id = MsgId, seq_id = SeqId, is_delivered = IsDelivered,
@@ -1356,6 +1393,18 @@ remove_queue_entries1(
13561393
cons_if(IndexOnDisk, SeqId, Acks),
13571394
stats({-1, 0}, {MsgStatus, none}, State)}.
13581395

1396+
process_delivers_and_acks_fun(deliver_and_ack) ->
1397+
fun (Delivers, Acks, State = #vqstate { index_state = IndexState }) ->
1398+
IndexState1 =
1399+
rabbit_queue_index:ack(
1400+
Acks, rabbit_queue_index:deliver(Delivers, IndexState)),
1401+
State #vqstate { index_state = IndexState1 }
1402+
end;
1403+
process_delivers_and_acks_fun(_) ->
1404+
fun (_, _, State) ->
1405+
State
1406+
end.
1407+
13591408
%%----------------------------------------------------------------------------
13601409
%% Internal gubbins for publishing
13611410
%%----------------------------------------------------------------------------
@@ -1550,11 +1599,29 @@ remove_pending_ack(false, SeqId, State = #vqstate{ram_pending_ack = RPA,
15501599
end.
15511600

15521601
purge_pending_ack(KeepPersistent,
1553-
State = #vqstate { ram_pending_ack = RPA,
1554-
disk_pending_ack = DPA,
1555-
qi_pending_ack = QPA,
1556-
index_state = IndexState,
1602+
State = #vqstate { index_state = IndexState,
15571603
msg_store_clients = MSCState }) ->
1604+
{IndexOnDiskSeqIds, MsgIdsByStore, State1} = purge_pending_ack1(State),
1605+
case KeepPersistent of
1606+
true -> remove_transient_msgs_by_id(MsgIdsByStore, MSCState),
1607+
State1;
1608+
false -> IndexState1 =
1609+
rabbit_queue_index:ack(IndexOnDiskSeqIds, IndexState),
1610+
remove_msgs_by_id(MsgIdsByStore, MSCState),
1611+
State1 #vqstate { index_state = IndexState1 }
1612+
end.
1613+
1614+
purge_pending_ack_delete_and_terminate(
1615+
State = #vqstate { index_state = IndexState,
1616+
msg_store_clients = MSCState }) ->
1617+
{_, MsgIdsByStore, State1} = purge_pending_ack1(State),
1618+
IndexState1 = rabbit_queue_index:delete_and_terminate(IndexState),
1619+
remove_msgs_by_id(MsgIdsByStore, MSCState),
1620+
State1 #vqstate { index_state = IndexState1 }.
1621+
1622+
purge_pending_ack1(State = #vqstate { ram_pending_ack = RPA,
1623+
disk_pending_ack = DPA,
1624+
qi_pending_ack = QPA }) ->
15581625
F = fun (_SeqId, MsgStatus, Acc) -> accumulate_ack(MsgStatus, Acc) end,
15591626
{IndexOnDiskSeqIds, MsgIdsByStore, _AllMsgIds} =
15601627
rabbit_misc:gb_trees_fold(
@@ -1564,19 +1631,26 @@ purge_pending_ack(KeepPersistent,
15641631
State1 = State #vqstate { ram_pending_ack = gb_trees:empty(),
15651632
disk_pending_ack = gb_trees:empty(),
15661633
qi_pending_ack = gb_trees:empty()},
1634+
{IndexOnDiskSeqIds, MsgIdsByStore, State1}.
15671635

1568-
case KeepPersistent of
1569-
true -> case orddict:find(false, MsgIdsByStore) of
1570-
error -> State1;
1571-
{ok, MsgIds} -> ok = msg_store_remove(MSCState, false,
1572-
MsgIds),
1573-
State1
1574-
end;
1575-
false -> IndexState1 =
1576-
rabbit_queue_index:ack(IndexOnDiskSeqIds, IndexState),
1577-
[ok = msg_store_remove(MSCState, IsPersistent, MsgIds)
1578-
|| {IsPersistent, MsgIds} <- orddict:to_list(MsgIdsByStore)],
1579-
State1 #vqstate { index_state = IndexState1 }
1636+
%% MsgIdsByStore is an orddict with two keys:
1637+
%%
1638+
%% true: holds a list of Persistent Message Ids.
1639+
%% false: holds a list of Transient Message Ids.
1640+
%%
1641+
%% When we call orddict:to_list/1 we get two sets of msg ids, where
1642+
%% IsPersistent is either true for persistent messages or false for
1643+
%% transient ones. The msg_store_remove/3 function takes this boolean
1644+
%% flag to determine from which store the messages should be removed
1645+
%% from.
1646+
remove_msgs_by_id(MsgIdsByStore, MSCState) ->
1647+
[ok = msg_store_remove(MSCState, IsPersistent, MsgIds)
1648+
|| {IsPersistent, MsgIds} <- orddict:to_list(MsgIdsByStore)].
1649+
1650+
remove_transient_msgs_by_id(MsgIdsByStore, MSCState) ->
1651+
case orddict:find(false, MsgIdsByStore) of
1652+
error -> ok;
1653+
{ok, MsgIds} -> ok = msg_store_remove(MSCState, false, MsgIds)
15801654
end.
15811655

15821656
accumulate_ack_init() -> {[], orddict:new(), []}.
@@ -1910,9 +1984,15 @@ fetch_from_q3(State = #vqstate { q1 = Q1,
19101984
{loaded, {MsgStatus, State2}}
19111985
end.
19121986

1913-
maybe_deltas_to_betas(State = #vqstate { delta = ?BLANK_DELTA_PATTERN(X) }) ->
1987+
maybe_deltas_to_betas(State) ->
1988+
AfterFun = process_delivers_and_acks_fun(deliver_and_ack),
1989+
maybe_deltas_to_betas(AfterFun, State).
1990+
1991+
maybe_deltas_to_betas(_DelsAndAcksFun,
1992+
State = #vqstate {delta = ?BLANK_DELTA_PATTERN(X) }) ->
19141993
State;
1915-
maybe_deltas_to_betas(State = #vqstate {
1994+
maybe_deltas_to_betas(DelsAndAcksFun,
1995+
State = #vqstate {
19161996
q2 = Q2,
19171997
delta = Delta,
19181998
q3 = Q3,
@@ -1932,34 +2012,35 @@ maybe_deltas_to_betas(State = #vqstate {
19322012
DeltaSeqIdEnd]),
19332013
{List, IndexState1} = rabbit_queue_index:read(DeltaSeqId, DeltaSeqId1,
19342014
IndexState),
1935-
{Q3a, RamCountsInc, RamBytesInc, IndexState2} =
2015+
{Q3a, RamCountsInc, RamBytesInc, State1} =
19362016
betas_from_index_entries(List, TransientThreshold,
1937-
RPA, DPA, QPA, IndexState1),
1938-
State1 = State #vqstate { index_state = IndexState2,
1939-
ram_msg_count = RamMsgCount + RamCountsInc,
1940-
ram_bytes = RamBytes + RamBytesInc,
1941-
disk_read_count = DiskReadCount + RamCountsInc},
2017+
RPA, DPA, QPA, DelsAndAcksFun,
2018+
State #vqstate { index_state = IndexState1 }),
2019+
State2 = State1 #vqstate { ram_msg_count = RamMsgCount + RamCountsInc,
2020+
ram_bytes = RamBytes + RamBytesInc,
2021+
disk_read_count = DiskReadCount + RamCountsInc },
19422022
case ?QUEUE:len(Q3a) of
19432023
0 ->
19442024
%% we ignored every message in the segment due to it being
19452025
%% transient and below the threshold
19462026
maybe_deltas_to_betas(
1947-
State1 #vqstate {
2027+
DelsAndAcksFun,
2028+
State2 #vqstate {
19482029
delta = d(Delta #delta { start_seq_id = DeltaSeqId1 })});
19492030
Q3aLen ->
19502031
Q3b = ?QUEUE:join(Q3, Q3a),
19512032
case DeltaCount - Q3aLen of
19522033
0 ->
19532034
%% delta is now empty, but it wasn't before, so
19542035
%% can now join q2 onto q3
1955-
State1 #vqstate { q2 = ?QUEUE:new(),
2036+
State2 #vqstate { q2 = ?QUEUE:new(),
19562037
delta = ?BLANK_DELTA,
19572038
q3 = ?QUEUE:join(Q3b, Q2) };
19582039
N when N > 0 ->
19592040
Delta1 = d(#delta { start_seq_id = DeltaSeqId1,
19602041
count = N,
19612042
end_seq_id = DeltaSeqIdEnd }),
1962-
State1 #vqstate { delta = Delta1,
2043+
State2 #vqstate { delta = Delta1,
19632044
q3 = Q3b }
19642045
end
19652046
end.

0 commit comments

Comments
 (0)