Skip to content

Commit 896b73a

Browse files
emmaling27Convex, Inc.
authored andcommitted
Add splay to subscription invalidations (#38941)
This PR adds splay to subscription invalidation. Code push invalidates all user queries because the modules change, which creates a spike in load. This should smooth the spike in load by adding a delay before sending the message to wake up the subscription. The delay is random, from a range proportional to how many subscribers there are. Also adds metrics and logs. GitOrigin-RevId: 6d5b430a930d3d8a2acc61b6a93febe3bf8a7935
1 parent 5886b39 commit 896b73a

File tree

3 files changed

+69
-9
lines changed

3 files changed

+69
-9
lines changed

crates/common/src/knobs.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1347,3 +1347,14 @@ pub static SUBSCRIPTIONS_WORKER_QUEUE_SIZE: LazyLock<usize> =
13471347
/// search query fails because indexes are bootstrapping.
13481348
pub static SEARCH_INDEXES_UNAVAILABLE_RETRY_DELAY: LazyLock<Duration> =
13491349
LazyLock::new(|| Duration::from_secs(env_config("SEARCH_INDEXES_UNAVAILABLE_RETRY_DELAY", 3)));
1350+
1351+
/// The maximum number of subscriptions that can be invalidated immediately. If
1352+
/// there are more, they will be splayed out.
1353+
pub static SUBSCRIPTION_INVALIDATION_DELAY_THRESHOLD: LazyLock<usize> =
1354+
LazyLock::new(|| env_config("SUBSCRIPTION_INVALIDATION_DELAY_THRESHOLD", 200));
1355+
1356+
/// How much to splay subscription invalidations. More precisely, this is the
1357+
/// number used to multiply by the number of subscriptions that need to be
1358+
/// invalidated to determine the delay before invalidating them.
1359+
pub static SUBSCRIPTION_INVALIDATION_DELAY_MULTIPLIER: LazyLock<u64> =
1360+
LazyLock::new(|| env_config("SUBSCRIPTION_INVALIDATION_DELAY_MULTIPLIER", 5));

crates/database/src/metrics.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1078,3 +1078,11 @@ register_convex_histogram!(
10781078
pub fn log_list_snapshot_page_documents(num_docs: usize) {
10791079
log_distribution(&LIST_SNAPSHOT_PAGE_DOCUMENTS, num_docs as f64);
10801080
}
1081+
1082+
register_convex_histogram!(
1083+
SUBSCRIPTION_INVALIDATION_UPDATES,
1084+
"Number of subscriptions invalidated when advancing the log",
1085+
);
1086+
pub fn log_subscriptions_invalidated(num: usize) {
1087+
log_distribution(&SUBSCRIPTION_INVALIDATION_UPDATES, num as f64);
1088+
}

crates/database/src/subscription.rs

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use std::{
1414
},
1515
Arc,
1616
},
17+
time::Duration,
1718
};
1819

