mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-06 05:17:42 +00:00
feat(perf): add large PUT tuning and encode optimization (#3816)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user