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}