Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
40aa9b9
enhancement(buffers): track unread disk usage
Jansen-w Aug 27, 2026
8cda89c
enhancement(topology): report disk buffer reload behavior
Jansen-w Aug 27, 2026
7b9f6e0
enhancement(config): detect unreferenced disk buffers
Jansen-w Aug 27, 2026
2163401
refactor(topology): simplify disk buffer lifecycle diagnostics
Jansen-w Sep 3, 2026
105a472
fix(topology): resolve disk buffer drain conflicts
Jansen-w Sep 3, 2026
3301482
fix(topology): report busy disk buffer drains
Jansen-w Sep 4, 2026
75d89e6
fix(topology): match canonical drain paths
Jansen-w Sep 4, 2026
4ebd182
fix(topology): synchronize orphan scan reporting
Jansen-w Sep 4, 2026
d1f5f8b
fix(topology): avoid blocking scan scheduling
Jansen-w Sep 4, 2026
3c80414
fix(topology): resolve drain paths off runtime
Jansen-w Sep 4, 2026
8dfe515
Merge master and resolve disk buffer lifecycle conflicts
Jansen-w Oct 5, 2026
c7b01c7
Merge remote-tracking branch 'origin/master' into fix/disk-buffer-orp…
Jansen-w Oct 13, 2026
dae02c6
docs(buffers): clarify usage snapshot semantics
Jansen-w Oct 13, 2026
6b1e979
test(topology): harden disk lifecycle shutdown checks
Jansen-w Oct 13, 2026
3e63ab3
fix(topology): clarify replaced disk buffer warning
Jansen-w Oct 13, 2026
9354d15
fix(topology): ignore vanished buffer files
Jansen-w Oct 13, 2026
4769361
refactor(topology): simplify orphan buffer scans
Jansen-w Oct 13, 2026
5b44400
refactor(topology): reuse active buffer discovery
Jansen-w Oct 13, 2026
57f4a32
fix(topology): release completed disk drain outputs
Jansen-w Oct 13, 2026
6a2fe4c
fix(buffers): await disk finalizers before reopen
Jansen-w Oct 13, 2026
7c9f867
fix(topology): rescan buffers after reload
Jansen-w Oct 13, 2026
c3648a8
fix(buffers): quiesce cleanup before reopen
Jansen-w Oct 14, 2026
a07e9d3
fix(topology): release buffers on failed builds
Jansen-w Oct 14, 2026
cf02daa
refactor(buffers): unify disk task lifecycle
Jansen-w Oct 14, 2026
5619bb4
refactor(topology): keep disk diagnostics non-invasive
Jansen-w Oct 14, 2026
78a4ddf
fix(topology): warn when replacement usage is unavailable
Jansen-w Oct 14, 2026
2cf7fc5
docs(topology): clarify detached buffer drains
Jansen-w Oct 14, 2026
061e707
test(topology): trim orphan scanner coverage
Jansen-w Oct 14, 2026
eeb09bf
fix(topology): correct disk diagnostics
Jansen-w Oct 14, 2026
fce10c1
test(buffers): use conventional module name
Jansen-w Oct 15, 2026
13ae326
fix(topology): conservatively exclude configured buffers
Jansen-w Oct 15, 2026
e101083
enhancement(topology): emit disk lifecycle metrics
Jansen-w Oct 19, 2026
798fc42
fix(topology): add disk lifecycle internal events
Jansen-w Oct 19, 2026
bf3c163
enhancement(topology): emit disk lifecycle metrics
Jansen-w Oct 19, 2026
43cc059
fix(metrics): namespace disk lifecycle counter
Jansen-w Oct 19, 2026
c3c6e32
fix(topology): make replacement diagnostics conservative
Jansen-w Oct 19, 2026
3cacfdb
fix(topology): clarify disk buffer lifecycle messages
Jansen-w Oct 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelog.d/disk_buffer_orphan_safety.enhancement.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Vector now detects orphaned disk buffers and provides lifecycle visibility when disk-buffered sinks continue draining in the background during configuration reloads.

authors: Jansen-w
1 change: 1 addition & 0 deletions lib/vector-buffers/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ pub mod test;
pub mod topology;

pub(crate) mod variants;
pub use variants::disk_v2::DiskBufferUsageSnapshot;

/// `disk_v2`'s write-buffer size, re-exported under `test` so the harness can
/// size payloads against the real value instead of hardcoding it.
Expand Down
67 changes: 66 additions & 1 deletion lib/vector-buffers/src/topology/channel/sender.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::{
BufferInstrumentation, Bufferable, WhenFull,
buffer_usage_data::BufferUsageHandle,
internal_events::BufferSendDuration,
variants::disk_v2::{self, ProductionFilesystem, TryWriteOutcome},
variants::disk_v2::{self, DiskBufferUsageSnapshot, ProductionFilesystem, TryWriteOutcome},
};

