9
9
10
10
-include_lib (" rabbit_common/include/rabbit.hrl" ).
11
11
12
- -export ([master_prepare /4 , master_go /8 , slave /7 , conserve_resources /3 ]).
12
+ -export ([master_prepare /4 , master_go /9 , slave /7 , conserve_resources /3 ]).
13
+
14
+ % % Export for UTs
15
+ -export ([maybe_master_batch_send /2 , get_time_diff /3 , append_to_acc /4 ]).
13
16
14
17
-define (SYNC_PROGRESS_INTERVAL , 1000000 ).
15
18
19
+ -define (SYNC_THROUGHPUT_EVAL_INTERVAL_MILLIS , 50 ).
20
+
16
21
% % There are three processes around, the master, the syncer and the
17
22
% % slave(s). The syncer is an intermediary, linked to the master in
18
23
% % order to make sure we do not mess with the master's credit flow or
@@ -67,23 +72,24 @@ master_prepare(Ref, QName, Log, SPids) ->
67
72
rabbit_mirror_queue_master :stats_fun (),
68
73
rabbit_mirror_queue_master :stats_fun (),
69
74
non_neg_integer (),
75
+ non_neg_integer (),
70
76
bq (), bqs ()) ->
71
77
{'already_synced' , bqs ()} | {'ok' , bqs ()} |
72
78
{'cancelled' , bqs ()} |
73
79
{'shutdown' , any (), bqs ()} |
74
80
{'sync_died' , any (), bqs ()}.
75
81
76
- master_go (Syncer , Ref , Log , HandleInfo , EmitStats , SyncBatchSize , BQ , BQS ) ->
82
+ master_go (Syncer , Ref , Log , HandleInfo , EmitStats , SyncBatchSize , SyncThroughput , BQ , BQS ) ->
77
83
Args = {Syncer , Ref , Log , HandleInfo , EmitStats , rabbit_misc :get_parent ()},
78
84
receive
79
85
{'EXIT' , Syncer , normal } -> {already_synced , BQS };
80
86
{'EXIT' , Syncer , Reason } -> {sync_died , Reason , BQS };
81
87
{ready , Syncer } -> EmitStats ({syncing , 0 }),
82
- master_batch_go0 (Args , SyncBatchSize ,
88
+ master_batch_go0 (Args , SyncBatchSize , SyncThroughput ,
83
89
BQ , BQS )
84
90
end .
85
91
86
- master_batch_go0 (Args , BatchSize , BQ , BQS ) ->
92
+ master_batch_go0 (Args , BatchSize , SyncThroughput , BQ , BQS ) ->
87
93
FoldFun =
88
94
fun (Msg , MsgProps , Unacked , Acc ) ->
89
95
Acc1 = append_to_acc (Msg , MsgProps , Unacked , Acc ),
@@ -92,24 +98,27 @@ master_batch_go0(Args, BatchSize, BQ, BQS) ->
92
98
false -> {cont , Acc1 }
93
99
end
94
100
end ,
95
- FoldAcc = {[], 0 , {0 , BQ :depth (BQS )}, erlang :monotonic_time ()},
101
+ FoldAcc = {[], 0 , {0 , erlang : monotonic_time (), SyncThroughput }, { 0 , BQ :depth (BQS )}, erlang :monotonic_time ()},
96
102
bq_fold (FoldFun , FoldAcc , Args , BQ , BQS ).
97
103
98
104
master_batch_send ({Syncer , Ref , Log , HandleInfo , EmitStats , Parent },
99
- {Batch , I , {Curr , Len }, Last }) ->
105
+ {Batch , I , {TotalBytes , LastCheck , SyncThroughput }, { Curr , Len }, Last }) ->
100
106
T = maybe_emit_stats (Last , I , EmitStats , Log ),
101
107
HandleInfo ({syncing , I }),
102
108
handle_set_maximum_since_use (),
103
109
SyncMsg = {msgs , Ref , lists :reverse (Batch )},
104
- NewAcc = {[], I + length (Batch ), {Curr , Len }, T },
110
+ NewAcc = {[], I + length (Batch ), {TotalBytes , LastCheck , SyncThroughput }, { Curr , Len }, T },
105
111
master_send_receive (SyncMsg , NewAcc , Syncer , Ref , Parent ).
106
112
107
113
% % Either send messages when we reach the last one in the queue or
108
114
% % whenever we have accumulated BatchSize messages.
109
- maybe_master_batch_send ({_ , _ , {Len , Len }, _ }, _BatchSize ) ->
115
+ maybe_master_batch_send ({_ , _ , _ , {Len , Len }, _ }, _BatchSize ) ->
116
+ true ;
117
+ maybe_master_batch_send ({_ , _ , _ , {Curr , _Len }, _ }, BatchSize )
118
+ when Curr rem BatchSize =:= 0 ->
110
119
true ;
111
- maybe_master_batch_send ({_ , _ , {Curr , _Len }, _ }, BatchSize )
112
- when Curr rem BatchSize =:= 0 ->
120
+ maybe_master_batch_send ({_ , _ , {TotalBytes , _ , SyncThroughput }, { _Curr , _Len }, _ }, _BatchSize )
121
+ when TotalBytes > SyncThroughput ->
113
122
true ;
114
123
maybe_master_batch_send (_Acc , _BatchSize ) ->
115
124
false .
@@ -121,8 +130,10 @@ bq_fold(FoldFun, FoldAcc, Args, BQ, BQS) ->
121
130
{_ , BQS1 } -> master_done (Args , BQS1 )
122
131
end .
123
132
124
- append_to_acc (Msg , MsgProps , Unacked , {Batch , I , {Curr , Len }, T }) ->
125
- {[{Msg , MsgProps , Unacked } | Batch ], I , {Curr + 1 , Len }, T }.
133
+ append_to_acc (Msg , MsgProps , Unacked , {Batch , I , {_ , _ , 0 }, {Curr , Len }, T }) ->
134
+ {[{Msg , MsgProps , Unacked } | Batch ], I , {0 , 0 , 0 }, {Curr + 1 , Len }, T };
135
+ append_to_acc (Msg , MsgProps , Unacked , {Batch , I , {TotalBytes , LastCheck , SyncThroughput }, {Curr , Len }, T }) ->
136
+ {[{Msg , MsgProps , Unacked } | Batch ], I , {TotalBytes + rabbit_basic :msg_size (Msg ), LastCheck , SyncThroughput }, {Curr + 1 , Len }, T }.
126
137
127
138
master_send_receive (SyncMsg , NewAcc , Syncer , Ref , Parent ) ->
128
139
receive
@@ -131,11 +142,44 @@ master_send_receive(SyncMsg, NewAcc, Syncer, Ref, Parent) ->
131
142
gen_server2 :reply (From , ok ),
132
143
{stop , cancelled };
133
144
{next , Ref } -> Syncer ! SyncMsg ,
134
- {cont , NewAcc };
145
+ {Msgs , I , {TotalBytes , LastCheck , SyncThroughput }, {Curr , Len }, T } = NewAcc ,
146
+ {NewTotalBytes , NewLastCheck } = maybe_throttle_sync_throughput (TotalBytes , LastCheck , SyncThroughput ),
147
+ {cont , {Msgs , I , {NewTotalBytes , NewLastCheck , SyncThroughput }, {Curr , Len }, T }};
135
148
{'EXIT' , Parent , Reason } -> {stop , {shutdown , Reason }};
136
149
{'EXIT' , Syncer , Reason } -> {stop , {sync_died , Reason }}
137
150
end .
138
151
152
+ maybe_throttle_sync_throughput (_ , _ , 0 ) ->
153
+ {0 , erlang :monotonic_time ()};
154
+ maybe_throttle_sync_throughput (TotalBytes , LastCheck , SyncThroughput ) ->
155
+ Interval = erlang :convert_time_unit (erlang :monotonic_time () - LastCheck , native , milli_seconds ),
156
+ case Interval > ? SYNC_THROUGHPUT_EVAL_INTERVAL_MILLIS of
157
+ true -> maybe_pause_sync (TotalBytes , Interval , SyncThroughput ),
158
+ {0 , erlang :monotonic_time ()}; % % reset TotalBytes counter and LastCheck.;
159
+ false -> {TotalBytes , LastCheck }
160
+ end .
161
+
162
+ maybe_pause_sync (TotalBytes , Interval , SyncThroughput ) ->
163
+ Delta = get_time_diff (TotalBytes , Interval , SyncThroughput ),
164
+ pause_queue_sync (Delta ).
165
+
166
+ pause_queue_sync (0 ) ->
167
+ rabbit_log_mirroring :debug (" Sync throughput is ok." );
168
+ pause_queue_sync (Delta ) ->
169
+ rabbit_log_mirroring :debug (" Sync throughput exceeds threshold. Pause queue sync for ~p ms" , [Delta ]),
170
+ timer :sleep (Delta ).
171
+
172
+ % % Sync throughput computation:
173
+ % % - Total bytes have been sent since last check: TotalBytes
174
+ % % - Used/Elapsed time since last check: Interval (in milliseconds)
175
+ % % - Effective/Used throughput in bytes/s: TotalBytes/Interval * 1000.
176
+ % % - When UsedThroughput > SyncThroughput -> we need to slow down to compensate over-used rate.
177
+ % % The amount of time to pause queue sync is the different between time needed to broadcast TotalBytes at max throughput
178
+ % % and the elapsed time (Interval).
179
+ get_time_diff (TotalBytes , Interval , SyncThroughput ) ->
180
+ rabbit_log_mirroring :debug (" Total ~p bytes has been sent over last ~p ms. Effective sync througput: ~p " , [TotalBytes , Interval , round (TotalBytes * 1000 / Interval )]),
181
+ max (round (TotalBytes / SyncThroughput * 1000 - Interval ), 0 ).
182
+
139
183
master_done ({Syncer , Ref , _Log , _HandleInfo , _EmitStats , Parent }, BQS ) ->
140
184
receive
141
185
{'$gen_call' , From ,
0 commit comments