Skip to main content

kvbm_config/
events.rs

1// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Event publishing configuration for KV cache coordination.
5//!
6//! This module defines the configuration for the event publishing pipeline
7//! that broadcasts block registration/removal events to distributed consumers
8//! (e.g., KvbmHub for radix tree maintenance).
9
10use serde::{Deserialize, Serialize};
11use validator::Validate;
12
13/// Configuration for event publishing.
14///
15/// Events are broadcast when blocks are registered or removed from the cache.
16/// The pipeline batches events for efficient wire transmission.
17#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
18pub struct EventsConfig {
19    /// Whether event publishing is enabled.
20    ///
21    /// When disabled, no events are emitted and no publisher is started.
22    /// Default: false
23    #[serde(default)]
24    pub enabled: bool,
25
26    /// Batching configuration for the event pipeline.
27    #[serde(default)]
28    #[validate(nested)]
29    pub batching: BatchingConfig,
30
31    /// Broadcast channel capacity for the EventsManager.
32    ///
33    /// This determines how many events can be buffered before slow
34    /// subscribers start lagging. Default: 1024
35    #[serde(default = "default_channel_capacity")]
36    #[validate(range(min = 16, max = 65536))]
37    pub channel_capacity: usize,
38
39    /// Subject/topic pattern for publishing events.
40    ///
41    /// This is the NATS/messaging subject where events are published.
42    /// Default: "kvbm.events"
43    #[serde(default = "default_subject")]
44    pub subject: String,
45
46    /// Event emission policy.
47    ///
48    /// Determines which blocks trigger events:
49    /// - `power_of_two`: Only emit for blocks at power-of-2 positions (default)
50    /// - `all`: Emit for all blocks (testing/debugging)
51    #[serde(default)]
52    pub policy: EventPolicyConfig,
53}
54
55impl Default for EventsConfig {
56    fn default() -> Self {
57        Self {
58            enabled: false,
59            batching: BatchingConfig::default(),
60            channel_capacity: default_channel_capacity(),
61            subject: default_subject(),
62            policy: EventPolicyConfig::default(),
63        }
64    }
65}
66
67fn default_channel_capacity() -> usize {
68    1024
69}
70
71fn default_subject() -> String {
72    "kvbm.events".to_string()
73}
74
75/// Batching configuration for the event pipeline.
76///
77/// Events are batched before publishing to reduce wire traffic.
78/// Batches are flushed when:
79/// - The window duration expires
80/// - The max batch size is reached
81/// - The event type switches (Create -> Remove or vice versa)
82#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
83pub struct BatchingConfig {
84    /// Maximum time to wait before flushing a batch (in milliseconds).
85    ///
86    /// Default: 10ms
87    #[serde(default = "default_window_duration_ms")]
88    #[validate(range(min = 1, max = 10000))]
89    pub window_duration_ms: u64,
90
91    /// Maximum number of events in a batch before flushing.
92    ///
93    /// Default: 1024
94    #[serde(default = "default_max_batch_size")]
95    #[validate(range(min = 1, max = 65536))]
96    pub max_batch_size: usize,
97}
98
99impl Default for BatchingConfig {
100    fn default() -> Self {
101        Self {
102            window_duration_ms: default_window_duration_ms(),
103            max_batch_size: default_max_batch_size(),
104        }
105    }
106}
107
108fn default_window_duration_ms() -> u64 {
109    10
110}
111
112fn default_max_batch_size() -> usize {
113    1024
114}
115
116/// Event emission policy configuration.
117#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
118#[serde(rename_all = "snake_case")]
119pub enum EventPolicyConfig {
120    /// Emit events only for blocks at power-of-2 positions (default).
121    ///
122    /// This creates sparse sampling at positions 16, 32, 64, ..., 65536
123    /// for efficient radix tree construction without tracking every block.
124    #[default]
125    PowerOfTwo,
126
127    /// Emit events for all blocks.
128    ///
129    /// Useful for testing or when complete block tracking is needed.
130    All,
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    #[test]
138    fn test_default_config() {
139        let config = EventsConfig::default();
140        assert!(!config.enabled);
141        assert_eq!(config.batching.window_duration_ms, 10);
142        assert_eq!(config.batching.max_batch_size, 1024);
143        assert_eq!(config.channel_capacity, 1024);
144        assert_eq!(config.subject, "kvbm.events");
145        assert_eq!(config.policy, EventPolicyConfig::PowerOfTwo);
146    }
147
148    #[test]
149    fn test_serde_roundtrip() {
150        let json = r#"{
151            "enabled": true,
152            "batching": {
153                "window_duration_ms": 50,
154                "max_batch_size": 512
155            },
156            "channel_capacity": 2048,
157            "subject": "my.events",
158            "policy": "all"
159        }"#;
160
161        let config: EventsConfig = serde_json::from_str(json).unwrap();
162        assert!(config.enabled);
163        assert_eq!(config.batching.window_duration_ms, 50);
164        assert_eq!(config.batching.max_batch_size, 512);
165        assert_eq!(config.channel_capacity, 2048);
166        assert_eq!(config.subject, "my.events");
167        assert_eq!(config.policy, EventPolicyConfig::All);
168
169        // Roundtrip
170        let serialized = serde_json::to_string(&config).unwrap();
171        let deserialized: EventsConfig = serde_json::from_str(&serialized).unwrap();
172        assert_eq!(deserialized.enabled, config.enabled);
173        assert_eq!(deserialized.policy, config.policy);
174    }
175
176    #[test]
177    fn test_empty_json_uses_defaults() {
178        let json = r#"{}"#;
179        let config: EventsConfig = serde_json::from_str(json).unwrap();
180        assert!(!config.enabled);
181        assert_eq!(config.batching.window_duration_ms, 10);
182    }
183
184    #[test]
185    fn test_partial_config() {
186        // Only override enabled, everything else uses defaults
187        let json = r#"{"enabled": true}"#;
188        let config: EventsConfig = serde_json::from_str(json).unwrap();
189        assert!(config.enabled);
190        assert_eq!(config.batching.window_duration_ms, 10);
191        assert_eq!(config.channel_capacity, 1024);
192    }
193
194    #[test]
195    fn test_validation() {
196        let config = EventsConfig {
197            enabled: true,
198            batching: BatchingConfig {
199                window_duration_ms: 10,
200                max_batch_size: 1024,
201            },
202            channel_capacity: 1024,
203            subject: "test".to_string(),
204            policy: EventPolicyConfig::PowerOfTwo,
205        };
206        assert!(config.validate().is_ok());
207    }
208}