perf(get): reduce response write allocations (#5890)

Avoid cloning cache-served GET bodies, preserve downstream vectored writes through the GET close-detection wrapper, and remove per-stripe EC decode sidecar allocations.

Co-authored-by: heihutu <heihutu@gmail.com>
Co-authored-by: zhi22915 <qiuzgang@gmail.com>
This commit is contained in:
houseme
2026-08-09 16:58:41 +08:00
committed by GitHub
parent 27ecdb88b1
commit a71726ef49
3 changed files with 84 additions and 17 deletions
+69 -1
View File
@@ -48,6 +48,7 @@ use metrics::counter;
use std::{
collections::{HashMap, VecDeque},
future::Future,
io::IoSlice,
pin::Pin,
sync::OnceLock,
task::{Context, Poll},
@@ -76,6 +77,16 @@ impl<W: AsyncWrite + Unpin> AsyncWrite for GetObjectDownstreamWriter<W> {
.map(|result| result.map_err(mark_get_object_downstream_closed))
}
fn poll_write_vectored(mut self: Pin<&mut Self>, cx: &mut Context<'_>, bufs: &[IoSlice<'_>]) -> Poll<std::io::Result<usize>> {
Pin::new(&mut self.inner)
.poll_write_vectored(cx, bufs)
.map(|result| result.map_err(mark_get_object_downstream_closed))
}
fn is_write_vectored(&self) -> bool {
self.inner.is_write_vectored()
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
Pin::new(&mut self.inner)
.poll_flush(cx)
@@ -3101,7 +3112,7 @@ mod metadata_cache_tests {
mod tests {
use super::*;
use crate::erasure::coding::BitrotWriter;
use std::io::{Cursor, ErrorKind};
use std::io::{Cursor, ErrorKind, IoSlice};
use std::sync::{
Arc,
atomic::{AtomicUsize, Ordering},
@@ -3128,6 +3139,63 @@ mod tests {
);
}
#[tokio::test]
async fn downstream_writer_preserves_vectored_write_support() {
#[derive(Default)]
struct VectoredSink {
writes: usize,
vectored_writes: usize,
bytes: Vec<u8>,
}
impl AsyncWrite for VectoredSink {
fn poll_write(mut self: Pin<&mut Self>, _cx: &mut Context<'_>, buf: &[u8]) -> Poll<std::io::Result<usize>> {
self.writes += 1;
self.bytes.extend_from_slice(buf);
Poll::Ready(Ok(buf.len()))
}
fn poll_write_vectored(
mut self: Pin<&mut Self>,
_cx: &mut Context<'_>,
bufs: &[IoSlice<'_>],
) -> Poll<std::io::Result<usize>> {
self.vectored_writes += 1;
let mut written = 0;
for buf in bufs {
written += buf.len();
self.bytes.extend_from_slice(buf);
}
Poll::Ready(Ok(written))
}
fn is_write_vectored(&self) -> bool {
true
}
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
Poll::Ready(Ok(()))
}
fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
Poll::Ready(Ok(()))
}
}
let mut writer = GetObjectDownstreamWriter::new(VectoredSink::default());
assert!(writer.is_write_vectored(), "downstream writer must preserve vectored-write capability");
let written = writer
.write_vectored(&[IoSlice::new(b"hello "), IoSlice::new(b"world")])
.await
.expect("vectored write through downstream adapter must succeed");
assert_eq!(written, 11);
assert_eq!(writer.inner.vectored_writes, 1);
assert_eq!(writer.inner.writes, 0);
assert_eq!(writer.inner.bytes, b"hello world");
}
async fn local_test_disks(count: usize, bucket: &str) -> (Vec<tempfile::TempDir>, Vec<Option<crate::disk::DiskStore>>) {
let mut dirs = Vec::with_capacity(count);
let mut disks = Vec::with_capacity(count);