vector/enrichment_tables/memory/
config.rs1use std::{num::NonZeroU64, sync::Arc};
2
3use async_trait::async_trait;
4use futures::{FutureExt, future};
5use tokio::sync::Mutex;
6use vector_lib::{
7 config::{AcknowledgementsConfig, DataType, Input, LogNamespace},
8 configurable::configurable_component,
9 enrichment::Table,
10 id::ComponentKey,
11 lookup::lookup_v2::OptionalValuePath,
12 schema::{self},
13 sink::VectorSink,
14};
15use vrl::{path::OwnedTargetPath, value::Kind};
16
17use super::{Memory, internal_events::InternalMetricsConfig, source::EXPIRED_ROUTE};
18use crate::{
19 config::{
20 EnrichmentTableConfig, SinkConfig, SinkContext, SourceConfig, SourceContext, SourceOutput,
21 },
22 enrichment_tables::memory::{
23 bloom_table::{BloomMemoryConfig, BloomMemoryTable},
24 cuckoo_table::{CuckooMemoryConfig, CuckooMemoryTable},
25 },
26 sinks::Healthcheck,
27 sources::Source,
28};
29
30#[configurable_component(enrichment_table("memory"))]
32#[derive(Clone)]
33#[serde(deny_unknown_fields)]
34pub struct MemoryConfig {
35 #[serde(default = "default_ttl")]
39 pub ttl: u64,
40 #[serde(default = "default_scan_interval")]
44 pub scan_interval: NonZeroU64,
45 #[serde(skip_serializing_if = "vector_lib::serde::is_default")]
56 pub flush_interval: Option<NonZeroU64>,
57 #[serde(skip_serializing_if = "vector_lib::serde::is_default")]
62 pub max_byte_size: Option<u64>,
63 #[configurable(metadata(docs::hidden))]
65 #[serde(default)]
66 pub log_namespace: Option<bool>,
67 #[configurable(derived)]
69 #[serde(default)]
70 pub internal_metrics: InternalMetricsConfig,
71 #[configurable(derived)]
73 #[serde(skip_serializing_if = "vector_lib::serde::is_default")]
74 pub source_config: Option<MemorySourceConfig>,
75 #[configurable(derived)]
77 #[serde(default)]
78 pub ttl_field: OptionalValuePath,
79 #[configurable(derived)]
81 #[serde(default)]
82 pub reload_behavior: ReloadBehavior,
83
84 #[configurable(derived)]
87 #[serde(default)]
88 pub filter: Option<TableFilter>,
89
90 #[serde(skip)]
91 memory: Arc<Mutex<Option<Box<Memory>>>>,
92 #[serde(skip)]
93 cuckoo: Arc<Mutex<Option<Box<CuckooMemoryTable>>>>,
94 #[serde(skip)]
95 bloom: Arc<Mutex<Option<Box<BloomMemoryTable>>>>,
96}
97
98#[configurable_component]
100#[derive(Clone, Default)]
101#[serde(rename_all = "kebab-case")]
102pub enum ReloadBehavior {
103 #[default]
105 ClearState,
106 PreserveState,
108}
109
110#[configurable_component]
112#[derive(Clone, Debug, PartialEq, Eq)]
113#[serde(deny_unknown_fields)]
114pub struct MemorySourceConfig {
115 #[serde(skip_serializing_if = "vector_lib::serde::is_default")]
117 pub export_interval: Option<NonZeroU64>,
118 #[serde(skip_serializing_if = "vector_lib::serde::is_default")]
123 pub export_batch_size: Option<u64>,
124 #[serde(default = "crate::serde::default_false")]
129 pub remove_after_export: bool,
130 #[serde(default = "crate::serde::default_false")]
133 pub export_expired_items: bool,
134 pub source_key: String,
137}
138
139#[configurable_component]
141#[derive(Clone, Debug, PartialEq, Eq)]
142#[serde(deny_unknown_fields, rename_all = "snake_case", tag = "type")]
143#[configurable(metadata(docs::enum_tag_description = "The probabilistic filter to use."))]
144pub enum TableFilter {
145 Cuckoo(CuckooMemoryConfig),
149 Bloom(BloomMemoryConfig),
153}
154
155impl PartialEq for MemoryConfig {
156 fn eq(&self, other: &Self) -> bool {
157 self.ttl == other.ttl
158 && self.scan_interval == other.scan_interval
159 && self.flush_interval == other.flush_interval
160 }
161}
162impl Eq for MemoryConfig {}
163
164impl Default for MemoryConfig {
165 fn default() -> Self {
166 Self {
167 ttl: default_ttl(),
168 scan_interval: default_scan_interval(),
169 flush_interval: None,
170 memory: Arc::new(Mutex::new(None)),
171 cuckoo: Arc::new(Mutex::new(None)),
172 bloom: Arc::new(Mutex::new(None)),
173 max_byte_size: None,
174 log_namespace: None,
175 source_config: None,
176 internal_metrics: InternalMetricsConfig::default(),
177 ttl_field: OptionalValuePath::none(),
178 reload_behavior: Default::default(),
179 filter: None,
180 }
181 }
182}
183
184const fn default_ttl() -> u64 {
185 600
186}
187
188const fn default_scan_interval() -> NonZeroU64 {
189 unsafe { NonZeroU64::new_unchecked(30) }
190}
191
192impl MemoryConfig {
193 pub(super) async fn get_or_build_memory(
194 &self,
195 prev_state: Option<Box<dyn std::any::Any + Send + Sync>>,
196 ) -> Memory {
197 let mut boxed_memory = self.memory.lock().await;
198 *boxed_memory
199 .get_or_insert_with(|| {
200 if let Some(prev) = prev_state {
201 Box::new(Memory::from_previous_state(self.clone(), prev))
202 } else {
203 Box::new(Memory::new(self.clone()))
204 }
205 })
206 .clone()
207 }
208
209 pub(super) async fn get_or_build_cuckoo(
210 &self,
211 prev_state: Option<Box<dyn std::any::Any + Send + Sync>>,
212 ) -> crate::Result<CuckooMemoryTable> {
213 let Some(TableFilter::Cuckoo(cuckoo)) = &self.filter else {
214 panic!("No cuckoo");
215 };
216 let mut boxed_cuckoo = self.cuckoo.lock().await;
217 if let Some(boxed_cuckoo) = boxed_cuckoo.as_ref() {
218 Ok(*boxed_cuckoo.clone())
219 } else {
220 Ok(*boxed_cuckoo
221 .insert(if let Some(prev) = prev_state {
222 Box::new(CuckooMemoryTable::from_previous_state(
223 self.clone(),
224 cuckoo.clone(),
225 prev,
226 )?)
227 } else {
228 Box::new(CuckooMemoryTable::new(self.clone(), cuckoo.clone())?)
229 })
230 .clone())
231 }
232 }
233
234 pub(super) async fn get_or_build_bloom(
235 &self,
236 prev_state: Option<Box<dyn std::any::Any + Send + Sync>>,
237 ) -> crate::Result<BloomMemoryTable> {
238 let mut boxed_bloom = self.bloom.lock().await;
239 let Some(TableFilter::Bloom(bloom)) = &self.filter else {
240 panic!("No bloom");
241 };
242 if let Some(boxed_bloom) = boxed_bloom.as_ref() {
243 Ok(*boxed_bloom.clone())
244 } else {
245 Ok(*boxed_bloom
246 .insert(if let Some(prev) = prev_state {
247 Box::new(BloomMemoryTable::from_previous_state(
248 self.clone(),
249 bloom.clone(),
250 prev,
251 )?)
252 } else {
253 Box::new(BloomMemoryTable::new(self.clone(), bloom.clone())?)
254 })
255 .clone())
256 }
257 }
258}
259
260impl EnrichmentTableConfig for MemoryConfig {
261 async fn build(
262 &self,
263 _globals: &crate::config::GlobalOptions,
264 prev_state: Option<Box<dyn std::any::Any + Send + Sync>>,
265 ) -> crate::Result<Box<dyn Table + Send + Sync>> {
266 match &self.filter {
267 Some(TableFilter::Cuckoo(_)) => {
268 if self.source_config.is_some() {
269 return Err("Source functionality is not supported for cuckoo filter".into());
270 }
271 Ok(Box::new(self.get_or_build_cuckoo(prev_state).await?))
272 }
273 Some(TableFilter::Bloom(_)) => {
274 if self.source_config.is_some() {
275 return Err("Source functionality is not supported for bloom filter".into());
276 }
277 if self.ttl_field.path.is_some() || self.ttl != default_ttl() {
278 return Err("TTL functionality is not supported for bloom filter.".into());
279 }
280 if self.scan_interval != default_scan_interval() {
281 return Err("`scan_interval` has no effect for bloom filter.".into());
282 }
283 Ok(Box::new(self.get_or_build_bloom(prev_state).await?))
284 }
285 None => Ok(Box::new(self.get_or_build_memory(prev_state).await)),
286 }
287 }
288
289 fn wants_previous_state(&self) -> bool {
290 matches!(self.reload_behavior, ReloadBehavior::PreserveState)
291 }
292
293 fn sink_config(
294 &self,
295 default_key: &ComponentKey,
296 ) -> Option<(ComponentKey, Box<dyn SinkConfig>)> {
297 Some((default_key.clone(), Box::new(self.clone())))
298 }
299
300 fn source_config(
301 &self,
302 _default_key: &ComponentKey,
303 ) -> Option<(ComponentKey, Box<dyn SourceConfig>)> {
304 let Some(source_config) = &self.source_config else {
305 return None;
306 };
307 if self.filter.is_some() {
309 return None;
310 }
311 Some((
312 source_config.source_key.clone().into(),
313 Box::new(self.clone()),
314 ))
315 }
316}
317
318#[async_trait]
319#[typetag::serde(name = "memory_enrichment_table")]
320impl SinkConfig for MemoryConfig {
321 async fn build(&self, _cx: SinkContext) -> crate::Result<(VectorSink, Healthcheck)> {
322 let sink = match &self.filter {
323 Some(TableFilter::Cuckoo(_)) => {
324 VectorSink::from_event_streamsink(self.get_or_build_cuckoo(None).await?)
325 }
326 Some(TableFilter::Bloom(_)) => {
327 VectorSink::from_event_streamsink(self.get_or_build_bloom(None).await?)
328 }
329 None => VectorSink::from_event_streamsink(self.get_or_build_memory(None).await),
330 };
331
332 Ok((sink, future::ok(()).boxed()))
333 }
334
335 fn input(&self) -> Input {
336 Input::log()
337 }
338
339 fn acknowledgements(&self) -> &AcknowledgementsConfig {
340 &AcknowledgementsConfig::DEFAULT
341 }
342}
343
344#[async_trait]
345#[typetag::serde(name = "memory_enrichment_table")]
346impl SourceConfig for MemoryConfig {
347 async fn build(&self, cx: SourceContext) -> crate::Result<Source> {
348 let memory = self.get_or_build_memory(None).await;
349
350 let log_namespace = cx.log_namespace(self.log_namespace);
351
352 Ok(Box::pin(
353 memory.as_source(cx.shutdown, cx.out, log_namespace).run(),
354 ))
355 }
356
357 fn outputs(&self, global_log_namespace: LogNamespace) -> Vec<SourceOutput> {
358 let log_namespace = global_log_namespace.merge(self.log_namespace);
359 let schema_definition = match log_namespace {
360 LogNamespace::Legacy => schema::Definition::default_legacy_namespace(),
361 LogNamespace::Vector => {
362 schema::Definition::new_with_default_metadata(Kind::any_object(), [log_namespace])
363 .with_meaning(OwnedTargetPath::event_root(), "message")
364 }
365 }
366 .with_standard_vector_source_metadata();
367
368 if self
369 .source_config
370 .as_ref()
371 .map(|c| c.export_expired_items)
372 .unwrap_or_default()
373 {
374 vec![
375 SourceOutput::new_maybe_logs(DataType::Log, schema_definition.clone()),
376 SourceOutput::new_maybe_logs(DataType::Log, schema_definition)
377 .with_port(EXPIRED_ROUTE),
378 ]
379 } else {
380 vec![SourceOutput::new_maybe_logs(
381 DataType::Log,
382 schema_definition,
383 )]
384 }
385 }
386
387 fn can_acknowledge(&self) -> bool {
388 false
389 }
390}
391
392impl std::fmt::Debug for MemoryConfig {
393 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
394 f.debug_struct("MemoryConfig")
395 .field("ttl", &self.ttl)
396 .field("scan_interval", &self.scan_interval)
397 .field("flush_interval", &self.flush_interval)
398 .field("max_byte_size", &self.max_byte_size)
399 .finish()
400 }
401}
402
403impl_generate_config_from_default!(MemoryConfig);