feat(perf): add large PUT tuning and encode optimization (#3816)

This commit is contained in:
houseme
2026-06-24 19:04:04 +08:00
committed by GitHub
parent 3c6e9acfcb
commit efebcb66be
14 changed files with 3120 additions and 22 deletions
+31 -7
View File
@@ -53,7 +53,8 @@ use crate::error::ApiError;
use crate::server::convert_ecstore_object_info;
use crate::storage::access::{PostObjectRequestMarker, authorize_request, has_bypass_governance_header, req_info_mut};
use crate::storage::concurrency::{
ConcurrencyManager, GetObjectGuard, get_concurrency_aware_buffer_size, get_concurrency_manager,
ConcurrencyManager, GetObjectGuard, PutObjectGuard, get_concurrency_aware_buffer_size, get_concurrency_manager,
get_put_concurrency_aware_buffer_size,
};
use crate::storage::ecfs::*;
use crate::storage::head_prefix::{head_prefix_not_found_message, probe_prefix_has_children};
@@ -117,6 +118,8 @@ use rustfs_zip::{ArchiveLimits, CompressionFormat};
use s3s::dto::*;
use s3s::header::{X_AMZ_RESTORE, X_AMZ_RESTORE_OUTPUT_PATH};
use s3s::{S3Error, S3ErrorCode, S3Request, S3Response, S3Result, s3_error};
const DEFAULT_PUT_LARGE_CONCURRENCY_TUNING_MIN_SIZE_BYTES: i64 = 32 * 1024 * 1024;
use std::collections::HashMap;
use std::ops::Add;
use std::path::Path;
@@ -1347,6 +1350,10 @@ pub struct DefaultObjectUsecase {
}
impl DefaultObjectUsecase {
fn should_use_large_put_concurrency_tuning(size: i64) -> bool {
size >= DEFAULT_PUT_LARGE_CONCURRENCY_TUNING_MIN_SIZE_BYTES
}
#[cfg(test)]
pub fn without_context() -> Self {
Self { context: None }
@@ -2024,16 +2031,19 @@ impl DefaultObjectUsecase {
let server_side_encryption_requested =
server_side_encryption.is_some() || sse_customer_algorithm.is_some() || ssekms_key_id.is_some();
let mut put_request_guard = PutObjectGuard::new();
let concurrent_put_requests = PutObjectGuard::concurrent_requests();
// Apply adaptive buffer sizing based on file size for optimal streaming performance.
// Uses workload profile configuration (enabled by default) to select appropriate buffer size.
// Buffer sizes range from 32KB to 4MB depending on file size and configured workload profile.
// Concurrency-aware adjustment reduces buffer size under high concurrency to lower memory pressure.
// TODO: get_concurrency_aware_buffer_size reads ACTIVE_GET_REQUESTS (GET concurrency tracker),
// not PUT concurrency. Under pure PUT load the counter stays zero so buffers never shrink;
// unrelated GET load can shrink PUT buffers instead. Fix by adding ACTIVE_PUT_REQUESTS +
// PutObjectGuard and using PUT concurrency here. See PR #3514 review comment.
// Concurrency-aware adjustment reduces buffer size under high PUT concurrency to lower memory pressure.
let base_buffer_size = get_buffer_size_opt_in(size);
let buffer_size = get_concurrency_aware_buffer_size(size, base_buffer_size);
let buffer_size = if Self::should_use_large_put_concurrency_tuning(size) {
get_put_concurrency_aware_buffer_size(size, base_buffer_size)
} else {
base_buffer_size
};
// Detect zero-copy opportunity before encryption/compression decisions
// Zero-copy is beneficial for large unencrypted, uncompressed objects
@@ -2377,6 +2387,7 @@ impl DefaultObjectUsecase {
"PutObject store write returned"
);
let result: S3Result<S3Response<PutObjectOutput>> = Err(err.into());
put_request_guard.finish_err();
let _ = helper.complete(&result);
return result;
}
@@ -2462,6 +2473,19 @@ impl DefaultObjectUsecase {
);
}
debug!(
target: "rustfs::app::object_usecase",
component = "app",
subsystem = "object",
bucket = %bucket,
key = %key,
concurrent_put_requests,
buffer_size,
"PutObject request completed"
);
put_request_guard.finish_ok();
result
}
@@ -39,6 +39,7 @@ use std::time::Duration;
/// Global concurrent request counter for adaptive buffer sizing.
pub(crate) static ACTIVE_GET_REQUESTS: AtomicUsize = AtomicUsize::new(0);
pub(crate) static ACTIVE_PUT_REQUESTS: AtomicUsize = AtomicUsize::new(0);
#[derive(Debug, Clone, PartialEq)]
pub enum IoLoadLevel {
@@ -1377,6 +1378,17 @@ pub fn get_concurrency_aware_buffer_size(file_size: i64, base_buffer_size: usize
compute_concurrency_aware_buffer_size(file_size, base_buffer_size, concurrent_requests, load_concurrency_thresholds())
}
pub fn get_put_concurrency_aware_buffer_size(file_size: i64, base_buffer_size: usize) -> usize {
let concurrent_requests = ACTIVE_PUT_REQUESTS.load(Ordering::Relaxed);
{
use metrics::gauge;
gauge!("rustfs_concurrent_put_requests").set(concurrent_requests as f64);
}
compute_concurrency_aware_buffer_size(file_size, base_buffer_size, concurrent_requests, load_concurrency_thresholds())
}
/// Advanced concurrency-aware buffer sizing with file size optimization
///
/// This enhanced version considers both concurrency level and file size patterns
+5 -1
View File
@@ -18,7 +18,7 @@ use super::io_schedule::{
IoLoadLevel, IoLoadMetrics, IoPriority, IoPriorityQueue, IoPriorityQueueConfig, IoQueueStatus, IoSchedulerConfig, IoStrategy,
get_advanced_buffer_size,
};
use super::request_guard::GetObjectGuard;
use super::request_guard::{GetObjectGuard, PutObjectGuard};
use crate::app::context::resolve_performance_metrics;
use rustfs_concurrency::{
AdmissionState, GetObjectQueueSnapshot, WorkloadAdmissionRegistrySnapshot, WorkloadAdmissionSnapshot,
@@ -145,6 +145,10 @@ impl ConcurrencyManager {
GetObjectGuard::new()
}
pub fn track_put_request() -> PutObjectGuard {
PutObjectGuard::new()
}
/// Get the bytes pool for buffer allocation
///
/// Returns a reference to the BytesPool which can be used to acquire
+7 -1
View File
@@ -47,10 +47,11 @@ pub mod request_guard;
pub use io_schedule::{
IO_PRIORITY_METRICS, IoLoadLevel, IoPriority, IoPriorityMetrics, IoPriorityQueue, IoPriorityQueueConfig, IoQueueStatus,
IoSchedulerConfig, IoStrategy, get_advanced_buffer_size, get_buffer_size_opt_in, get_concurrency_aware_buffer_size,
get_put_concurrency_aware_buffer_size,
};
// Request tracking
pub use request_guard::GetObjectGuard;
pub use request_guard::{GetObjectGuard, PutObjectGuard};
// Concurrency manager
pub use manager::ConcurrencyManager;
@@ -88,6 +89,11 @@ pub fn reset_active_get_requests() {
io_schedule::ACTIVE_GET_REQUESTS.store(0, std::sync::atomic::Ordering::Relaxed);
}
#[allow(dead_code)]
pub fn reset_active_put_requests() {
io_schedule::ACTIVE_PUT_REQUESTS.store(0, std::sync::atomic::Ordering::Relaxed);
}
/// Create a new I/O scheduler with default configuration.
#[allow(dead_code)]
pub fn create_io_scheduler() -> IoScheduler {
@@ -17,8 +17,11 @@
use std::sync::atomic::Ordering;
use std::time::Instant;
use super::io_schedule::ACTIVE_GET_REQUESTS;
use rustfs_io_metrics::{record_get_object_request_result, record_get_object_request_start};
use super::io_schedule::{ACTIVE_GET_REQUESTS, ACTIVE_PUT_REQUESTS};
use rustfs_io_metrics::{
record_get_object_request_result, record_get_object_request_start, record_put_object_request_result,
record_put_object_request_start,
};
/// RAII guard for tracking active GetObject requests.
#[derive(Debug)]
@@ -108,6 +111,66 @@ impl Drop for GetObjectGuard {
}
}
/// RAII guard for tracking active PutObject requests.
#[derive(Debug)]
pub struct PutObjectGuard {
start_time: Instant,
result: Option<&'static str>,
}
impl PutObjectGuard {
pub fn new() -> Self {
ACTIVE_PUT_REQUESTS.fetch_add(1, Ordering::Relaxed);
let concurrent = ACTIVE_PUT_REQUESTS.load(Ordering::Relaxed);
record_put_object_request_start(concurrent);
Self {
start_time: Instant::now(),
result: None,
}
}
pub fn finish_ok(&mut self) {
self.result = Some("ok");
}
pub fn finish_err(&mut self) {
self.result = Some("error");
}
pub fn concurrent_count() -> usize {
ACTIVE_PUT_REQUESTS.load(Ordering::Relaxed)
}
pub fn concurrent_requests() -> usize {
Self::concurrent_count()
}
}
impl Default for PutObjectGuard {
fn default() -> Self {
Self::new()
}
}
impl Drop for PutObjectGuard {
fn drop(&mut self) {
let duration_secs = self.start_time.elapsed().as_secs_f64();
let status = self.result.unwrap_or("unknown");
record_put_object_request_result(status, duration_secs);
if let Err(previous) =
ACTIVE_PUT_REQUESTS.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| current.checked_sub(1))
{
debug_assert_eq!(
previous, 0,
"ACTIVE_PUT_REQUESTS underflow attempt in PutObjectGuard::drop; previous value = {}",
previous
);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -128,4 +191,14 @@ mod tests {
std::thread::sleep(std::time::Duration::from_millis(10));
assert!(guard.elapsed().as_millis() >= 10);
}
#[test]
fn test_put_guard_increments_counter() {
let initial = PutObjectGuard::concurrent_count();
{
let _guard = PutObjectGuard::new();
assert_eq!(PutObjectGuard::concurrent_count(), initial + 1);
}
assert_eq!(PutObjectGuard::concurrent_count(), initial);
}
}