v0.20.4: mux performance + observability — universal across storage
Four targeted changes to maximize mux throughput regardless of storage backend (local SSD, local HDD, NFS, network share) and surface enough log data to diagnose 'mux slow' reports without a re-rip: 1. POSIX_FADV_SEQUENTIAL on FileSectorSource::open (Linux only). Widens the kernel readahead window for sequential ISO reads. One syscall at open, free on every storage type. 2. POSIX_FADV_DONTNEED on the ISO read side after every 32 MiB chunk. Mirrors the writeback DONTNEED that already runs on the write side. Keeps the read-side page cache bounded during multi-GB ISO reads — eliminates the OOM-pressure / eviction-storm risk on long mux runs. Linux only; per-drop trace at target="mux". 3. WritebackFile::create_with_size_hint(path, size_bytes) calls fallocate(FALLOC_FL_KEEP_SIZE) on Linux to pre-reserve extents for the output. Reported file size stays 0 (writes grow it naturally) but the on-disk extent allocation is contiguous — reduces extent fragmentation for big sequential muxes. Wired into mkv:// and m2ts:// output paths via DiscTitle::size_bytes. No-op on macOS/Windows; old create() kept with #[allow(dead_code)] for callers without a size hint. 4. Adaptive WRITEBACK_CHUNK_BYTES in the Linux writeback pipeline. Tracks sync_file_range(WAIT_AFTER) elapsed_ms in a rolling 16-sample window. p95 > 200 ms → double chunk size (cap 256 MiB). p95 < 20 ms → halve (floor 4 MiB). One algorithm, both fast-storage (small chunks, responsive) and slow-storage (big chunks, fewer commit round-trips) optimized. Per-chunk trace + per-32-chunk debug snapshot + info-on-resize so an operator can see where the autoscaler settled. All four are universal — no storage-type detection, no env vars to flip, no per-deploy tuning required. Total +201/-6 across four files.
This commit is contained in:
+66
-1
@@ -15,6 +15,12 @@ use crate::error::{Error, Result};
|
||||
|
||||
use super::{SectorSink, SectorSource};
|
||||
|
||||
/// Bytes-read threshold per `posix_fadvise(DONTNEED)` drop on the
|
||||
/// read side. Mirrors the writeback chunk size so the read-side
|
||||
/// page cache stays bounded the same way the write side does.
|
||||
#[cfg(target_os = "linux")]
|
||||
const READ_DROP_CHUNK_BYTES: u64 = 32 * 1024 * 1024;
|
||||
|
||||
/// SectorSource backed by a file (ISO image).
|
||||
///
|
||||
/// Seeks to `lba * 2048`, reads `count * 2048` bytes per call. The
|
||||
@@ -27,6 +33,14 @@ use super::{SectorSink, SectorSource};
|
||||
pub struct FileSectorSource {
|
||||
file: File,
|
||||
capacity: u32,
|
||||
/// Bytes read since the last `posix_fadvise(DONTNEED)` drop.
|
||||
/// Only updated on Linux; on other targets it stays at 0.
|
||||
#[cfg(target_os = "linux")]
|
||||
bytes_read_since_drop: u64,
|
||||
/// Byte offset at which the current drop window starts (the
|
||||
/// next `posix_fadvise(DONTNEED)` call drops from here).
|
||||
#[cfg(target_os = "linux")]
|
||||
drop_window_start: u64,
|
||||
}
|
||||
|
||||
impl FileSectorSource {
|
||||
@@ -45,7 +59,26 @@ impl FileSectorSource {
|
||||
.into());
|
||||
}
|
||||
let capacity = sectors as u32;
|
||||
Ok(Self { file, capacity })
|
||||
|
||||
// Hint sequential access on Linux so the kernel's readahead
|
||||
// window widens for the ISO sweep. Best-effort: return value
|
||||
// is ignored. On macOS / Windows this is a no-op.
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
use std::os::unix::io::AsRawFd;
|
||||
unsafe {
|
||||
libc::posix_fadvise(file.as_raw_fd(), 0, 0, libc::POSIX_FADV_SEQUENTIAL);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
file,
|
||||
capacity,
|
||||
#[cfg(target_os = "linux")]
|
||||
bytes_read_since_drop: 0,
|
||||
#[cfg(target_os = "linux")]
|
||||
drop_window_start: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +108,38 @@ impl SectorSource for FileSectorSource {
|
||||
self.file
|
||||
.read_exact(&mut buf[..bytes])
|
||||
.map_err(|e| Error::IoError { source: e })?;
|
||||
|
||||
// On Linux, periodically drop the just-read region from the
|
||||
// page cache to keep cache pressure bounded during multi-GB
|
||||
// sequential ISO reads. Mirrors the write-side pipeline.
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
use std::os::unix::io::AsRawFd;
|
||||
self.bytes_read_since_drop += bytes as u64;
|
||||
if self.bytes_read_since_drop >= READ_DROP_CHUNK_BYTES {
|
||||
let drop_start = self.drop_window_start;
|
||||
let drop_len = self.bytes_read_since_drop;
|
||||
let t0 = std::time::Instant::now();
|
||||
unsafe {
|
||||
libc::posix_fadvise(
|
||||
self.file.as_raw_fd(),
|
||||
drop_start as i64,
|
||||
drop_len as i64,
|
||||
libc::POSIX_FADV_DONTNEED,
|
||||
);
|
||||
}
|
||||
let elapsed_ms = t0.elapsed().as_millis();
|
||||
let start_lba = drop_start / 2048;
|
||||
let end_lba = (drop_start + drop_len) / 2048;
|
||||
tracing::trace!(
|
||||
target: "mux",
|
||||
"FileSectorSource fadvise DONTNEED lba=[{start_lba}..{end_lba}) bytes={drop_len} elapsed_ms={elapsed_ms}"
|
||||
);
|
||||
self.drop_window_start = drop_start + drop_len;
|
||||
self.bytes_read_since_drop = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user