/// Adapter for papering over various sender backends.
Expand Down Expand Up @@ -164,6 +164,16 @@ where
Self::DiskV2(_) => None,
}
}

fn usage_snapshot(&self) -> Option<DiskBufferUsageSnapshot> {
match self {
Self::InMemory(_) => Some(DiskBufferUsageSnapshot {
event_count: 0,
byte_size: 0,
}),
Self::DiskV2(writer) => writer.try_lock().ok().map(|writer| writer.usage_snapshot()),
}
}
}

enum UsageAccounting {
Expand Down Expand Up @@ -278,6 +288,19 @@ impl<T: Bufferable> BufferSender<T> {
pub fn with_custom_instrumentation(&mut self, instrumentation: impl BufferInstrumentation<T>) {
self.custom_instrumentation = Some(Arc::new(instrumentation));
}

/// Returns the aggregate approximate usage of all disk stages in this buffer topology.
///
/// Returns `None` rather than waiting if any disk writer is currently busy.
pub fn disk_usage_snapshot(&self) -> Option<DiskBufferUsageSnapshot> {
let mut usage = self.base.usage_snapshot()?;
if let Some(overflow) = self.overflow.as_ref() {
let overflow_usage = overflow.disk_usage_snapshot()?;
usage.event_count = usage.event_count.saturating_add(overflow_usage.event_count);
usage.byte_size = usage.byte_size.saturating_add(overflow_usage.byte_size);
}
Some(usage)
}
}

impl<T: Bufferable> BufferSender<T> {
Expand Down Expand Up @@ -381,3 +404,45 @@ impl<T: Bufferable> BufferSender<T> {
Ok(())
}
}

#[cfg(test)]
mod usage_snapshot_tests {
Comment thread
Jansen-w marked this conversation as resolved.
Outdated
use std::num::{NonZeroU64, NonZeroUsize};

use temp_dir::TempDir;
use tracing::Span;

use super::SenderAdapter;
use crate::{BufferConfig, BufferType, MemoryBufferSize, WhenFull, test::SizedRecord};

#[tokio::test]
async fn busy_disk_stage_makes_diagnostic_snapshot_unavailable() {
let config = BufferConfig::Chained(vec![
BufferType::Memory {
size: MemoryBufferSize::MaxEvents(NonZeroUsize::new(1).unwrap()),
when_full: WhenFull::Overflow,
},
BufferType::DiskV2 {
max_size: NonZeroU64::new(268_435_488).unwrap(),
when_full: WhenFull::Block,
},
]);
let data_dir = TempDir::new().unwrap();
let (sender, _receiver) = config
.build::<SizedRecord>(
Some(data_dir.path().to_path_buf()),
"busy".into(),
Span::none(),
)
.await
.unwrap();
let overflow = sender.overflow.as_ref().unwrap();
let writer = match &overflow.base {
SenderAdapter::DiskV2(writer) => writer,
SenderAdapter::InMemory(_) => unreachable!(),
};
let _guard = writer.lock().await;

assert_eq!(sender.disk_usage_snapshot(), None);
}
}
5 changes: 5 additions & 0 deletions lib/vector-buffers/src/variants/disk_v2/ledger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,11 @@ where
.increment_dropped_event_count_and_byte_size(0, total_record_size, false);
}

/// Removes an unread data file tail that could not be interpreted as a record.
pub fn track_abandoned_tail_bytes(&self, total_size: u64) {
self.decrement_total_buffer_size(total_size);
}

Comment thread
Jansen-w marked this conversation as resolved.
Outdated
/// Marks the writer as finished.
///
/// If the writer was not yet marked done, `false` is returned. Otherwise, `true` is returned,
Expand Down
30 changes: 26 additions & 4 deletions lib/vector-buffers/src/variants/disk_v2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ use std::{

use async_trait::async_trait;
use snafu::{ResultExt, Snafu};
use tracing::debug;
use tracing::{debug, info};
use vector_common::finalization::Finalizable;

mod backed_archive;
Expand Down Expand Up @@ -212,6 +212,15 @@ use crate::{
},
};

/// An approximate view of unread records in a disk buffer.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct DiskBufferUsageSnapshot {
/// Number of unread events in the buffer.
pub event_count: u64,
/// Encoded size of unread records in the buffer.
pub byte_size: u64,
}

/// Error that occurred when creating/loading a disk buffer.
#[derive(Debug, Snafu)]
pub enum BufferError<T>
Expand Down Expand Up @@ -396,7 +405,8 @@ where
usage_handle.set_buffer_limits(Some(max_size.get()), None);

