Skip to content

Commit e936bf3

Browse files
authored
Merge pull request #636 from nextcloud/debouce-smear
spread out debouce times per-connection
2 parents f66ef2d + 9fe46cb commit e936bf3

2 files changed

Lines changed: 54 additions & 31 deletions

File tree

src/connection.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crate::{App, UserId};
1212
use dashmap::mapref::entry::Entry;
1313
use dashmap::DashMap;
1414
use futures::{future::select, pin_mut, SinkExt, StreamExt};
15-
use rand::{Rng, SeedableRng};
15+
use rand::{thread_rng, Rng, SeedableRng};
1616
use std::net::IpAddr;
1717
use std::num::NonZeroUsize;
1818
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
@@ -131,11 +131,15 @@ pub async fn handle_user_socket(
131131
let expect_pong = &expect_pong;
132132

133133
let transmit = async {
134-
// Use faster random generator for generating ping messages, they dont need to be
135-
// cryptographically secure. It is also OK to use same sequence for every connection.
136-
let mut rng = rand::rngs::SmallRng::seed_from_u64(0);
134+
// Use faster random generator for generating ping messages and time smearthey dont need to be
135+
// cryptographically secure.
136+
let mut rng =
137+
rand::rngs::SmallRng::from_rng(thread_rng()).expect("Failed to initialize rng");
137138

138-
let mut send_queue = SendQueue::default();
139+
// for each connection we randomize the max debounce time to remove the chance that many connections
140+
// get messages at the same time and cause load peaks
141+
let debounce_factor = rng.gen_range(0.5..1.5);
142+
let mut send_queue = SendQueue::new(opts.max_debounce_time, debounce_factor);
139143

140144
let mut reset = app.reset_rx();
141145

@@ -162,7 +166,7 @@ pub async fn handle_user_socket(
162166
break 'tx_loop;
163167
}
164168

165-
for msg in send_queue.drain(now, METRICS.active_connection_count() + 50000, opts.max_debounce_time) {
169+
for msg in send_queue.drain(now, METRICS.active_connection_count()) {
166170
last_send = now;
167171
METRICS.add_message(msg.message_type());
168172
log::debug!(target: "notify_push::send", "Sending debounced {msg} to {user_id}");

src/message.rs

Lines changed: 44 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,21 @@ impl PushMessage {
5959
}
6060
}
6161

62-
pub fn debounce_time(&self, connection_count: usize, max_debounce_time: usize) -> Duration {
62+
pub fn debounce_time(
63+
&self,
64+
connection_count: usize,
65+
max_debounce_time: usize,
66+
debounce_factor: f32,
67+
) -> Duration {
6368
// scale the debounce time between 1s and 15s based on the number of active connections
64-
// this provide a decent balance between performance and load
65-
let time = max(1, min(connection_count / 10, max_debounce_time));
69+
// this provides a decent balance between performance and load.
70+
// Additionally, each connection will have a random debounce_factor between 0.5 and 1.5
71+
// to spread out the load of notifications.
72+
let time = max(1, min(connection_count / 10, max_debounce_time)) as f32;
73+
let time = time * debounce_factor;
6674
match self {
67-
PushMessage::File(_) => Duration::from_secs(time as u64),
68-
PushMessage::Activity => Duration::from_secs(time as u64),
75+
PushMessage::File(_) => Duration::from_secs_f32(time),
76+
PushMessage::Activity => Duration::from_secs_f32(time),
6977
PushMessage::Notification => Duration::from_secs(1),
7078
PushMessage::Custom(..) => Duration::from_millis(1), // no debouncing for custom messages
7179
}
@@ -132,14 +140,23 @@ impl Default for SendQueueItem {
132140
}
133141
}
134142

135-
#[derive(Default, Debug)]
143+
/// Queue for sending outgoing messages to a user for debounce
144+
///
145+
/// The server maintains once queue per connection
146+
#[derive(Debug)]
136147
pub struct SendQueue {
148+
max_debounce_time: usize,
149+
debounce_factor: f32,
137150
items: [SendQueueItem; 3],
138151
}
139152

140153
impl SendQueue {
141-
pub fn new() -> Self {
142-
SendQueue::default()
154+
pub fn new(max_debounce_time: usize, debounce_factor: f32) -> Self {
155+
SendQueue {
156+
max_debounce_time,
157+
debounce_factor,
158+
items: Default::default(),
159+
}
143160
}
144161

145162
fn item_mut(&mut self, message: &PushMessage) -> Option<&mut SendQueueItem> {
@@ -177,13 +194,15 @@ impl SendQueue {
177194
&mut self,
178195
now: Instant,
179196
connection_count: usize,
180-
max_debounce_time: usize,
181197
) -> impl Iterator<Item = PushMessage> + '_ {
198+
let max_debounce_time = self.max_debounce_time;
199+
let debounce_factor = self.debounce_factor;
182200
self.items.iter_mut().filter_map(move |item| {
183-
let debounce_time = item
184-
.message
185-
.as_ref()?
186-
.debounce_time(connection_count, max_debounce_time);
201+
let debounce_time = item.message.as_ref()?.debounce_time(
202+
connection_count,
203+
max_debounce_time,
204+
debounce_factor,
205+
);
187206
if now.duration_since(item.sent) > debounce_time {
188207
if now.duration_since(item.received) > Duration::from_millis(100) {
189208
item.sent = now;
@@ -201,7 +220,7 @@ impl SendQueue {
201220
#[test]
202221
fn test_send_queue_100() {
203222
let base_time = Instant::now();
204-
let mut queue = SendQueue::new();
223+
let mut queue = SendQueue::new(15, 1.0);
205224
queue.push(PushMessage::Activity, base_time);
206225
queue.push(
207226
PushMessage::File(UpdatedFiles::Known(vec![1].into())),
@@ -216,7 +235,7 @@ fn test_send_queue_100() {
216235
assert_eq!(
217236
Vec::<PushMessage>::new(),
218237
queue
219-
.drain(base_time + Duration::from_millis(20), 100, 15)
238+
.drain(base_time + Duration::from_millis(20), 100)
220239
.collect::<Vec<_>>()
221240
);
222241

@@ -227,7 +246,7 @@ fn test_send_queue_100() {
227246
PushMessage::Activity
228247
],
229248
queue
230-
.drain(base_time + Duration::from_millis(200), 100, 15)
249+
.drain(base_time + Duration::from_millis(200), 100)
231250
.collect::<Vec<_>>()
232251
);
233252

@@ -243,31 +262,31 @@ fn test_send_queue_100() {
243262
assert_eq!(
244263
Vec::<PushMessage>::new(),
245264
queue
246-
.drain(base_time + Duration::from_secs(10), 100, 15)
265+
.drain(base_time + Duration::from_secs(10), 100)
247266
.collect::<Vec<_>>()
248267
);
249268

250269
// after debounce time we get the merged messages from the timeframe
251270
assert_eq!(
252271
vec![PushMessage::File(UpdatedFiles::Known(vec![3, 4].into()))],
253272
queue
254-
.drain(base_time + Duration::from_secs(70), 100, 15)
273+
.drain(base_time + Duration::from_secs(70), 100)
255274
.collect::<Vec<_>>()
256275
);
257276

258277
// nothing left
259278
assert_eq!(
260279
Vec::<PushMessage>::new(),
261280
queue
262-
.drain(base_time + Duration::from_secs(300), 100, 15)
281+
.drain(base_time + Duration::from_secs(300), 100)
263282
.collect::<Vec<_>>()
264283
);
265284
}
266285

267286
#[test]
268287
fn test_send_queue_1() {
269288
let base_time = Instant::now();
270-
let mut queue = SendQueue::new();
289+
let mut queue = SendQueue::new(15, 1.0);
271290
queue.push(PushMessage::Activity, base_time);
272291
queue.push(
273292
PushMessage::File(UpdatedFiles::Known(vec![1].into())),
@@ -282,7 +301,7 @@ fn test_send_queue_1() {
282301
assert_eq!(
283302
Vec::<PushMessage>::new(),
284303
queue
285-
.drain(base_time + Duration::from_millis(20), 1, 15)
304+
.drain(base_time + Duration::from_millis(20), 1)
286305
.collect::<Vec<_>>()
287306
);
288307

@@ -293,7 +312,7 @@ fn test_send_queue_1() {
293312
PushMessage::Activity
294313
],
295314
queue
296-
.drain(base_time + Duration::from_millis(200), 1, 15)
315+
.drain(base_time + Duration::from_millis(200), 1)
297316
.collect::<Vec<_>>()
298317
);
299318

@@ -309,23 +328,23 @@ fn test_send_queue_1() {
309328
assert_eq!(
310329
Vec::<PushMessage>::new(),
311330
queue
312-
.drain(base_time + Duration::from_secs(1), 1, 15)
331+
.drain(base_time + Duration::from_secs(1), 1)
313332
.collect::<Vec<_>>()
314333
);
315334

316335
// after debounce time we get the merged messages from the timeframe
317336
assert_eq!(
318337
vec![PushMessage::File(UpdatedFiles::Known(vec![3, 4].into()))],
319338
queue
320-
.drain(base_time + Duration::from_secs(3), 1, 15)
339+
.drain(base_time + Duration::from_secs(3), 1)
321340
.collect::<Vec<_>>()
322341
);
323342

324343
// nothing left
325344
assert_eq!(
326345
Vec::<PushMessage>::new(),
327346
queue
328-
.drain(base_time + Duration::from_secs(5), 1, 15)
347+
.drain(base_time + Duration::from_secs(5), 1)
329348
.collect::<Vec<_>>()
330349
);
331350
}

0 commit comments

Comments
 (0)