1920
use ::metrics::Timer;
@@ -24,7 +25,11 @@ use common::{
2425
DocumentIndexKeys,
2526
},
2627
errors::report_error,
27-
knobs::SUBSCRIPTIONS_WORKER_QUEUE_SIZE,
28+
knobs::{
29+
SUBSCRIPTIONS_WORKER_QUEUE_SIZE,
30+
SUBSCRIPTION_INVALIDATION_DELAY_MULTIPLIER,
31+
SUBSCRIPTION_INVALIDATION_DELAY_THRESHOLD,
32+
},
2833
runtime::{
2934
block_in_place,
3035
Runtime,
@@ -58,7 +63,10 @@ use tokio::sync::{
5863
use value::ResolvedDocumentId;
5964

6065
use crate::{
61-
metrics,
66+
metrics::{
67+
self,
68+
log_subscriptions_invalidated,
69+
},
6270
reads::ReadSet,
6371
write_log::{
6472
LogOwner,
@@ -116,6 +124,20 @@ impl Drop for SubscriptionSender {
116124
}
117125
}
118126

127+
impl SubscriptionSender {
128+
fn drop_with_delay(self, delay: Option<Duration>) {
129+
self.valid_ts.store(-1, Ordering::SeqCst);
130+
if let Some(delay) = delay {
131+
tokio::spawn(async move {
132+
tokio::time::sleep(delay).await;
133+
_ = self.valid_tx.send(SubscriptionState::Invalid);
134+
});
135+
} else {
136+
_ = self.valid_tx.send(SubscriptionState::Invalid);
137+
}
138+
}
139+
}
140+
119141
enum SubscriptionRequest {
120142
Subscribe {
121143
token: Token,
@@ -314,9 +336,27 @@ impl SubscriptionManager {
314336
}
315337
}
316338
// Then, invalidate all the remaining subscriptions.
339+
let num_subscriptions_invalidated = to_notify.len();
340+
let should_splay_invalidations =
341+
num_subscriptions_invalidated > *SUBSCRIPTION_INVALIDATION_DELAY_THRESHOLD;
342+
if should_splay_invalidations {
343+
tracing::info!(
344+
"Splaying subscription invalidations since there are {} subscriptions to \
345+
invalidate. The threshold is {}",
346+
num_subscriptions_invalidated,
347+
*SUBSCRIPTION_INVALIDATION_DELAY_THRESHOLD
348+
);
349+
}
317350
for subscriber_id in to_notify {
318-
self._remove(subscriber_id);
351+
let delay = should_splay_invalidations.then(|| {
352+
Duration::from_millis(rand::random_range(
353+
0..=num_subscriptions_invalidated as u64
354+
* *SUBSCRIPTION_INVALIDATION_DELAY_MULTIPLIER,
355+
))
356+
});
357+
self._remove(subscriber_id, delay);
319358
}
359+
log_subscriptions_invalidated(num_subscriptions_invalidated);
320360

321361
assert!(self.processed_ts <= next_ts);
322362
self.processed_ts = next_ts;
@@ -369,13 +409,14 @@ impl SubscriptionManager {
369409
if self.get_subscriber(key).is_none() {
370410
return;
371411
}
372-
self._remove(key.id);
412+
self._remove(key.id, None);
373413
}
374414

375-
fn _remove(&mut self, id: SubscriberId) {
415+
fn _remove(&mut self, id: SubscriberId, delay: Option<Duration>) {
376416
let entry = self.subscribers.remove(id);
377417
self.subscriptions.remove(id, &entry.reads);
378418
// dropping `entry.sender` will invalidate the subscription
419+
entry.sender.drop_with_delay(delay);
379420
}
380421
}
381422

@@ -779,7 +820,7 @@ mod tests {
779820
.subscribe_for_testing(token.clone())
780821
.unwrap();
781822
subscriptions.push(subscriber);
782-
subscription_manager._remove(id);
823+
subscription_manager._remove(id, None);
783824
}
784825

785826
assert!(
@@ -817,7 +858,7 @@ mod tests {
817858
let (_subscription, id) = subscription_manager
818859
.subscribe_for_testing(token.clone())
819860
.unwrap();
820-
subscription_manager._remove(id);
861+
subscription_manager._remove(id, None);
821862

822863
assert!(notify_subscribed_tokens(
823864
&mut id_generator,
@@ -891,7 +932,7 @@ mod tests {
891932
);
892933
}
893934
for (_, id) in &subscriptions {
894-
subscription_manager._remove(*id);
935+
subscription_manager._remove(*id, None);
895936
}
896937
let notifications = notify_subscribed_tokens(
897938
&mut id_generator,
@@ -916,7 +957,7 @@ mod tests {
916957
for token in &tokens {
917958
let (_subscription, id) = subscription_manager
918959
.subscribe_for_testing(token.clone()).unwrap();
919-
subscription_manager._remove(id);
960+
subscription_manager._remove(id, None);
920961
}
921962
let notifications = notify_subscribed_tokens(
922963
&mut id_generator,

0 commit comments

Comments
 (0)