1use std::{marker::PhantomData, num::NonZeroUsize, time::Duration};
2
3use derivative::Derivative;
4use serde_with::serde_as;
5use snafu::Snafu;
6use vector_lib::{
7 configurable::configurable_component, json_size::JsonSize, stream::BatcherSettings,
8};
9
10use super::EncodedEvent;
11use crate::{event::EventFinalizers, internal_events::LargeEventDroppedError};
12
13#[derive(Debug, Snafu, PartialEq, Eq)]
17pub enum BatchError {
18 #[snafu(display("This sink does not allow setting `max_bytes`"))]
19 BytesNotAllowed,
20 #[snafu(display("`max_bytes` must be greater than zero"))]
21 InvalidMaxBytes,
22 #[snafu(display("`max_events` must be greater than zero"))]
23 InvalidMaxEvents,
24 #[snafu(display("`timeout_secs` must be greater than zero"))]
25 InvalidTimeout,
26 #[snafu(display("provided `max_bytes` exceeds the maximum limit of {}", limit))]
27 MaxBytesExceeded { limit: usize },
28 #[snafu(display("provided `max_events` exceeds the maximum limit of {}", limit))]
29 MaxEventsExceeded { limit: usize },
30}
31
32pub trait SinkBatchSettings {
33 const MAX_EVENTS: Option<usize>;
34 const MAX_BYTES: Option<usize>;
35 const TIMEOUT_SECS: f64;
36}
37
38#[derive(Clone, Copy, Debug, Default)]
40pub struct RealtimeEventBasedDefaultBatchSettings;
41
42impl SinkBatchSettings for RealtimeEventBasedDefaultBatchSettings {
43 const MAX_EVENTS: Option<usize> = Some(1000);
44 const MAX_BYTES: Option<usize> = None;
45 const TIMEOUT_SECS: f64 = 1.0;
46}
47
48#[derive(Clone, Copy, Debug, Default)]
50pub struct RealtimeSizeBasedDefaultBatchSettings;
51
52impl SinkBatchSettings for RealtimeSizeBasedDefaultBatchSettings {
53 const MAX_EVENTS: Option<usize> = None;
54 const MAX_BYTES: Option<usize> = Some(10_000_000);
55 const TIMEOUT_SECS: f64 = 1.0;
56}
57
58#[derive(Clone, Copy, Debug, Default)]
61pub struct BulkSizeBasedDefaultBatchSettings;
62
63impl SinkBatchSettings for BulkSizeBasedDefaultBatchSettings {
64 const MAX_EVENTS: Option<usize> = None;
65 const MAX_BYTES: Option<usize> = Some(10_000_000);
66 const TIMEOUT_SECS: f64 = 300.0;
67}
68
69#[derive(Clone, Copy, Debug, Default)]
75pub struct NoDefaultsBatchSettings;
76
77impl SinkBatchSettings for NoDefaultsBatchSettings {
78 const MAX_EVENTS: Option<usize> = None;
79 const MAX_BYTES: Option<usize> = None;
80 const TIMEOUT_SECS: f64 = 1.0;
81}
82
83#[derive(Clone, Copy, Debug, Default)]
84pub struct Merged;
85
86#[derive(Clone, Copy, Debug, Default)]
87pub struct Unmerged;
88
89#[serde_as]
94#[configurable_component]
95#[derive(Clone, Copy, Debug, Default)]
96pub struct BatchConfig<D: SinkBatchSettings + Clone, S = Unmerged>
97where
98 S: Clone,
99{
100 #[serde(default = "default_max_bytes::<D>")]
105 #[configurable(metadata(docs::type_unit = "bytes"))]
106 pub max_bytes: Option<usize>,
107
108 #[serde(default = "default_max_events::<D>")]
110 #[configurable(metadata(docs::type_unit = "events"))]
111 pub max_events: Option<usize>,
112
113 #[serde(default = "default_timeout::<D>")]
115 #[configurable(metadata(docs::type_unit = "seconds"))]
116 #[configurable(metadata(docs::human_name = "Timeout"))]
117 pub timeout_secs: Option<f64>,
118
119 #[serde(skip)]
120 _d: PhantomData<D>,
121 #[serde(skip)]
122 _s: PhantomData<S>,
123}
124
125const fn default_max_bytes<D: SinkBatchSettings>() -> Option<usize> {
126 D::MAX_BYTES
127}
128
129const fn default_max_events<D: SinkBatchSettings>() -> Option<usize> {
130 D::MAX_EVENTS
131}
132
133const fn default_timeout<D: SinkBatchSettings>() -> Option<f64> {
134 Some(D::TIMEOUT_SECS)
135}
136
137impl<D: SinkBatchSettings + Clone> BatchConfig<D, Unmerged> {
138 pub fn validate(self) -> Result<BatchConfig<D, Merged>, BatchError> {
139 let config = BatchConfig {
140 max_bytes: self.max_bytes.or(D::MAX_BYTES),
141 max_events: self.max_events.or(D::MAX_EVENTS),
142 timeout_secs: self.timeout_secs.or(Some(D::TIMEOUT_SECS)),
143 _d: PhantomData,
144 _s: PhantomData,
145 };
146
147 match (config.max_bytes, config.max_events, config.timeout_secs) {
148 (Some(0), _, _) => Err(BatchError::InvalidMaxBytes),
154 (_, Some(0), _) => Err(BatchError::InvalidMaxEvents),
155 (_, _, Some(timeout)) if timeout <= 0.0 => Err(BatchError::InvalidTimeout),
156
157 _ => Ok(config),
158 }
159 }
160
161 pub fn into_batch_settings<T: Batch>(self) -> Result<BatchSettings<T>, BatchError> {
162 let config = self.validate()?;
163 config.into_batch_settings()
164 }
165
166 pub fn into_batcher_settings(self) -> Result<BatcherSettings, BatchError> {
173 let config = self.validate()?;
174 config.into_batcher_settings()
175 }
176}
177
178impl<D: SinkBatchSettings + Clone> BatchConfig<D, Merged> {
179 pub const fn validate(self) -> Result<BatchConfig<D, Merged>, BatchError> {
180 Ok(self)
181 }
182
183 pub const fn disallow_max_bytes(self) -> Result<Self, BatchError> {
184 match self.max_bytes {
187 Some(_) => Err(BatchError::BytesNotAllowed),
188 None => Ok(self),
189 }
190 }
191
192 pub const fn limit_max_bytes(self, limit: usize) -> Result<Self, BatchError> {
193 match self.max_bytes {
194 Some(n) if n > limit => Err(BatchError::MaxBytesExceeded { limit }),
195 _ => Ok(self),
196 }
197 }
198
199 pub const fn limit_max_events(self, limit: usize) -> Result<Self, BatchError> {
200 match self.max_events {
201 Some(n) if n > limit => Err(BatchError::MaxEventsExceeded { limit }),
202 _ => Ok(self),
203 }
204 }
205
206 pub fn into_batch_settings<T: Batch>(self) -> Result<BatchSettings<T>, BatchError> {
207 let adjusted = T::get_settings_defaults(self)?;
208
209 let timeout_secs = adjusted.timeout_secs.ok_or(BatchError::InvalidTimeout)?;
212
213 Ok(BatchSettings {
214 size: BatchSize {
215 bytes: adjusted.max_bytes.unwrap_or(usize::MAX),
216 events: adjusted.max_events.unwrap_or(usize::MAX),
217 _type_marker: PhantomData,
218 },
219 timeout: Duration::from_secs_f64(timeout_secs),
220 })
221 }
222
223 pub fn into_batcher_settings(self) -> Result<BatcherSettings, BatchError> {
230 let max_bytes = self
231 .max_bytes
232 .and_then(NonZeroUsize::new)
233 .or_else(|| NonZeroUsize::new(usize::MAX))
234 .expect("`max_bytes` should already be validated");
235
236 let max_events = self
237 .max_events
238 .and_then(NonZeroUsize::new)
239 .or_else(|| NonZeroUsize::new(usize::MAX))
240 .expect("`max_bytes` should already be validated");
241
242 let timeout_secs = self.timeout_secs.ok_or(BatchError::InvalidTimeout)?;
245
246 Ok(BatcherSettings::new(
247 Duration::from_secs_f64(timeout_secs),
248 max_bytes,
249 max_events,
250 ))
251 }
252}
253
254impl<D1, D2> From<BatchConfig<D1, Merged>> for BatchConfig<D2, Unmerged>
257where
258 D1: SinkBatchSettings + Clone,
259 D2: SinkBatchSettings + Clone,
260{
261 fn from(config: BatchConfig<D1, Merged>) -> Self {
262 BatchConfig {
263 max_bytes: config.max_bytes,
264 max_events: config.max_events,
265 timeout_secs: config.timeout_secs,
266 _d: PhantomData,
267 _s: PhantomData,
268 }
269 }
270}
271
272#[derive(Debug, Derivative)]
273#[derivative(Clone(bound = ""))]
274#[derivative(Copy(bound = ""))]
275pub struct BatchSize<B> {
276 pub bytes: usize,
277 pub events: usize,
278 _type_marker: PhantomData<B>,
282}
283
284impl<B> BatchSize<B> {
285 pub const fn const_default() -> Self {
286 BatchSize {
287 bytes: usize::MAX,
288 events: usize::MAX,
289 _type_marker: PhantomData,
290 }
291 }
292}
293
294impl<B> Default for BatchSize<B> {
295 fn default() -> Self {
296 BatchSize::const_default()
297 }
298}
299
300#[derive(Debug, Derivative)]
301#[derivative(Clone(bound = ""))]
302#[derivative(Copy(bound = ""))]
303pub struct BatchSettings<B> {
304 pub size: BatchSize<B>,
305 pub timeout: Duration,
306}
307
308impl<B> Default for BatchSettings<B> {
309 fn default() -> Self {
310 BatchSettings {
311 size: BatchSize {
312 bytes: 10_000_000,
313 events: usize::MAX,
314 _type_marker: PhantomData,
315 },
316 timeout: Duration::from_secs(1),
317 }
318 }
319}
320
321pub(super) fn err_event_too_large<T>(length: usize, max_length: usize) -> PushResult<T> {
322 emit!(LargeEventDroppedError { length, max_length });
323 PushResult::Ok(false)
324}
325
326#[must_use]
329#[derive(Debug, Eq, PartialEq)]
330pub enum PushResult<T> {
331 Ok(bool),
333 Overflow(T),
337}
338
339pub trait Batch: Sized {
340 type Input;
341 type Output;
342
343 fn get_settings_defaults<D: SinkBatchSettings + Clone>(
348 config: BatchConfig<D, Merged>,
349 ) -> Result<BatchConfig<D, Merged>, BatchError> {
350 Ok(config)
351 }
352
353 fn push(&mut self, item: Self::Input) -> PushResult<Self::Input>;
354 fn is_empty(&self) -> bool;
355 fn fresh(&self) -> Self;
356 fn finish(self) -> Self::Output;
357 fn num_items(&self) -> usize;
358}
359
360#[derive(Debug)]
361pub struct EncodedBatch<I> {
362 pub items: I,
363 pub finalizers: EventFinalizers,
364 pub count: usize,
365 pub byte_size: usize,
366 pub json_byte_size: JsonSize,
367}
368
369#[derive(Clone, Debug)]
371pub struct FinalizersBatch<B> {
372 inner: B,
373 finalizers: EventFinalizers,
374 count: usize,
378 byte_size: usize,
379 json_byte_size: JsonSize,
380}
381
382impl<B: Batch> From<B> for FinalizersBatch<B> {
383 fn from(inner: B) -> Self {
384 Self {
385 inner,
386 finalizers: Default::default(),
387 count: 0,
388 byte_size: 0,
389 json_byte_size: JsonSize::zero(),
390 }
391 }
392}
393
394impl<B: Batch> Batch for FinalizersBatch<B> {
395 type Input = EncodedEvent<B::Input>;
396 type Output = EncodedBatch<B::Output>;
397
398 fn get_settings_defaults<D: SinkBatchSettings + Clone>(
399 config: BatchConfig<D, Merged>,
400 ) -> Result<BatchConfig<D, Merged>, BatchError> {
401 B::get_settings_defaults(config)
402 }
403
404 fn push(&mut self, item: Self::Input) -> PushResult<Self::Input> {
405 let EncodedEvent {
406 item,
407 finalizers,
408 byte_size,
409 json_byte_size,
410 } = item;
411 match self.inner.push(item) {
412 PushResult::Ok(full) => {
413 self.finalizers.merge(finalizers);
414 self.count += 1;
415 self.byte_size += byte_size;
416 self.json_byte_size += json_byte_size;
417 PushResult::Ok(full)
418 }
419 PushResult::Overflow(item) => PushResult::Overflow(EncodedEvent {
420 item,
421 finalizers,
422 byte_size,
423 json_byte_size,
424 }),
425 }
426 }
427
428 fn is_empty(&self) -> bool {
429 self.inner.is_empty()
430 }
431
432 fn fresh(&self) -> Self {
433 Self {
434 inner: self.inner.fresh(),
435 finalizers: Default::default(),
436 count: 0,
437 byte_size: 0,
438 json_byte_size: JsonSize::zero(),
439 }
440 }
441
442 fn finish(self) -> Self::Output {
443 EncodedBatch {
444 items: self.inner.finish(),
445 finalizers: self.finalizers,
446 count: self.count,
447 byte_size: self.byte_size,
448 json_byte_size: self.json_byte_size,
449 }
450 }
451
452 fn num_items(&self) -> usize {
453 self.inner.num_items()
454 }
455}
456
457#[derive(Clone, Debug)]
458pub struct StatefulBatch<B> {
459 inner: B,
460 was_full: bool,
461}
462
463impl<B: Batch> From<B> for StatefulBatch<B> {
464 fn from(inner: B) -> Self {
465 Self {
466 inner,
467 was_full: false,
468 }
469 }
470}
471
472impl<B> StatefulBatch<B> {
473 pub const fn was_full(&self) -> bool {
474 self.was_full
475 }
476
477 #[allow(clippy::missing_const_for_fn)] pub fn into_inner(self) -> B {
479 self.inner
480 }
481}
482
483impl<B: Batch> Batch for StatefulBatch<B> {
484 type Input = B::Input;
485 type Output = B::Output;
486
487 fn get_settings_defaults<D: SinkBatchSettings + Clone>(
488 config: BatchConfig<D, Merged>,
489 ) -> Result<BatchConfig<D, Merged>, BatchError> {
490 B::get_settings_defaults(config)
491 }
492
493 fn push(&mut self, item: Self::Input) -> PushResult<Self::Input> {
494 if self.was_full {
495 PushResult::Overflow(item)
496 } else {
497 let result = self.inner.push(item);
498 self.was_full =
499 matches!(result, PushResult::Overflow(_)) || matches!(result, PushResult::Ok(true));
500 result
501 }
502 }
503
504 fn is_empty(&self) -> bool {
505 !self.was_full && self.inner.is_empty()
506 }
507
508 fn fresh(&self) -> Self {
509 Self {
510 inner: self.inner.fresh(),
511 was_full: false,
512 }
513 }
514
515 fn finish(self) -> Self::Output {
516 self.inner.finish()
517 }
518
519 fn num_items(&self) -> usize {
520 self.inner.num_items()
521 }
522}