Skip to content

Commit 5cdee15

Browse files
committed
Do not lock entire routing table when cleaning up bindings.
Instead of locking entire table we can use a custom global lock on the affected resource (source or destination). This can improve performance when multiple records deleted at the same time, for example when connection with exclusive queues closes. Resource lock also aquired when adding or removing a binding, so it won't conflict with bulk removal. Addresses #1566 [#156352963]
1 parent 4005308 commit 5cdee15

File tree

3 files changed

+40
-36
lines changed

3 files changed

+40
-36
lines changed

src/rabbit_amqqueue.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -981,7 +981,7 @@ internal_delete(QueueName, ActingUser) ->
981981
?INTERNAL_USER),
982982
fun() ->
983983
ok = T(),
984-
rabbit_core_metrics:queue_deleted(QueueName),
984+
rabbit_core_metrics:queue_deleted(QueueName),
985985
ok = rabbit_event:notify(queue_deleted,
986986
[{name, QueueName},
987987
{user_who_performed_action, ActingUser}])

src/rabbit_amqqueue_process.erl

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -311,8 +311,14 @@ terminate_delete(EmitStats, Reason,
311311
fun() -> emit_stats(State) end);
312312
true -> ok
313313
end,
314-
%% don't care if the internal delete doesn't return 'ok'.
315-
rabbit_amqqueue:internal_delete(QName, ActingUser),
314+
%% This try-catch block transforms throws to errors since throws are not
315+
%% logged.
316+
try
317+
%% don't care if the internal delete doesn't return 'ok'.
318+
rabbit_amqqueue:internal_delete(QName, ActingUser)
319+
catch
320+
{error, Reason} -> error(Reason)
321+
end,
316322
BQS1
317323
end.
318324

src/rabbit_binding.erl

Lines changed: 31 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@ add(Binding, InnerFun, ActingUser) ->
166166
fun (Src, Dst, B) ->
167167
case rabbit_exchange:validate_binding(Src, B) of
168168
ok ->
169+
lock_resource(Src),
170+
lock_resource(Dst),
169171
%% this argument is used to check queue exclusivity;
170172
%% in general, we want to fail on that in preference to
171173
%% anything else
@@ -184,6 +186,8 @@ add(Binding, InnerFun, ActingUser) ->
184186
end, fun not_found_or_absent_errs/1).
185187

186188
add(Src, Dst, B, ActingUser) ->
189+
lock_resource(Src),
190+
lock_resource(Dst),
187191
[SrcDurable, DstDurable] = [durable(E) || E <- [Src, Dst]],
188192
case (SrcDurable andalso DstDurable andalso
189193
mnesia:read({rabbit_durable_route, B}) =/= []) of
@@ -206,6 +210,8 @@ remove(Binding, InnerFun, ActingUser) ->
206210
binding_action(
207211
Binding,
208212
fun (Src, Dst, B) ->
213+
lock_resource(Src),
214+
lock_resource(Dst),
209215
case mnesia:read(rabbit_route, B, write) of
210216
[] -> case mnesia:read(rabbit_durable_route, B, write) of
211217
[] -> rabbit_misc:const(ok);
@@ -219,6 +225,8 @@ remove(Binding, InnerFun, ActingUser) ->
219225
end, fun absent_errs_only/1).
220226

221227
remove(Src, Dst, B, ActingUser) ->
228+
lock_resource(Src),
229+
lock_resource(Dst),
222230
ok = sync_route(#route{binding = B}, durable(Src), durable(Dst),
223231
fun mnesia:delete_object/3),
224232
Deletions = maybe_auto_delete(
@@ -303,12 +311,12 @@ has_for_source(SrcName) ->
303311
contains(rabbit_semi_durable_route, Match).
304312

305313
remove_for_source(SrcName) ->
306-
lock_route_tables(),
314+
lock_resource(SrcName),
307315
Match = #route{binding = #binding{source = SrcName, _ = '_'}},
308316
remove_routes(
309317
lists:usort(
310-
mnesia:match_object(rabbit_route, Match, write) ++
311-
mnesia:match_object(rabbit_semi_durable_route, Match, write))).
318+
mnesia:dirty_match_object(rabbit_route, Match) ++
319+
mnesia:dirty_match_object(rabbit_semi_durable_route, Match))).
312320