let buffer_path = get_disk_v2_data_dir_path(data_dir, id);
let builder = DiskBufferConfigBuilder::from_path(buffer_path).max_buffer_size(max_size.get());
let builder =
DiskBufferConfigBuilder::from_path(buffer_path.clone()).max_buffer_size(max_size.get());
// Shrink the data-file size (and the matching record size) so files fill and
// rotate constantly. That is what reaches the rare recovery paths the bug hides
// in: reopening a file whose last write was cut short, and reusing a file number
Expand All @@ -412,9 +422,21 @@ where
None => builder,
};
let config = builder.build()?;
Buffer::from_config(config, usage_handle)
let (writer, reader) = Buffer::from_config(config, usage_handle)
.await
.map_err(Into::into)
.map_err(Box::<dyn Error + Send + Sync>::from)?;
let usage = writer.usage_snapshot();
if usage.event_count > 0 || usage.byte_size > 0 {
info!(
buffer_id = id,
buffer_dir = buffer_path.to_string_lossy().as_ref(),
accounted_events = usage.event_count,
accounted_bytes = usage.byte_size,
message = "Opened disk buffer with remaining accounted data; resuming processing.",
Comment thread
Jansen-w marked this conversation as resolved.
Outdated
);
}

Ok((writer, reader))
}

pub(crate) fn get_disk_v2_data_dir_path(base_dir: &Path, buffer_id: &str) -> PathBuf {
Expand Down
3 changes: 1 addition & 2 deletions lib/vector-buffers/src/variants/disk_v2/reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -598,8 +598,7 @@ where
abandoned_tail_bytes,
"Data file tail was abandoned after bad read. Adjusting buffer size to compensate.",
);
self.ledger
.decrement_total_buffer_size(abandoned_tail_bytes);
self.ledger.track_abandoned_tail_bytes(abandoned_tail_bytes);
}

Ok(())
Expand Down
10 changes: 8 additions & 2 deletions lib/vector-buffers/src/variants/disk_v2/tests/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,16 +236,19 @@ async fn initial_size_correct_with_multievents() {
drop(ledger);

// Reopen the buffer with a full reader to verify the persisted data.
let (writer, mut reader, ledger, usage) =
let (mut writer, mut reader, ledger, usage) =
create_default_buffer_v2_with_usage::<_, MultiEventRecord>(&data_dir).await;
drop(writer);
let disk_usage = writer.usage_snapshot();
writer.close();

// Make sure our usage data agrees with our expected event count and byte size:
let snapshot = usage.snapshot();
assert_eq!(expected_events as u64, snapshot.received_event_count);
assert_eq!(expected_bytes as u64, snapshot.received_byte_size);
assert_eq!(expected_events as u64, ledger.get_total_records());
assert_eq!(expected_bytes, total_bytes_written);
assert_eq!(expected_events as u64, disk_usage.event_count);
assert_eq!(expected_bytes as u64, disk_usage.byte_size);

// Make sure we can read all of the records we wrote, and recalculate some of these
// values from the source:
Expand All @@ -260,6 +263,9 @@ async fn initial_size_correct_with_multievents() {

assert_eq!(expected_events, total_record_events);
assert_eq!(expected_records, total_records_read);
let disk_usage = writer.usage_snapshot();
assert_eq!(0, disk_usage.event_count);
assert_eq!(0, disk_usage.byte_size);
}
})
.await;
Expand Down
12 changes: 12 additions & 0 deletions lib/vector-buffers/src/variants/disk_v2/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -913,6 +913,18 @@ where
}
}

/// Gets the current published usage of the buffer.
///
/// Writes are included after they are flushed and reads after they are acknowledged, matching
/// the ledger's existing published-data semantics. The two counters are sampled separately, so
/// the result is approximate when reads and writes are being published concurrently.
pub fn usage_snapshot(&self) -> super::DiskBufferUsageSnapshot {
Comment thread
Jansen-w marked this conversation as resolved.
Outdated
super::DiskBufferUsageSnapshot {
event_count: self.ledger.get_total_records(),
byte_size: self.ledger.get_total_buffer_size(),
Comment thread
Jansen-w marked this conversation as resolved.
Outdated
}
}

fn get_next_record_id(&mut self) -> u64 {
self.next_record_id + self.unflushed_events
}
Expand Down
1 change: 1 addition & 0 deletions src/topology/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub mod schema;

pub mod builder;
mod controller;
mod orphaned_disk_buffers;
mod ready_arrays;
mod running;
mod task;
Expand Down
Loading
Loading