Skip to main content

vector_buffers/
lib.rs

1//! The Vector Core buffer
2//!
3//! This library implements a channel like functionality, one variant which is
4//! solely in-memory and the other that is on-disk. Both variants are bounded.
5
6#![deny(warnings)]
7#![deny(clippy::all)]
8#![deny(clippy::pedantic)]
9#![allow(clippy::module_name_repetitions)]
10#![allow(clippy::type_complexity)] // long-types happen, especially in async code
11#![allow(clippy::must_use_candidate)]
12#![allow(async_fn_in_trait)]
13
14#[macro_use]
15extern crate tracing;
16
17mod buffer_usage_data;
18
19pub mod config;
20pub use config::{BufferConfig, BufferType, MemoryBufferSize};
21use encoding::Encodable;
22pub(crate) use vector_common::Result;
23use vector_config::configurable_component;
24
25pub mod encoding;
26
27mod internal_events;
28
29#[cfg(test)]
30pub mod test;
31pub mod topology;
32
33pub(crate) mod variants;
34
35/// `disk_v2`'s write-buffer size, re-exported under `test` so the harness can
36/// size payloads against the real value instead of hardcoding it.
37#[cfg(feature = "test")]
38pub use variants::disk_v2::common::DEFAULT_WRITE_BUFFER_SIZE as WRITE_BUFFER_SIZE_V2;
39
40use std::fmt::Debug;
41
42#[cfg(test)]
43use quickcheck::{Arbitrary, Gen};
44use vector_common::{
45    byte_size_of::ByteSizeOf,
46    finalization::{AddBatchNotifier, Finalizable, GroupedFinalizable},
47};
48
49/// Event handling behavior when a buffer is full.
50#[configurable_component]
51#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
52#[serde(rename_all = "snake_case")]
53pub enum WhenFull {
54    /// Wait for free space in the buffer.
55    ///
56    /// This applies backpressure up the topology, signalling that sources should slow down
57    /// the acceptance/consumption of events. This means that while no data is lost, data will pile
58    /// up at the edge.
59    #[default]
60    Block,
61
62    /// Drops the event instead of waiting for free space in buffer.
63    ///
64    /// The event will be intentionally dropped. This mode is typically used when performance is the
65    /// highest priority, and it is preferable to temporarily lose events rather than cause a
66    /// slowdown in the acceptance/consumption of events.
67    DropNewest,
68
69    /// Overflows to the next stage in the buffer topology.
70    ///
71    /// If the current buffer stage is full, attempt to send this event to the next buffer stage.
72    /// That stage may also be configured overflow, and so on, but ultimately the last stage in a
73    /// buffer topology must use one of the other handling behaviors. This means that next stage may
74    /// potentially be able to buffer the event, but it may also block or drop the event.
75    ///
76    /// This mode can only be used when two or more buffer stages are configured.
77    #[configurable(metadata(docs::hidden))]
78    Overflow,
79}
80
81#[cfg(test)]
82impl Arbitrary for WhenFull {
83    fn arbitrary(g: &mut Gen) -> Self {
84        // TODO: We explicitly avoid generating "overflow" as a possible value because nothing yet
85        // supports handling it, and will be defaulted to using "block" if they encounter
86        // "overflow".  Thus, there's no reason to emit it here... yet.
87        if bool::arbitrary(g) {
88            WhenFull::Block
89        } else {
90            WhenFull::DropNewest
91        }
92    }
93}
94
95/// An item that can be buffered in memory.
96///
97/// This supertrait serves as the base trait for any item that can be pushed into a memory buffer.
98/// It is a relaxed version of `Bufferable` that allows for items that are not `Encodable` (e.g., `Instant`),
99/// which is an unnecessary constraint for memory buffers.
100pub trait InMemoryBufferable:
101    AddBatchNotifier
102    + Finalizable
103    + ByteSizeOf
104    + EventCount
105    + Debug
106    + Send
107    + Sync
108    + Unpin
109    + Sized
110    + 'static
111{
112}
113
114// Blanket implementation for anything that is already in-memory bufferable.
115impl<T> InMemoryBufferable for T where
116    T: AddBatchNotifier
117        + Finalizable
118        + ByteSizeOf
119        + EventCount
120        + Debug
121        + Send
122        + Sync
123        + Unpin
124        + Sized
125        + 'static
126{
127}
128
129/// An item that can be buffered.
130///
131/// This supertrait serves as the base trait for any item that can be pushed into a buffer.
132pub trait Bufferable: InMemoryBufferable + Encodable + GroupedFinalizable {
133    /// Drops any sub-items that cannot be persisted by the calling backend (e.g. due to
134    /// format-imposed nesting depth limits), reporting them as dropped via the appropriate
135    /// telemetry. Returns `None` if nothing remains worth writing.
136    ///
137    /// # Who calls this
138    ///
139    /// Only persistent backends with wire-format constraints invoke this — today that's
140    /// the disk-v2 sender (`SenderAdapter::send`/`try_send`). In-memory channels skip it
141    /// entirely because they hold the in-memory representation and have no nesting-limit
142    /// risk. A new backend with similar constraints should call this in the same place
143    /// and surface the resulting `FilterDrops` to `BufferSender` so that buffer-usage
144    /// instrumentation stays consistent with what actually lands in the buffer.
145    ///
146    /// # Default behaviour
147    ///
148    /// The default returns `Some(self)` if the item carries any events, and `None` if
149    /// it is already empty. This means an item that arrives empty (`event_count() == 0`)
150    /// is silently *not* persisted — preserving the pre-existing
151    /// "don't write empty records to disk" behaviour the call site used to enforce.
152    /// Types whose owners want empty items to be persisted must override this.
153    ///
154    /// # Skipping this call
155    ///
156    /// If a persistent backend writes an item without first calling `filter_unencodable`,
157    /// any sub-item that exceeds the format's limits will surface as a hard
158    /// [`Encodable::encode`] error and the *entire* item is rejected — including any
159    /// sibling sub-items that would otherwise have encoded fine. The filter is the only
160    /// path that produces graceful per-item drop with telemetry and a `Rejected` event
161    /// status; the encode-level check exists purely as defense-in-depth to ensure a
162    /// corrupt record cannot reach disk if a future caller forgets to filter.
163    fn filter_unencodable(self) -> Option<Self> {
164        if self.event_count() > 0 {
165            Some(self)
166        } else {
167            None
168        }
169    }
170
171    /// Returns whether every sub-item can be persisted by a backend with wire-format
172    /// constraints, without consuming or modifying the item.
173    ///
174    /// This is the non-destructive counterpart to [`Bufferable::filter_unencodable`], and
175    /// exists so routing policy can be decided *before* any filtering happens. In
176    /// particular `WhenFull::Overflow` needs to know that an item can never reach disk, so
177    /// it can hand the item to the overflow stage intact rather than pruning sub-items for
178    /// a write that would not have succeeded at any buffer occupancy.
179    ///
180    /// The default returns `true`, which is correct for any type without format limits.
181    /// Implementors overriding [`Bufferable::filter_unencodable`] must override this too,
182    /// and the two must agree: this returns `false` exactly when `filter_unencodable` would
183    /// drop at least one sub-item.
184    fn is_fully_encodable(&self) -> bool {
185        true
186    }
187}
188
189/// Hook for observing items as they are sent into a `BufferSender`.
190pub trait BufferInstrumentation<T: Bufferable>: Send + Sync + 'static {
191    /// Called immediately before the item is emitted to the underlying buffer.
192    /// The underlying type is stored in an `Arc`, so we cannot have `&mut self`.
193    fn on_send(&self, item: &mut T);
194}
195
196pub trait EventCount {
197    fn event_count(&self) -> usize;
198}
199
200impl<T> EventCount for Vec<T>
201where
202    T: EventCount,
203{
204    fn event_count(&self) -> usize {
205        self.iter().map(EventCount::event_count).sum()
206    }
207}
208
209impl<T> EventCount for &T
210where
211    T: EventCount,
212{
213    fn event_count(&self) -> usize {
214        (*self).event_count()
215    }
216}
217
218#[track_caller]
219pub(crate) fn spawn_named<T>(
220    task: impl std::future::Future<Output = T> + Send + 'static,
221    _name: &str,
222) -> tokio::task::JoinHandle<T>
223where
224    T: Send + 'static,
225{
226    #[cfg(tokio_unstable)]
227    return tokio::task::Builder::new()
228        .name(_name)
229        .spawn(task)
230        .expect("tokio task should spawn");
231
232    #[cfg(not(tokio_unstable))]
233    tokio::spawn(task)
234}