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}