Skip to main content

forge_core/config/
realtime_config.rs

1//! Real-time subscription engine and SSE transport configuration.
2
3use std::time::Duration;
4
5use serde::{Deserialize, Serialize};
6
7use super::types::DurationStr;
8
9/// Configuration for the real-time subscription engine and SSE transport.
10///
11/// All fields have production-safe defaults; only set these to tune behaviour.
12#[derive(Debug, Clone, Serialize, Deserialize)]
13#[non_exhaustive]
14pub struct RealtimeConfig {
15    /// Maximum concurrent query re-executions during an invalidation flush.
16    #[serde(default = "default_max_concurrent_reexecutions")]
17    pub max_concurrent_reexecutions: usize,
18
19    /// Periodic resync interval. Re-evaluates every active query group to recover
20    /// from dropped NOTIFY payloads. "0s" disables the sweep (e.g. "60s", "5m").
21    #[serde(default = "default_resync_interval")]
22    pub resync_interval: DurationStr,
23
24    /// Broadcast channel buffer for raw change notifications from PG.
25    #[serde(
26        default = "default_postgres_change_buffer_size",
27        alias = "listener_channel_buffer"
28    )]
29    pub postgres_change_buffer_size: usize,
30
31    /// Debounce quiet window duration. Changes arriving within this window are
32    /// coalesced into a single flush (e.g. "50ms", "100ms").
33    #[serde(default = "default_debounce_quiet_window", alias = "debounce_quiet")]
34    pub debounce_quiet_window: DurationStr,
35
36    /// Absolute maximum debounce wait before forcing a flush (e.g. "200ms", "1s").
37    #[serde(default = "default_debounce_max_wait", alias = "debounce_max")]
38    pub debounce_max_wait: DurationStr,
39
40    /// Maximum concurrent SSE sessions across all clients.
41    #[serde(default = "default_sse_max_sessions")]
42    pub sse_max_sessions: usize,
43
44    /// Maximum subscriptions per SSE session.
45    #[serde(default = "default_subscription_max_per_session")]
46    pub subscription_max_per_session: usize,
47
48    /// Number of DashMap shards for the subscription manager. Higher values
49    /// reduce lock contention at the cost of memory.
50    #[serde(default = "default_shard_count")]
51    pub shard_count: usize,
52
53    /// Maximum concurrent SSE sessions per authenticated user.
54    ///
55    /// Interacts with `subscription_max_per_session` and `max_subscriptions_per_user`:
56    /// the effective per-user subscription cap is
57    /// `min(max_subscriptions_per_user, max_sessions_per_user * subscription_max_per_session)`.
58    /// With defaults (10 sessions, 100 per session, 500 global), the cap is 500.
59    #[serde(default = "default_max_sessions_per_user")]
60    pub max_sessions_per_user: usize,
61
62    /// Maximum concurrent SSE sessions per source IP.
63    #[serde(default = "default_max_sessions_per_ip")]
64    pub max_sessions_per_ip: usize,
65
66    /// Cap on a user's total subscriptions across every active session.
67    ///
68    /// Global per-user cap across all sessions. With `max_sessions_per_user=10`
69    /// and `subscription_max_per_session=100`, the effective per-user cap is
70    /// `min(500, 10*100) = 500`. Lowering this below
71    /// `max_sessions_per_user * subscription_max_per_session` enforces a tighter
72    /// global ceiling regardless of how subscriptions are distributed.
73    #[serde(default = "default_max_subscriptions_per_user")]
74    pub max_subscriptions_per_user: usize,
75
76    /// Per-query cached-result memory ceiling (bytes). Cached results
77    /// exceeding this size are dropped after re-execution.
78    #[serde(default = "default_max_cached_result_bytes")]
79    pub max_cached_result_bytes: usize,
80}
81
82fn default_max_concurrent_reexecutions() -> usize {
83    64
84}
85fn default_resync_interval() -> DurationStr {
86    DurationStr::new(Duration::from_secs(60))
87}
88fn default_postgres_change_buffer_size() -> usize {
89    1024
90}
91fn default_debounce_quiet_window() -> DurationStr {
92    DurationStr::new(Duration::from_millis(50))
93}
94fn default_debounce_max_wait() -> DurationStr {
95    DurationStr::new(Duration::from_millis(200))
96}
97fn default_sse_max_sessions() -> usize {
98    10_000
99}
100fn default_subscription_max_per_session() -> usize {
101    100
102}
103fn default_shard_count() -> usize {
104    64
105}
106fn default_max_sessions_per_user() -> usize {
107    10
108}
109fn default_max_sessions_per_ip() -> usize {
110    32
111}
112fn default_max_subscriptions_per_user() -> usize {
113    500
114}
115fn default_max_cached_result_bytes() -> usize {
116    10_485_760
117}
118
119impl Default for RealtimeConfig {
120    fn default() -> Self {
121        Self {
122            max_concurrent_reexecutions: default_max_concurrent_reexecutions(),
123            resync_interval: default_resync_interval(),
124            postgres_change_buffer_size: default_postgres_change_buffer_size(),
125            debounce_quiet_window: default_debounce_quiet_window(),
126            debounce_max_wait: default_debounce_max_wait(),
127            sse_max_sessions: default_sse_max_sessions(),
128            subscription_max_per_session: default_subscription_max_per_session(),
129            shard_count: default_shard_count(),
130            max_sessions_per_user: default_max_sessions_per_user(),
131            max_sessions_per_ip: default_max_sessions_per_ip(),
132            max_subscriptions_per_user: default_max_subscriptions_per_user(),
133            max_cached_result_bytes: default_max_cached_result_bytes(),
134        }
135    }
136}