Skip to content

Commit a6a2bc4

Browse files
committed
enhancement(buffers): improve disk buffer lifecycle visibility
1 parent 3df0f29 commit a6a2bc4

10 files changed

Lines changed: 580 additions & 19 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Vector now detects orphaned disk buffers and logs buffer reuse and background drain progress during configuration reloads.
2+
3+
authors: Jansen-w

‎lib/vector-buffers/src/lib.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ pub mod test;
3131
pub mod topology;
3232

3333
pub(crate) mod variants;
34+
pub use variants::disk_v2::{DiskBufferUsageHandle, DiskBufferUsageSnapshot};
3435

3536
/// `disk_v2`'s write-buffer size, re-exported under `test` so the harness can
3637
/// size payloads against the real value instead of hardcoding it.

‎lib/vector-buffers/src/topology/channel/sender.rs‎

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use crate::{
1414
BufferInstrumentation, Bufferable, WhenFull,
1515
buffer_usage_data::BufferUsageHandle,
1616
internal_events::BufferSendDuration,
17-
variants::disk_v2::{self, ProductionFilesystem, TryWriteOutcome},
17+
variants::disk_v2::{self, DiskBufferUsageHandle, ProductionFilesystem, TryWriteOutcome},
1818
};
1919

2020
/// Adapter for papering over various sender backends.
@@ -24,7 +24,10 @@ pub enum SenderAdapter<T: Bufferable> {
2424
InMemory(LimitedSender<T>),
2525

2626
/// The disk v2 buffer.
27-
DiskV2(Arc<Mutex<disk_v2::BufferWriter<T, ProductionFilesystem>>>),
27+
DiskV2 {
28+
writer: Arc<Mutex<disk_v2::BufferWriter<T, ProductionFilesystem>>>,
29+
usage: DiskBufferUsageHandle,
30+
},
2831
}
2932