313321
remove_for_destination(DstName, OnlyDurable) ->
314322
remove_for_destination(DstName, OnlyDurable, fun remove_routes/1).
@@ -393,32 +401,12 @@ continue('$end_of_table') -> false;
393401
continue({[_|_], _}) -> true;
394402
continue({[], Continuation}) -> continue(mnesia:select(Continuation)).
395403

396-
%% For bulk operations we lock the tables we are operating on in order
397-
%% to reduce the time complexity. Without the table locks we end up
398-
%% with num_tables*num_bulk_bindings row-level locks. Taking each lock
399-
%% takes time proportional to the number of existing locks, thus
400-
%% resulting in O(num_bulk_bindings^2) complexity.
401-
%%
402-
%% The locks need to be write locks since ultimately we end up
403-
%% removing all these rows.
404-
%%
405-
%% The downside of all this is that no other binding operations except
406-
%% lookup/routing (which uses dirty ops) can take place
407-
%% concurrently. However, that is the case already since the bulk
408-
%% operations involve mnesia:match_object calls with a partial key,
409-
%% which entails taking a table lock.
410-
lock_route_tables() ->
411-
[mnesia:lock({table, T}, write) || T <- [rabbit_route,
412-
rabbit_reverse_route,
413-
rabbit_semi_durable_route,
414-
rabbit_durable_route]].
415-
416404
remove_routes(Routes) ->
417405
%% This partitioning allows us to suppress unnecessary delete
418406
%% operations on disk tables, which require an fsync.
419407
{RamRoutes, DiskRoutes} =
420-
lists:partition(fun (R) -> mnesia:match_object(
421-
rabbit_durable_route, R, write) == [] end,
408+
lists:partition(fun (R) -> mnesia:dirty_match_object(
409+
rabbit_durable_route, R) == [] end,
422410
Routes),
423411
%% Of course the destination might not really be durable but it's
424412
%% just as easy to try to delete it from the semi-durable table
@@ -436,23 +424,33 @@ remove_transient_routes(Routes) ->
436424
end || R <- Routes].
437425

438426
remove_for_destination(DstName, OnlyDurable, Fun) ->
439-
lock_route_tables(),
427+
lock_resource(DstName),
440428
MatchFwd = #route{binding = #binding{destination = DstName, _ = '_'}},
441429
MatchRev = reverse_route(MatchFwd),
442430
Routes = case OnlyDurable of
443-
false -> [reverse_route(R) ||
444-
R <- mnesia:match_object(
445-
rabbit_reverse_route, MatchRev, write)];
431+
false ->
432+
[reverse_route(R) ||
433+
R <- mnesia:dirty_match_object(
434+
rabbit_reverse_route, MatchRev)];
446435
true -> lists:usort(
447-
mnesia:match_object(
448-
rabbit_durable_route, MatchFwd, write) ++
449-
mnesia:match_object(
450-
rabbit_semi_durable_route, MatchFwd, write))
436+
mnesia:dirty_match_object(
437+
rabbit_durable_route, MatchFwd) ++
438+
mnesia:dirty_match_object(
439+
rabbit_semi_durable_route, MatchFwd))
451440
end,
452441
Bindings = Fun(Routes),
453442
group_bindings_fold(fun maybe_auto_delete/4, new_deletions(),
454443
lists:keysort(#binding.source, Bindings), OnlyDurable).
455444

445+
%% Instead of locking entire table on remove operations we can lock the
446+
%% affected resource only. This will allow us to use dirty_match_object for
447+
%% do faster search of records to delete.
448+
%% This works better when there are multiple resources deleted at once, for
449+
%% example when exclusive queues are deleted.
450+
lock_resource(Name) ->
451+
mnesia:lock({global, Name, mnesia:table_info(rabbit_route, where_to_write)},
452+
write).
453+
456454
%% Requires that its input binding list is sorted in exchange-name
457455
%% order, so that the grouping of bindings (for passing to
458456
%% group_bindings_and_auto_delete1) works properly.

0 commit comments

Comments
 (0)