You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
synctv/synctv-api-common/src/impls/messaging/heartbeat.rs

68 lines
2.2 KiB
Rust

use rand::RngExt;
use std::time::Duration;
use synctv_core::models::RealtimeActor;
/// Default TTL for membership cache entries (30 seconds).
///
/// This TTL is chosen to balance between:
/// - Reducing database load (longer TTL = fewer queries)
/// - Responsiveness to membership changes (shorter TTL = faster detection of bans/removals)
///
/// With a 30-second TTL and 25-35 second heartbeat interval, we ensure:
/// - At most 1 DB query per connection per 30 seconds (vs. every heartbeat without cache)
/// - Banned/removed users are disconnected within ~30-65 seconds worst case
/// - The disconnect signal channel (Redis `PubSub`) provides immediate notification in most cases
pub const MEMBERSHIP_CACHE_TTL: Duration = Duration::from_secs(30);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct HeartbeatSchedule {
membership_cache_ttl: Duration,
base_interval: Duration,
max_jitter_secs: u64,
}
impl HeartbeatSchedule {
#[must_use]
pub const fn production() -> Self {
Self {
membership_cache_ttl: MEMBERSHIP_CACHE_TTL,
base_interval: Duration::from_secs(25),
max_jitter_secs: 10,
}
}
#[must_use]
pub const fn fixed(membership_cache_ttl: Duration, base_interval: Duration) -> Self {
Self {
membership_cache_ttl,
base_interval,
max_jitter_secs: 0,
}
}
#[must_use]
pub const fn membership_cache_ttl(self) -> Duration {
self.membership_cache_ttl
}
#[must_use]
pub fn period_with_random_jitter(self) -> Duration {
self.base_interval
+ Duration::from_secs(rand::rng().random_range(0u64..=self.max_jitter_secs))
}
#[must_use]
pub fn period_for_actor(self, actor: &RealtimeActor) -> Duration {
use std::hash::{Hash, Hasher};
let jitter_secs = if self.max_jitter_secs == 0 {
0
} else {
let mut hasher = std::collections::hash_map::DefaultHasher::new();
actor.connection_key().hash(&mut hasher);
hasher.finish() % (self.max_jitter_secs + 1)
};
self.base_interval + Duration::from_secs(jitter_secs)
}
}