3033
impl<T: Bufferable> From<LimitedSender<T>> for SenderAdapter<T> {
@@ -35,7 +38,11 @@ impl<T: Bufferable> From<LimitedSender<T>> for SenderAdapter<T> {
3538

3639
impl<T: Bufferable> From<disk_v2::BufferWriter<T, ProductionFilesystem>> for SenderAdapter<T> {
3740
fn from(v: disk_v2::BufferWriter<T, ProductionFilesystem>) -> Self {
38-
Self::DiskV2(Arc::new(Mutex::new(v)))
41+
let usage = v.usage_handle();
42+
Self::DiskV2 {
43+
writer: Arc::new(Mutex::new(v)),
44+
usage,
45+
}
3946
}
4047
}
4148

@@ -50,7 +57,7 @@ where
5057
.await
5158
.map(|()| TryWriteOutcome::Written)
5259
.map_err(Into::into),
53-
Self::DiskV2(writer) => {
60+
Self::DiskV2 { writer, .. } => {
5461
let mut writer = writer.lock().await;
5562

5663
writer.write_record_outcome(item).await.map_err(|e| {
@@ -73,7 +80,7 @@ where
7380
.try_send(item)
7481
.map(|()| TryWriteOutcome::Written)
7582
.or_else(|e| Ok(TryWriteOutcome::Full(e.into_inner()))),
76-
Self::DiskV2(writer) => {
83+
Self::DiskV2 { writer, .. } => {
7784
let mut writer = writer.lock().await;
7885

7986
writer.try_write_record(item).await.map_err(|e| {
@@ -93,7 +100,7 @@ where
93100
pub(crate) async fn flush(&mut self) -> crate::Result<()> {
94101
match self {
95102
Self::InMemory(_) => Ok(()),
96-
Self::DiskV2(writer) => {
103+
Self::DiskV2 { writer, .. } => {
97104
let mut writer = writer.lock().await;
98105
writer.flush().await.map_err(|e| {
99106
// Errors on the I/O path, which is all that flushing touches, are never recoverable.
@@ -108,7 +115,14 @@ where
108115
pub fn capacity(&self) -> Option<usize> {
109116
match self {
110117
Self::InMemory(tx) => Some(tx.available_capacity()),
111-
Self::DiskV2(_) => None,
118+
Self::DiskV2 { .. } => None,
119+
}
120+
}
121+
122+
fn disk_usage_handle(&self) -> Option<DiskBufferUsageHandle> {
123+
match self {
124+
Self::InMemory(_) => None,
125+
Self::DiskV2 { usage, .. } => Some(usage.clone()),
112126
}
113127
}
114128
}
@@ -225,6 +239,19 @@ impl<T: Bufferable> BufferSender<T> {
225239
pub fn with_custom_instrumentation(&mut self, instrumentation: impl BufferInstrumentation<T>) {
226240
self.custom_instrumentation = Some(Arc::new(instrumentation));
227241
}
242+
243+
/// Returns read-only usage handles for all disk stages in this buffer topology.
244+
pub fn disk_usage_handles(&self) -> Vec<DiskBufferUsageHandle> {
245+
let mut handles = self
246+
.base
247+
.disk_usage_handle()
248+
.into_iter()
249+
.collect::<Vec<_>>();
250+
if let Some(overflow) = self.overflow.as_ref() {
251+
handles.extend(overflow.disk_usage_handles());
252+
}
253+
handles
254+
}
228255
}
229256

230257
impl<T: Bufferable> BufferSender<T> {

‎lib/vector-buffers/src/variants/disk_v2/ledger.rs‎

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use tokio::{
2323
use vector_common::finalizer::OrderedFinalizer;
2424

2525
use super::{
26-
Filesystem,
26+
DiskBufferUsageHandle, Filesystem,
2727
backed_archive::BackedArchive,
2828
common::{
2929
DEFAULT_DATA_FILE_CLEANUP_INTERVAL, DiskBufferConfig, MAX_FILE_ID, align16,
@@ -257,6 +257,7 @@ where
257257
last_flush: AtomicCell<Instant>,
258258
// Tracks usage data about the buffer.
259259
usage_handle: BufferUsageHandle,
260+
disk_usage_handle: DiskBufferUsageHandle,
260261
}
261262

262263
impl<FS> Ledger<FS>
@@ -317,6 +318,10 @@ where
317318
self.total_buffer_size.load(Ordering::Acquire)
318319
}
319320

321+
pub fn disk_usage_handle(&self) -> DiskBufferUsageHandle {
322+
self.disk_usage_handle.clone()
323+
}
324+
320325
/// Increments the total number of bytes for all unread records in the buffer.
321326
pub fn increment_total_buffer_size(&self, amount: u64) {
322327
let last_total_buffer_size = self.total_buffer_size.fetch_add(amount, Ordering::AcqRel);
@@ -530,6 +535,7 @@ where
530535
self.increment_total_buffer_size(record_size);
531536
self.usage_handle
532537
.increment_received_event_count_and_byte_size(event_count, record_size);
538+
self.disk_usage_handle.increment(event_count, record_size);
533539
self.notify_writer_waiters();
534540
next_record_id
535541
}
@@ -539,6 +545,8 @@ where
539545
self.decrement_total_buffer_size(total_record_size);
540546
self.usage_handle
541547
.increment_sent_event_count_and_byte_size(event_count, total_record_size);
548+
self.disk_usage_handle
549+
.decrement(event_count, total_record_size);
542550
}
543551

544552
/// Tracks the known byte size of a record that the reader consumed but could not decode.
@@ -548,6 +556,13 @@ where
548556
// provides the exact byte span that has left the buffer.
549557
self.usage_handle
550558
.increment_dropped_event_count_and_byte_size(0, total_record_size, false);
559+
self.disk_usage_handle.decrement(0, total_record_size);
560+
}
561+
562+
/// Removes an unread data file tail that could not be interpreted as a record.
563+
pub fn track_abandoned_tail_bytes(&self, total_size: u64) {
564+
self.decrement_total_buffer_size(total_size);
565+
self.disk_usage_handle.decrement(0, total_size);
551566
}
552567

553568
/// Marks the writer as finished.
@@ -675,6 +690,8 @@ where
675690
initial_buffer_events,
676691
initial_buffer_size,
677692
);
693+
self.disk_usage_handle
694+
.set(initial_buffer_events, initial_buffer_size);
678695
}
679696

680697
pub fn track_dropped_events(&self, count: u64) {
@@ -688,6 +705,7 @@ where
688705
// TODO: Can we do better here?
689706
self.usage_handle
690707
.increment_dropped_event_count_and_byte_size(count, 0, false);
708+
self.disk_usage_handle.decrement(count, 0);
691709
}
692710

693711
/// Records that a record was dropped because it could never be written to the buffer (e.g. it
@@ -810,6 +828,7 @@ where
810828
data_file_cleanup: DataFileCleanupState::new(cleanup_reader_file_id),
811829
last_flush: AtomicCell::new(Instant::now()),
812830
usage_handle,
831+
disk_usage_handle: DiskBufferUsageHandle::new(),
813832
};
814833

815834
// NOTE: We deliberately do not seed `total_buffer_size` here. Historically, the ledger

‎lib/vector-buffers/src/variants/disk_v2/mod.rs‎

Lines changed: 83 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -174,12 +174,15 @@ use std::{
174174
marker::PhantomData,
175175
num::NonZeroU64,
176176
path::{Path, PathBuf},
177-
sync::Arc,
177+
sync::{
178+
Arc,
179+
atomic::{AtomicU64, Ordering},
180+
},
178181
};
179182

180183
use async_trait::async_trait;
181184
use snafu::{ResultExt, Snafu};
182-
use tracing::debug;
185+
use tracing::{debug, info};
183186
use vector_common::finalization::Finalizable;
184187

185188
mod backed_archive;
@@ -212,6 +215,62 @@ use crate::{
212215
},
213216
};
214217

218+
/// An approximate view of unread records in a disk buffer.
219+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
220+
pub struct DiskBufferUsageSnapshot {
221+
/// Number of unread events in the buffer.
222+
pub event_count: u64,
223+
/// Encoded size of unread records in the buffer.
224+
pub byte_size: u64,
225+
}
226+
227+
/// Read-only access to the current unread usage of a disk buffer.
228+
#[derive(Clone, Debug)]
229+
pub struct DiskBufferUsageHandle {
230+
event_count: Arc<AtomicU64>,
231+
byte_size: Arc<AtomicU64>,
232+
}
233+
234+
impl DiskBufferUsageHandle {
235+
fn new() -> Self {
236+
Self {
237+
event_count: Arc::new(AtomicU64::new(0)),
238+
byte_size: Arc::new(AtomicU64::new(0)),
239+
}
240+
}
241+
242+
/// Gets the approximate current number and encoded size of unread records.
243+
pub fn snapshot(&self) -> DiskBufferUsageSnapshot {
244+
DiskBufferUsageSnapshot {
245+
event_count: self.event_count.load(Ordering::Relaxed),
246+
byte_size: self.byte_size.load(Ordering::Relaxed),
247+
}
248+
}
249+
250+
fn set(&self, event_count: u64, byte_size: u64) {
251+
self.event_count.store(event_count, Ordering::Relaxed);
252+
self.byte_size.store(byte_size, Ordering::Relaxed);
253+
}
254+
255+
fn increment(&self, event_count: u64, byte_size: u64) {
256+
self.event_count.fetch_add(event_count, Ordering::Relaxed);
257+
self.byte_size.fetch_add(byte_size, Ordering::Relaxed);
258+
}
259+
260+
fn decrement(&self, event_count: u64, byte_size: u64) {
261+
_ = self
262+
.event_count
263+
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
264+
Some(current.saturating_sub(event_count))
265+
});
266+
_ = self
267+
.byte_size
268+
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
269+
Some(current.saturating_sub(byte_size))
270+
});
271+
}
272+
}
273+
215274
/// Error that occurred when creating/loading a disk buffer.
216275
#[derive(Debug, Snafu)]
217276
pub enum BufferError<T>
@@ -396,7 +455,8 @@ where
396455
usage_handle.set_buffer_limits(Some(max_size.get()), None);
397456

398457
let buffer_path = get_disk_v2_data_dir_path(data_dir, id);
399-
let builder = DiskBufferConfigBuilder::from_path(buffer_path).max_buffer_size(max_size.get());
458+
let builder =
459+
DiskBufferConfigBuilder::from_path(buffer_path.clone()).max_buffer_size(max_size.get());
400460
// Shrink the data-file size (and the matching record size) so files fill and
401461
// rotate constantly. That is what reaches the rare recovery paths the bug hides
402462
// in: reopening a file whose last write was cut short, and reusing a file number
@@ -412,9 +472,27 @@ where
412472
None => builder,
413473
};
414474
let config = builder.build()?;
415-
Buffer::from_config(config, usage_handle)
475+
let (writer, reader) = Buffer::from_config(config, usage_handle)
416476
.await
417-
.map_err(Into::into)
477+
.map_err(Box::<dyn Error + Send + Sync>::from)?;
478+
let existing_usage = writer.usage_handle().snapshot();
479+
if existing_usage.event_count > 0 || existing_usage.byte_size > 0 {
480+
info!(
481+
buffer_id = id,
482+
buffer_dir = buffer_path.to_string_lossy().as_ref(),
483+
unread_events = existing_usage.event_count,
484+
unread_bytes = existing_usage.byte_size,
485+
message = "Opened disk buffer with unread data.",
486+
);
487+
} else {
488+
debug!(
489+
buffer_id = id,
490+
buffer_dir = buffer_path.to_string_lossy().as_ref(),
491+
message = "Opened empty disk buffer.",
492+
);
493+
}
494+
495+
Ok((writer, reader))
418496
}
419497

420498
pub(crate) fn get_disk_v2_data_dir_path(base_dir: &Path, buffer_id: &str) -> PathBuf {

‎lib/vector-buffers/src/variants/disk_v2/reader.rs‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -598,8 +598,7 @@ where
598598
abandoned_tail_bytes,
599599
"Data file tail was abandoned after bad read. Adjusting buffer size to compensate.",
600600
);
601-
self.ledger
602-
.decrement_total_buffer_size(abandoned_tail_bytes);
601+
self.ledger.track_abandoned_tail_bytes(abandoned_tail_bytes);
603602
}
604603

605604
Ok(())

‎lib/vector-buffers/src/variants/disk_v2/tests/basic.rs‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,8 @@ async fn initial_size_correct_with_multievents() {
238238
// Reopen the buffer with a full reader to verify the persisted data.
239239
let (writer, mut reader, ledger, usage) =
240240
create_default_buffer_v2_with_usage::<_, MultiEventRecord>(&data_dir).await;
241+
let disk_usage_handle = writer.usage_handle();
242+
let disk_usage = disk_usage_handle.snapshot();
241243
drop(writer);
242244

243245
// Make sure our usage data agrees with our expected event count and byte size:
@@ -246,6 +248,8 @@ async fn initial_size_correct_with_multievents() {
246248
assert_eq!(expected_bytes as u64, snapshot.received_byte_size);
247249
assert_eq!(expected_events as u64, ledger.get_total_records());
248250
assert_eq!(expected_bytes, total_bytes_written);
251+
assert_eq!(expected_events as u64, disk_usage.event_count);
252+
assert_eq!(expected_bytes as u64, disk_usage.byte_size);
249253

250254
// Make sure we can read all of the records we wrote, and recalculate some of these
251255
// values from the source:
@@ -260,6 +264,8 @@ async fn initial_size_correct_with_multievents() {
260264

261265
assert_eq!(expected_events, total_record_events);
262266
assert_eq!(expected_records, total_records_read);
267+
assert_eq!(0, disk_usage_handle.snapshot().event_count);
268+
assert_eq!(0, disk_usage_handle.snapshot().byte_size);
263269
}
264270
})
265271
.await;

‎lib/vector-buffers/src/variants/disk_v2/writer.rs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -913,6 +913,10 @@ where
913913
}
914914
}
915915

916+
pub(crate) fn usage_handle(&self) -> super::DiskBufferUsageHandle {
917+
self.ledger.disk_usage_handle()
918+
}
919+
916920
fn get_next_record_id(&mut self) -> u64 {
917921
self.next_record_id + self.unflushed_events
918922
}

0 commit comments

Comments
 (0)