Skip to main content

vibesql_server/subscription/
config.rs

1//! Subscription configuration types
2//!
3//! This module provides configuration structures for controlling subscription
4//! behavior, including limits, quotas, backpressure, and retry policies.
5
6use std::time::Duration;
7
8use serde::{Deserialize, Serialize};
9
10use super::selective::SelectiveColumnConfig;
11
12// ============================================================================
13// Subscription Configuration
14// ============================================================================
15
16/// Configuration for subscription limits, quotas, and backpressure
17///
18/// Provides configurable limits to prevent resource exhaustion attacks
19/// and ensure fair resource sharing between clients.
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct SubscriptionConfig {
22    /// Maximum subscriptions per connection (default: 100)
23    ///
24    /// Prevents a single client from creating too many subscriptions
25    /// and monopolizing server resources.
26    #[serde(default = "default_max_per_connection")]
27    pub max_per_connection: usize,
28
29    /// Maximum subscriptions globally across all connections (default: 10,000)
30    ///
31    /// Sets an upper bound on total subscriptions to ensure predictable
32    /// memory usage and performance.
33    #[serde(default = "default_max_global")]
34    pub max_global: usize,
35
36    /// Maximum result set size per subscription in rows (default: 10,000)
37    ///
38    /// Limits memory usage per subscription by capping the number of rows
39    /// that can be returned.
40    #[serde(default = "default_max_result_rows")]
41    pub max_result_rows: usize,
42
43    /// Rate limit: subscriptions per second per connection (default: 10)
44    ///
45    /// Prevents rapid subscription creation that could degrade performance.
46    #[serde(default = "default_rate_limit_per_second")]
47    pub rate_limit_per_second: u32,
48
49    /// Channel buffer size per subscription (default: 64)
50    /// Larger values reduce chance of drops but use more memory.
51    /// Smaller values detect slow consumers faster.
52    #[serde(default = "default_channel_buffer_size")]
53    pub channel_buffer_size: usize,
54
55    /// Slow consumer threshold as percentage of buffer full (default: 80)
56    /// When channel depth exceeds this percentage, warn about slow consumer
57    #[serde(default = "default_slow_consumer_threshold_percent")]
58    pub slow_consumer_threshold_percent: u8,
59
60    /// Configuration for selective column updates
61    ///
62    /// Controls when the server sends partial row updates (only changed columns)
63    /// instead of full rows, reducing bandwidth for wide tables with few changes.
64    #[serde(default)]
65    pub selective_updates: SelectiveColumnConfig,
66}
67
68fn default_max_per_connection() -> usize {
69    100
70}
71
72fn default_max_global() -> usize {
73    10_000
74}
75
76fn default_max_result_rows() -> usize {
77    10_000
78}
79
80fn default_rate_limit_per_second() -> u32 {
81    10
82}
83
84fn default_channel_buffer_size() -> usize {
85    64
86}
87
88fn default_slow_consumer_threshold_percent() -> u8 {
89    80
90}
91
92impl Default for SubscriptionConfig {
93    fn default() -> Self {
94        Self {
95            max_per_connection: default_max_per_connection(),
96            max_global: default_max_global(),
97            max_result_rows: default_max_result_rows(),
98            rate_limit_per_second: default_rate_limit_per_second(),
99            channel_buffer_size: default_channel_buffer_size(),
100            slow_consumer_threshold_percent: default_slow_consumer_threshold_percent(),
101            selective_updates: SelectiveColumnConfig::default(),
102        }
103    }
104}
105
106// ============================================================================
107// Retry Policy
108// ============================================================================
109
110/// Configuration for subscription query retry behavior
111///
112/// When a subscription query fails during re-execution, it may be automatically
113/// retried with exponential backoff if the error is classified as transient.
114#[derive(Debug, Clone, PartialEq)]
115pub struct SubscriptionRetryPolicy {
116    /// Maximum number of retry attempts after initial failure
117    ///
118    /// Default: 3
119    /// Once retries are exhausted, the subscription enters a failed state
120    /// and the error is sent to the client.
121    pub max_retries: u32,
122
123    /// Base delay for the first retry in milliseconds
124    ///
125    /// Default: 1000 (1 second)
126    /// Used as the starting point for exponential backoff calculation.
127    pub base_delay_ms: u64,
128
129    /// Maximum delay between retries in milliseconds
130    ///
131    /// Default: 30000 (30 seconds)
132    /// Exponential backoff is capped at this duration to prevent excessive delays.
133    pub max_delay_ms: u64,
134
135    /// Multiplier for exponential backoff
136    ///
137    /// Default: 2.0
138    /// Delay for retry N = base_delay * (multiplier ^ N), capped at max_delay
139    pub backoff_multiplier: f64,
140}
141
142impl Default for SubscriptionRetryPolicy {
143    fn default() -> Self {
144        Self { max_retries: 3, base_delay_ms: 1000, max_delay_ms: 30000, backoff_multiplier: 2.0 }
145    }
146}
147
148impl SubscriptionRetryPolicy {
149    /// Calculate the backoff delay for a given retry attempt
150    ///
151    /// # Arguments
152    ///
153    /// * `attempt` - The retry attempt number (0-indexed, so first retry is 0)
154    ///
155    /// # Returns
156    ///
157    /// Duration to wait before the next retry
158    pub(crate) fn calculate_backoff(&self, attempt: u32) -> Duration {
159        let backoff_ms = self.base_delay_ms as f64 * self.backoff_multiplier.powi(attempt as i32);
160
161        let capped_ms = backoff_ms.min(self.max_delay_ms as f64);
162        Duration::from_millis(capped_ms as u64)
163    }
164}