refactor(storage): remove object cache plumbing (#2422)

Co-authored-by: heihutu <heihutu@gmail.com>
This commit is contained in:
houseme
2026-04-07 22:00:53 +08:00
committed by GitHub
parent e3000f16e0
commit 79ffecbf14
22 changed files with 188 additions and 5011 deletions
-2
View File
@@ -124,7 +124,6 @@ serde_urlencoded = { workspace = true }
# Cryptography and Security
rustls = { workspace = true }
subtle = { workspace = true }
chrono = { workspace = true }
jiff = { workspace = true }
time = { workspace = true, features = ["parsing", "formatting", "serde"] }
@@ -142,7 +141,6 @@ hex-simd.workspace = true
matchit = { workspace = true }
md5.workspace = true
mime_guess = { workspace = true }
moka = { workspace = true }
percent-encoding = { workspace = true }
pin-project-lite.workspace = true
rust-embed = { workspace = true, features = ["interpolate-folder-path"] }
-13
View File
@@ -18,14 +18,12 @@ use crate::app::context::{AppContext, get_global_app_context};
use crate::app::object_usecase::{build_put_like_object_lock_metadata, validate_existing_object_lock_for_write};
use crate::error::ApiError;
use crate::storage::access::has_bypass_governance_header;
use crate::storage::concurrency::get_concurrency_manager;
use crate::storage::entity;
use crate::storage::helper::OperationHelper;
use crate::storage::options::{
copy_src_opts, extract_metadata, get_complete_multipart_upload_opts, get_content_sha256_with_query, get_opts,
parse_copy_source_range, put_opts, validate_archive_content_encoding,
};
use crate::storage::request_context::spawn_traced;
use crate::storage::s3_api::multipart::build_list_parts_output;
use crate::storage::*;
use bytes::Bytes;
@@ -398,24 +396,13 @@ impl DefaultMultipartUsecase {
maybe_enqueue_transition_immediate(&obj_info, LcEventSrc::S3CompleteMultipartUpload).await;
// Invalidate cache for the completed multipart object
let manager = get_concurrency_manager();
let mpu_bucket = bucket.clone();
let mpu_key = key.clone();
let raw_mpu_version = obj_info.version_id.map(|v| v.to_string());
let mpu_version = if BucketVersioningSys::prefix_enabled(&bucket, &key).await {
raw_mpu_version.clone()
} else {
None
};
let mpu_version_clone = mpu_version.clone();
let mpu_version_for_event = mpu_version.clone();
spawn_traced(async move {
manager
.invalidate_cache_versioned(&mpu_bucket, &mpu_key, mpu_version_clone.as_deref())
.await;
});
info!(
"TDD: Creating output with SSE: {:?}, KMS Key: {:?}",
server_side_encryption, ssekms_key_id
+1 -52
View File
@@ -31,7 +31,7 @@ use crate::capacity::capacity_manager::get_capacity_manager;
use crate::config::RustFSBufferConfig;
use crate::error::ApiError;
use crate::storage::access::{PostObjectRequestMarker, authorize_request, has_bypass_governance_header, req_info_mut};
use crate::storage::concurrency::{CachedGetObject, ConcurrencyManager, GetObjectGuard, get_concurrency_manager};
use crate::storage::concurrency::{GetObjectGuard, get_concurrency_manager};
use crate::storage::ecfs::*;
use crate::storage::head_prefix::{head_prefix_not_found_message, probe_prefix_has_children};
use crate::storage::helper::OperationHelper;
@@ -599,13 +599,6 @@ impl DefaultObjectUsecase {
}
}
fn spawn_cache_invalidation(bucket: String, key: String, version_id: Option<String>) {
let manager = get_concurrency_manager();
crate::storage::request_context::spawn_traced(async move {
manager.invalidate_cache_versioned(&bucket, &key, version_id.as_deref()).await;
});
}
#[instrument(level = "debug", skip(self, _fs, req))]
pub async fn execute_put_object(&self, _fs: &FS, req: S3Request<PutObjectInput>) -> S3Result<S3Response<PutObjectOutput>> {
if let Some(context) = &self.context {
@@ -987,18 +980,6 @@ impl DefaultObjectUsecase {
}
};
let manager = get_concurrency_manager();
let version_id = req.input.version_id.clone();
let cache_key = ConcurrencyManager::make_cache_key(&bucket, &object, version_id.clone().as_deref());
let cache_bucket = bucket.clone();
let cache_object = object.clone();
crate::storage::request_context::spawn_traced(async move {
manager
.invalidate_cache_versioned(&cache_bucket, &cache_object, version_id.as_deref())
.await;
debug!("Cache invalidated for tagged object: {}", cache_key);
});
counter!("rustfs.put_object_tagging.success").increment(1);
let event_version_id = req
@@ -1793,7 +1774,6 @@ impl DefaultObjectUsecase {
}
let raw_dest_version = oi.version_id.map(|v| v.to_string());
Self::spawn_cache_invalidation(bucket.clone(), key.clone(), raw_dest_version.clone());
let dest_version = if BucketVersioningSys::prefix_enabled(&bucket, &key).await {
raw_dest_version
} else {
@@ -1995,21 +1975,6 @@ impl DefaultObjectUsecase {
)
.await;
let manager = get_concurrency_manager();
let bucket_clone = bucket.clone();
let deleted_objects = dobjs.clone();
crate::storage::request_context::spawn_traced(async move {
for dobj in deleted_objects {
manager
.invalidate_cache_versioned(
&bucket_clone,
&dobj.object_name,
dobj.version_id.map(|v| v.to_string()).as_deref(),
)
.await;
}
});
if is_all_buckets_not_found(
&errs
.iter()
@@ -2267,8 +2232,6 @@ impl DefaultObjectUsecase {
// Fast in-memory update for immediate quota consistency
rustfs_ecstore::data_usage::decrement_bucket_usage_memory(&bucket, obj_info.size as u64).await;
Self::spawn_cache_invalidation(bucket.clone(), key.clone(), obj_info.version_id.map(|v| v.to_string()));
if obj_info.name.is_empty() {
if replicate_force_delete {
schedule_replication_delete(DeletedObjectReplicationInfo {
@@ -2395,20 +2358,6 @@ impl DefaultObjectUsecase {
}
};
let manager = get_concurrency_manager();
let version_id_clone = version_id.clone();
let cache_bucket = bucket.clone();
let cache_object = object.clone();
crate::storage::request_context::spawn_traced(async move {
manager
.invalidate_cache_versioned(&cache_bucket, &cache_object, version_id_clone.as_deref())
.await;
debug!(
"Cache invalidated for deleted tagged object: bucket={}, object={}, version_id={:?}",
cache_bucket, cache_object, version_id_clone
);
});
counter!("rustfs.delete_object_tagging.success").increment(1);
let event_version_id = version_id
+93 -252
View File
@@ -15,14 +15,12 @@
use super::get_object_flow::GetObjectBootstrap;
use super::*;
use crate::app::context::NotifyInterface;
use crate::storage::concurrency::{self, get_buffer_size_opt_in};
use crate::storage::concurrency::{self, ConcurrencyManager, get_buffer_size_opt_in};
use hashbrown::HashMap;
use rustfs_object_io::get::{
CachedGetObjectSource as ObjectIoCachedGetObjectSource, GetObjectBodyPlan as ObjectIoGetObjectBodyPlan,
GetObjectCacheWriteback, GetObjectDataPlaneMetricContract as ObjectIoGetObjectDataPlaneMetricContract, GetObjectFlowResult,
GetObjectResponseMode, MaterializeGetObjectBodyError as ObjectIoMaterializeGetObjectBodyError,
build_cached_get_object_flow_result_from_source as object_io_build_cached_get_object_flow_result_from_source,
finalize_get_object_cache_writeback as object_io_finalize_get_object_cache_writeback,
GetObjectBodyPlan as ObjectIoGetObjectBodyPlan, GetObjectBodyPlanningInputs as ObjectIoGetObjectBodyPlanningInputs,
GetObjectDataPlaneMetricContract as ObjectIoGetObjectDataPlaneMetricContract, GetObjectFlowResult,
MaterializeGetObjectBodyError as ObjectIoMaterializeGetObjectBodyError,
materialize_get_object_body as object_io_materialize_get_object_body, plan_get_object_body as object_io_plan_get_object_body,
plan_get_object_strategy_layout as object_io_plan_get_object_strategy_layout,
};
@@ -69,7 +67,6 @@ pub(super) async fn prepare_get_object_request_context(req: &S3Request<GetObject
.map_err(ApiError::from)?;
Ok(GetObjectRequestContext {
cache_key: ConcurrencyManager::make_cache_key(&bucket, &key, version_id.as_deref()),
version_id_for_event: version_id.unwrap_or_default(),
bucket,
key,
@@ -83,92 +80,6 @@ pub(super) async fn prepare_get_object_request_context(req: &S3Request<GetObject
})
}
impl ObjectIoCachedGetObjectSource for CachedGetObject {
fn body(&self) -> &std::sync::Arc<bytes::Bytes> {
&self.body
}
fn content_length(&self) -> i64 {
self.content_length
}
fn content_type(&self) -> Option<&str> {
self.content_type.as_deref()
}
fn e_tag(&self) -> Option<&str> {
self.e_tag.as_deref()
}
fn last_modified(&self) -> Option<&str> {
self.last_modified.as_deref()
}
fn expires(&self) -> Option<&str> {
self.expires.as_deref()
}
fn cache_control(&self) -> Option<&str> {
self.cache_control.as_deref()
}
fn content_disposition(&self) -> Option<&str> {
self.content_disposition.as_deref()
}
fn content_encoding(&self) -> Option<&str> {
self.content_encoding.as_deref()
}
fn content_language(&self) -> Option<&str> {
self.content_language.as_deref()
}
fn storage_class(&self) -> Option<&str> {
self.storage_class.as_deref()
}
fn version_id(&self) -> Option<&str> {
self.version_id.as_deref()
}
fn delete_marker(&self) -> bool {
self.delete_marker
}
fn tag_count(&self) -> Option<i32> {
self.tag_count
}
fn user_metadata(&self) -> &std::collections::HashMap<String, String> {
&self.user_metadata
}
fn checksum_crc32(&self) -> Option<&str> {
self.checksum_crc32.as_deref()
}
fn checksum_crc32c(&self) -> Option<&str> {
self.checksum_crc32c.as_deref()
}
fn checksum_sha1(&self) -> Option<&str> {
self.checksum_sha1.as_deref()
}
fn checksum_sha256(&self) -> Option<&str> {
self.checksum_sha256.as_deref()
}
fn checksum_crc64nvme(&self) -> Option<&str> {
self.checksum_crc64nvme.as_deref()
}
fn checksum_type(&self) -> Option<&ChecksumType> {
self.checksum_type.as_ref()
}
}
pub(super) fn init_get_object_bootstrap(bucket: &str, key: &str, request_id: &str) -> S3Result<GetObjectBootstrap> {
let timeout_config = TimeoutConfig::from_env();
let wrapper = RequestTimeoutWrapper::with_request_id(timeout_config.clone(), request_id.to_string());
@@ -204,108 +115,32 @@ pub(super) fn init_get_object_bootstrap(bucket: &str, key: &str, request_id: &st
request_start,
request_guard,
_deadlock_request_guard: deadlock_request_guard,
concurrent_requests,
})
}
#[allow(clippy::too_many_arguments)]
pub(super) async fn maybe_get_cached_get_object_flow_result(
manager: &ConcurrencyManager,
bucket: &str,
key: &str,
cache_key: &str,
version_id_for_event: String,
part_number: Option<usize>,
rs: Option<&HTTPRangeSpec>,
request_start: std::time::Instant,
) -> Option<GetObjectFlowResult> {
if !manager.is_cache_enabled() || part_number.is_some() || rs.is_some() {
return None;
}
let cached = manager.get_cached_object(cache_key).await?;
let cache_serve_duration = request_start.elapsed();
let metric_contract = ObjectIoGetObjectDataPlaneMetricContract::cache_served();
debug!("Serving object from response cache: {} (latency: {:?})", cache_key, cache_serve_duration);
if metric_contract.record_cache_served_metric {
rustfs_io_metrics::record_get_object_cache_served(cache_serve_duration.as_secs_f64(), cached.body.len());
}
rustfs_io_metrics::record_io_path_selected("get", metric_contract.io_path);
rustfs_io_metrics::record_io_copy_mode("get", metric_contract.copy_mode, cached.body.len());
manager.record_transfer(cached.content_length as u64, Duration::from_micros(1));
rustfs_io_metrics::record_get_object(request_start.elapsed().as_millis() as f64, cached.content_length, true);
Some(object_io_build_cached_get_object_flow_result_from_source(
bucket,
key,
cached.as_ref(),
version_id_for_event,
))
}
pub(super) struct GetObjectBodyAdapterOutput {
pub(super) body: Option<StreamingBlob>,
pub(super) body_plan: ObjectIoGetObjectBodyPlan,
pub(super) cache_writeback: Option<GetObjectCacheWriteback>,
}
pub(super) fn spawn_get_object_cache_writeback(
cache_key: &str,
writeback: GetObjectCacheWriteback,
metric_contract: ObjectIoGetObjectDataPlaneMetricContract,
) {
debug_assert_eq!(
metric_contract.request_source,
rustfs_object_io::get::GetObjectDataPlaneRequestSource::Disk
);
debug_assert!(!metric_contract.record_cache_served_metric);
debug_assert!(metric_contract.record_cache_writeback_metric);
let cached_response = CachedGetObject::from_get_object_cache_writeback(writeback);
let cache_key_clone = cache_key.to_string();
crate::storage::request_context::spawn_traced(async move {
let manager = get_concurrency_manager();
manager.put_cached_object(cache_key_clone.clone(), cached_response).await;
debug!("Object cached successfully with metadata: {}", cache_key_clone);
});
if metric_contract.record_cache_writeback_metric {
rustfs_io_metrics::record_object_cache_writeback();
}
}
pub(super) async fn build_get_object_body_adapter<R>(
final_stream: R,
info: &ObjectInfo,
cache_key: &str,
bucket: &str,
key: &str,
response_content_length: i64,
optimal_buffer_size: usize,
cache_eligibility: rustfs_concurrency::GetObjectCacheEligibility,
) -> S3Result<GetObjectBodyAdapterOutput>
planning_inputs: ObjectIoGetObjectBodyPlanningInputs,
) -> S3Result<Option<StreamingBlob>>
where
R: AsyncRead + Send + Sync + Unpin + 'static,
{
let body_plan = object_io_plan_get_object_body(cache_eligibility, rustfs_config::DEFAULT_OBJECT_SEEK_SUPPORT_THRESHOLD);
let body_plan = object_io_plan_get_object_body(planning_inputs, rustfs_config::DEFAULT_OBJECT_SEEK_SUPPORT_THRESHOLD);
match body_plan {
ObjectIoGetObjectBodyPlan::CacheWriteback => {
debug!(
"Reading object into memory for caching: key={} size={}",
cache_key, response_content_length
);
}
ObjectIoGetObjectBodyPlan::BufferSeekable => {
debug!(
"Reading small object into memory for seek support: key={} size={}",
cache_key, response_content_length
bucket = %bucket,
key = %key,
size = response_content_length,
"reading object into memory for seek support"
);
}
ObjectIoGetObjectBodyPlan::Stream if cache_eligibility.encryption_applied => {
ObjectIoGetObjectBodyPlan::Stream if planning_inputs.encryption_applied => {
info!(
"Encrypted object: Using unlimited stream for decryption with buffer size {}",
optimal_buffer_size
@@ -315,76 +150,94 @@ where
}
let materialized =
object_io_materialize_get_object_body(final_stream, info, body_plan, response_content_length, optimal_buffer_size)
object_io_materialize_get_object_body(final_stream, body_plan, response_content_length, optimal_buffer_size)
.await
.map_err(|err| match err {
ObjectIoMaterializeGetObjectBodyError::CacheRead(err) => {
error!("Failed to read object into memory for caching: {}", err);
ApiError::from(StorageError::other(format!("Failed to read object for caching: {err}")))
}
ObjectIoMaterializeGetObjectBodyError::EncryptedRead(err) => {
error!("Failed to read decrypted object into memory: {}", err);
ApiError::from(StorageError::other(format!("Failed to read decrypted object: {err}")))
}
})?;
Ok(GetObjectBodyAdapterOutput {
body: materialized.body,
body_plan: materialized.plan,
cache_writeback: materialized.cache_writeback.map(|writeback| {
object_io_finalize_get_object_cache_writeback(
info,
writeback,
filter_object_metadata(&info.user_defined).unwrap_or_default(),
)
}),
})
Ok(materialized.body)
}
pub(super) fn finalize_get_object_completion(
cache_key: &str,
wrapper: &RequestTimeoutWrapper,
timeout_config: &TimeoutConfig,
total_duration: Duration,
response_content_length: i64,
optimal_buffer_size: usize,
metric_contract: ObjectIoGetObjectDataPlaneMetricContract,
) {
pub(super) struct GetObjectCompletionInputs<'a> {
pub(super) bucket: &'a str,
pub(super) key: &'a str,
pub(super) wrapper: &'a RequestTimeoutWrapper,
pub(super) timeout_config: &'a TimeoutConfig,
pub(super) total_duration: Duration,
pub(super) response_content_length: i64,
pub(super) optimal_buffer_size: usize,
pub(super) metric_contract: ObjectIoGetObjectDataPlaneMetricContract,
}
pub(super) struct GetObjectStrategyRuntimeInputs<'a> {
pub(super) base_buffer_size: usize,
pub(super) manager: &'a ConcurrencyManager,
pub(super) bucket: &'a str,
pub(super) key: &'a str,
pub(super) info: &'a ObjectInfo,
pub(super) rs: Option<&'a HTTPRangeSpec>,
pub(super) response_content_length: i64,
pub(super) permit_wait_duration: Duration,
pub(super) queue_utilization: f64,
pub(super) queue_status: &'a concurrency::IoQueueStatus,
}
pub(super) fn finalize_get_object_completion(inputs: GetObjectCompletionInputs<'_>) {
let GetObjectCompletionInputs {
bucket,
key,
wrapper,
timeout_config,
total_duration,
response_content_length,
optimal_buffer_size,
metric_contract,
} = inputs;
rustfs_io_metrics::record_get_object_completion(total_duration.as_secs_f64(), response_content_length, optimal_buffer_size);
rustfs_io_metrics::record_get_object(total_duration.as_millis() as f64, response_content_length, false);
rustfs_io_metrics::record_get_object(total_duration.as_millis() as f64, response_content_length);
rustfs_io_metrics::record_io_copy_mode("get", metric_contract.copy_mode, response_content_length.max(0) as usize);
if wrapper.is_timeout() {
warn!(
"GetObject request exceeded timeout: key={} duration={:?} timeout={:?}",
cache_key,
wrapper.elapsed(),
timeout_config.get_object_timeout
bucket = %bucket,
key = %key,
elapsed = ?wrapper.elapsed(),
timeout = ?timeout_config.get_object_timeout,
"GetObject request exceeded timeout"
);
rustfs_io_metrics::record_get_object_timeout(None, Some(wrapper.elapsed().as_secs_f64()));
}
debug!(
"GetObject completed: key={} size={} duration={:?} buffer={}",
cache_key, response_content_length, total_duration, optimal_buffer_size
bucket = %bucket,
key = %key,
size = response_content_length,
duration = ?total_duration,
buffer = optimal_buffer_size,
"GetObject completed"
);
}
#[allow(clippy::too_many_arguments)]
pub(super) fn finalize_get_object_strategy_runtime(
base_buffer_size: usize,
manager: &ConcurrencyManager,
bucket: &str,
key: &str,
info: &ObjectInfo,
rs: Option<&HTTPRangeSpec>,
response_content_length: i64,
permit_wait_duration: Duration,
queue_utilization: f64,
queue_status: &concurrency::IoQueueStatus,
concurrent_requests: usize,
) -> (concurrency::IoStrategy, usize) {
pub(super) fn finalize_get_object_strategy_runtime(inputs: GetObjectStrategyRuntimeInputs<'_>) -> usize {
let GetObjectStrategyRuntimeInputs {
base_buffer_size,
manager,
bucket,
key,
info,
rs,
response_content_length,
permit_wait_duration,
queue_utilization,
queue_status,
} = inputs;
let strategy_layout = object_io_plan_get_object_strategy_layout(
rs,
response_content_length,
@@ -415,7 +268,6 @@ pub(super) fn finalize_get_object_strategy_runtime(
buffer_size = io_strategy.buffer_size,
buffer_multiplier = io_strategy.buffer_multiplier,
readahead = io_strategy.enable_readahead,
cache_wb = io_strategy.cache_writeback_enabled,
storage_media = ?io_strategy.storage_media,
access_pattern = ?io_strategy.access_pattern,
bandwidth_tier = ?io_strategy.bandwidth_tier,
@@ -447,7 +299,6 @@ pub(super) fn finalize_get_object_strategy_runtime(
io_strategy.load_level.as_str(),
io_strategy.buffer_multiplier,
);
rustfs_io_metrics::record_io_priority_assignment(io_priority.as_str());
let strategy_layout = object_io_plan_get_object_strategy_layout(
rs,
@@ -467,11 +318,11 @@ pub(super) fn finalize_get_object_strategy_runtime(
response_content_length,
get_buffer_size_opt_in(response_content_length),
strategy_layout.optimal_buffer_size,
concurrent_requests,
io_strategy.concurrent_requests,
io_strategy.load_level
);
(io_strategy, strategy_layout.optimal_buffer_size)
strategy_layout.optimal_buffer_size
}
pub(super) fn prepare_put_object_request_context(req: &S3Request<PutObjectInput>) -> PutObjectRequestContext {
@@ -525,29 +376,19 @@ pub(super) async fn complete_get_flow_result(
request_context: &GetObjectRequestContext,
flow_result: GetObjectFlowResult,
) -> S3Result<S3Response<GetObjectOutput>> {
match flow_result.response_mode {
GetObjectResponseMode::Plain => {
let helper = bind_helper_object(helper, flow_result.event_info, Some(flow_result.version_id_for_event));
let result = Ok(S3Response::new(flow_result.output));
let _ = helper.complete(&result);
result
}
GetObjectResponseMode::CorsWrapped => {
let helper = helper
.object(flow_result.event_info)
.version_id(flow_result.version_id_for_event);
let response = wrap_response_with_cors(
&request_context.bucket,
&request_context.method,
&request_context.headers,
flow_result.output,
)
.await;
let result = Ok(response);
let _ = helper.complete(&result);
result
}
}
let helper = helper
.object(flow_result.event_info)
.version_id(flow_result.version_id_for_event);
let response = wrap_response_with_cors(
&request_context.bucket,
&request_context.method,
&request_context.headers,
flow_result.output,
)
.await;
let result = Ok(response);
let _ = helper.complete(&result);
result
}
pub(super) fn complete_put_response(helper: OperationHelper, output: PutObjectOutput) -> S3Result<S3Response<PutObjectOutput>> {
@@ -14,8 +14,8 @@
use super::DeadlockRequestGuard;
use super::app_adapters::{
bucket_prefix_versioning_enabled, build_get_object_body_adapter, finalize_get_object_completion,
finalize_get_object_strategy_runtime, maybe_get_cached_get_object_flow_result, spawn_get_object_cache_writeback,
GetObjectCompletionInputs, GetObjectStrategyRuntimeInputs, bucket_prefix_versioning_enabled, build_get_object_body_adapter,
finalize_get_object_completion, finalize_get_object_strategy_runtime,
};
use super::get_object_zero_copy::{GetObjectPreparedRead, prepare_get_object_read_execution};
use super::types::GetObjectRequestContext;
@@ -25,7 +25,7 @@ use crate::storage::options::filter_object_metadata;
use crate::storage::timeout_wrapper::{RequestTimeoutWrapper, TimeoutConfig};
use rustfs_ecstore::store_api::{HTTPRangeSpec, ObjectInfo};
use rustfs_object_io::get::{
GetObjectBodyPlan as ObjectIoGetObjectBodyPlan, GetObjectBodySource,
GetObjectBodyPlanningInputs as ObjectIoGetObjectBodyPlanningInputs, GetObjectBodySource,
GetObjectDataPlaneMetricContract as ObjectIoGetObjectDataPlaneMetricContract, GetObjectFlowResult, GetObjectOutputContext,
GetObjectReadSetup, build_chunk_blob as object_io_build_chunk_blob,
build_cors_wrapped_get_object_flow_result as object_io_build_cors_wrapped_get_object_flow_result,
@@ -43,7 +43,6 @@ pub(super) struct GetObjectBootstrap {
pub(super) request_start: std::time::Instant,
pub(super) request_guard: GetObjectGuard,
pub(super) _deadlock_request_guard: DeadlockRequestGuard,
pub(super) concurrent_requests: usize,
}
#[derive(Clone, Copy)]
@@ -56,7 +55,6 @@ pub(super) struct GetObjectFlowRuntime<'a> {
#[allow(clippy::too_many_arguments)]
pub(super) async fn build_get_object_output_context(
request_context: &GetObjectRequestContext,
cache_key: &str,
manager: &ConcurrencyManager,
bucket: &str,
key: &str,
@@ -76,53 +74,45 @@ pub(super) async fn build_get_object_output_context(
permit_wait_duration: Duration,
queue_utilization: f64,
queue_status: &concurrency::IoQueueStatus,
concurrent_requests: usize,
base_buffer_size: usize,
part_number: Option<usize>,
versioned: bool,
) -> S3Result<(GetObjectOutputContext, ObjectIoGetObjectDataPlaneMetricContract)> {
let (io_strategy, optimal_buffer_size) = finalize_get_object_strategy_runtime(
let optimal_buffer_size = finalize_get_object_strategy_runtime(GetObjectStrategyRuntimeInputs {
base_buffer_size,
manager,
bucket,
key,
&info,
rs.as_ref(),
info: &info,
rs: rs.as_ref(),
response_content_length,
permit_wait_duration,
queue_utilization,
queue_status,
concurrent_requests,
);
});
let (body, metric_contract) = match body_source {
GetObjectBodySource::Reader(final_stream) => {
let cache_eligibility = manager.get_object_cache_eligibility(
io_strategy.cache_writeback_enabled,
part_number.is_some(),
rs.is_some(),
encryption_applied,
response_content_length,
);
let adapter_output = build_get_object_body_adapter(
let body = build_get_object_body_adapter(
final_stream,
&info,
cache_key,
bucket,
key,
response_content_length,
optimal_buffer_size,
cache_eligibility,
ObjectIoGetObjectBodyPlanningInputs {
is_part_request: part_number.is_some(),
is_range_request: rs.is_some(),
encryption_applied,
response_size: response_content_length,
},
)
.await?;
let metric_contract = ObjectIoGetObjectDataPlaneMetricContract::disk(
rustfs_io_metrics::IoPath::Legacy,
rustfs_io_metrics::CopyMode::SingleCopy,
adapter_output.body_plan,
);
if let Some(writeback) = adapter_output.cache_writeback {
spawn_get_object_cache_writeback(cache_key, writeback, metric_contract);
}
(adapter_output.body, metric_contract)
(body, metric_contract)
}
GetObjectBodySource::Chunk {
stream: chunk_stream,
@@ -132,7 +122,7 @@ pub(super) async fn build_get_object_output_context(
let (io_path, copy_mode) = object_io_chunk_body_data_plane_labels(path, copy_mode);
(
object_io_build_chunk_blob(chunk_stream),
ObjectIoGetObjectDataPlaneMetricContract::disk(io_path, copy_mode, ObjectIoGetObjectBodyPlan::Stream),
ObjectIoGetObjectDataPlaneMetricContract::disk(io_path, copy_mode),
)
}
};
@@ -176,30 +166,13 @@ pub(super) async fn run_get_object_flow(
let timeout_config = &bootstrap.timeout_config;
let wrapper = &bootstrap.wrapper;
let request_start = bootstrap.request_start;
let concurrent_requests = bootstrap.concurrent_requests;
let bucket = request_context.bucket.clone();
let key = request_context.key.clone();
let cache_key = request_context.cache_key.clone();
let version_id_for_event = request_context.version_id_for_event.clone();
let part_number = request_context.part_number;
let rs = request_context.rs.clone();
let opts = request_context.opts.clone();
if let Some(cached_result) = maybe_get_cached_get_object_flow_result(
manager,
&bucket,
&key,
&cache_key,
version_id_for_event.clone(),
part_number,
rs.as_ref(),
request_start,
)
.await
{
return Ok(cached_result);
}
let prepared_read = prepare_get_object_read_execution(
&request_context,
manager,
@@ -236,7 +209,6 @@ pub(super) async fn run_get_object_flow(
let versioned = bucket_prefix_versioning_enabled(&bucket, &key).await;
let (output_context, metric_contract) = build_get_object_output_context(
&request_context,
&cache_key,
manager,
&bucket,
&key,
@@ -256,7 +228,6 @@ pub(super) async fn run_get_object_flow(
permit_wait_duration,
queue_utilization,
&queue_status,
concurrent_requests,
base_buffer_size,
part_number,
versioned,
@@ -266,15 +237,16 @@ pub(super) async fn run_get_object_flow(
let optimal_buffer_size = output_context.optimal_buffer_size;
let total_duration = request_start.elapsed();
finalize_get_object_completion(
&cache_key,
finalize_get_object_completion(GetObjectCompletionInputs {
bucket: &bucket,
key: &key,
wrapper,
timeout_config,
total_duration,
response_content_length,
optimal_buffer_size,
metric_contract,
);
});
Ok(object_io_build_cors_wrapped_get_object_flow_result(output_context, version_id_for_event))
}
@@ -312,13 +312,6 @@ impl DefaultObjectUsecase {
}
};
let manager = get_concurrency_manager();
let fpath_clone = fpath.clone();
let bucket_clone = bucket.clone();
crate::storage::request_context::spawn_traced(async move {
manager.invalidate_cache_versioned(&bucket_clone, &fpath_clone, None).await;
});
let e_tag = obj_info.etag.clone().map(|etag| to_s3s_etag(&etag));
let output = PutObjectOutput {
@@ -625,8 +625,6 @@ impl DefaultObjectUsecase {
let raw_version = obj_info.version_id.map(|v| v.to_string());
Self::spawn_cache_invalidation(bucket.clone(), key.clone(), raw_version.clone());
let put_version = if bucket_prefix_versioning_enabled(&bucket, &key).await {
raw_version.clone()
} else {
-1
View File
@@ -18,7 +18,6 @@ use super::*;
pub(super) struct GetObjectRequestContext {
pub(super) bucket: String,
pub(super) key: String,
pub(super) cache_key: String,
pub(super) version_id_for_event: String,
pub(super) part_number: Option<usize>,
pub(super) rs: Option<HTTPRangeSpec>,
@@ -13,6 +13,7 @@
// limitations under the License.
use super::*;
use crate::storage::concurrency::ConcurrencyManager;
use futures::StreamExt;
use http::{Extensions, HeaderMap, Method, Uri};
use rustfs_ecstore::store_api::GetObjectChunkPath;
+2 -43
View File
@@ -458,8 +458,6 @@ pub struct IoStrategyCore {
pub buffer_multiplier: f64,
/// Whether sequential read-ahead should be enabled
pub enable_readahead: bool,
/// Whether cache writeback should be enabled
pub cache_writeback_enabled: bool,
/// Whether tokio BufReader should be used
pub use_buffered_io: bool,
@@ -491,7 +489,6 @@ pub struct IoStrategyCore {
pub should_expand_for_sequential: bool,
pub should_reduce_for_concurrency: bool,
pub should_reduce_for_bandwidth: bool,
pub should_disable_cache_writeback: bool,
pub should_disable_readahead: bool,
// ===== Priority Scheduling =====
@@ -515,7 +512,6 @@ impl IoStrategyCore {
buffer_size,
buffer_multiplier: 1.0,
enable_readahead: false,
cache_writeback_enabled: true,
use_buffered_io: true,
concurrent_requests: 1,
observed_bandwidth_bps: None,
@@ -536,7 +532,6 @@ impl IoStrategyCore {
should_expand_for_sequential: false,
should_reduce_for_concurrency: false,
should_reduce_for_bandwidth: false,
should_disable_cache_writeback: false,
should_disable_readahead: false,
priority_enabled: false,
priority: IoPriority::Normal,
@@ -600,11 +595,6 @@ pub struct IoStrategyDebugInfo {
pub readahead_disabled_by_load: bool,
pub readahead_disabled_by_bandwidth: bool,
// ===== Cache Writeback Decisions =====
pub cache_writeback_disabled_by_load: bool,
pub cache_writeback_disabled_by_pattern: bool,
pub cache_writeback_disabled_by_request_size: bool,
// ===== Threshold Snapshots =====
pub final_buffer_floor: usize,
pub queue_depth_hint: usize,
@@ -656,7 +646,6 @@ pub struct IoStrategyDebugInfo {
/// // Apply strategy to I/O operations
/// let buffer_size = strategy.buffer_size;
/// let enable_readahead = strategy.enable_readahead;
/// let enable_cache_writeback = strategy.cache_writeback_enabled;
/// ```
#[derive(Debug, Clone, PartialEq)]
pub struct IoStrategy {
@@ -718,11 +707,6 @@ impl IoStrategy {
IoLoadLevel::High | IoLoadLevel::Critical => false,
};
let cache_writeback_enabled = match load_level {
IoLoadLevel::Low | IoLoadLevel::Medium | IoLoadLevel::High => true,
IoLoadLevel::Critical => false, // Disable under extreme load
};
// Build minimal scheduling context for compatibility path
let scheduling_context = IoSchedulingContext::from_wait_duration(permit_wait_duration, base_buffer_size);
#[cfg(feature = "io-scheduler-debug")]
@@ -740,7 +724,6 @@ impl IoStrategy {
buffer_size,
buffer_multiplier,
enable_readahead,
cache_writeback_enabled,
use_buffered_io: true,
// Performance state
@@ -767,7 +750,6 @@ impl IoStrategy {
should_expand_for_sequential: false,
should_reduce_for_concurrency: false,
should_reduce_for_bandwidth: false,
should_disable_cache_writeback: !cache_writeback_enabled,
should_disable_readahead: !enable_readahead,
// Priority scheduling
@@ -810,9 +792,6 @@ impl IoStrategy {
readahead_disabled_by_pattern: false,
readahead_disabled_by_load: !enable_readahead,
readahead_disabled_by_bandwidth: false,
cache_writeback_disabled_by_load: !cache_writeback_enabled,
cache_writeback_disabled_by_pattern: false,
cache_writeback_disabled_by_request_size: false,
final_buffer_floor: 32 * KI_B,
queue_depth_hint: 0,
permit_wait_ms: permit_wait_duration.as_millis() as u64,
@@ -1016,17 +995,6 @@ impl IoStrategy {
let enable_readahead = should_enable_readahead;
// Determine cache writeback
let cache_writeback_enabled = match load_level {
IoLoadLevel::Critical => false,
_ => !bandwidth_limited,
};
#[cfg(feature = "io-scheduler-debug")]
let cache_writeback_disabled_by_load = matches!(load_level, IoLoadLevel::Critical);
#[cfg(feature = "io-scheduler-debug")]
let cache_writeback_disabled_by_pattern = matches!(context.access_pattern, AccessPattern::Random);
// Calculate priority based on request size
let priority = if context.file_size > 0 {
IoPriority::from_size_with_thresholds(
@@ -1052,7 +1020,6 @@ impl IoStrategy {
buffer_size,
buffer_multiplier,
enable_readahead,
cache_writeback_enabled,
use_buffered_io: true,
// ===== Performance State =====
@@ -1074,7 +1041,6 @@ impl IoStrategy {
should_expand_for_sequential: matches!(context.access_pattern, AccessPattern::Sequential),
should_reduce_for_concurrency: concurrency_multiplier < 1.0,
should_reduce_for_bandwidth: bandwidth_limited,
should_disable_cache_writeback: !cache_writeback_enabled,
should_disable_readahead: !enable_readahead,
// ===== Priority Scheduling =====
@@ -1161,11 +1127,6 @@ impl IoStrategy {
readahead_disabled_by_load,
readahead_disabled_by_bandwidth,
// ===== Cache Writeback Decisions =====
cache_writeback_disabled_by_load,
cache_writeback_disabled_by_pattern,
cache_writeback_disabled_by_request_size: false,
// ===== Threshold Snapshots =====
final_buffer_floor: clamp_min,
queue_depth_hint: context.concurrent_requests,
@@ -1212,12 +1173,11 @@ impl IoStrategy {
#[allow(dead_code)]
pub fn description(&self) -> String {
format!(
"IoStrategy[{:?}]: buffer={}KB, multiplier={:.2}, readahead={}, cache_wb={}, wait={:?}",
"IoStrategy[{:?}]: buffer={}KB, multiplier={:.2}, readahead={}, wait={:?}",
self.load_level,
self.buffer_size / 1024,
self.buffer_multiplier,
self.enable_readahead,
self.cache_writeback_enabled,
self.permit_wait_duration
)
}
@@ -2151,10 +2111,9 @@ mod tests {
let config = IoSchedulerConfig::default();
let strategy = IoStrategy::from_context_with_config(&context, &config);
// Critical load should disable readahead and cache writeback
// Critical load should disable readahead
assert_eq!(strategy.load_level, IoLoadLevel::Critical);
assert!(!strategy.enable_readahead, "Critical load should disable readahead");
assert!(!strategy.cache_writeback_enabled, "Critical load should disable cache writeback");
// Buffer: 256KB * 0.4 (critical) * 1.35 (sequential) ≈ 138KB
assert!(strategy.buffer_size < 200 * 1024, "Critical load should reduce buffer");
}
+1 -392
View File
@@ -18,9 +18,8 @@ use super::io_schedule::{
IoLoadLevel, IoLoadMetrics, IoPriority, IoPriorityQueue, IoPriorityQueueConfig, IoQueueStatus, IoSchedulerConfig, IoStrategy,
get_advanced_buffer_size,
};
use super::object_cache::{CacheStats, CachedGetObject, TieredObjectCache, WarmupPattern};
use super::request_guard::GetObjectGuard;
use rustfs_concurrency::{GetObjectCacheEligibility, GetObjectQueueSnapshot};
use rustfs_concurrency::GetObjectQueueSnapshot;
use rustfs_config::{KI_B, MI_B};
use rustfs_io_core::BytesPool;
use rustfs_io_core::io_profile::{AccessPattern, IoPatternDetector, StorageMedia, detect_storage_media};
@@ -37,12 +36,8 @@ pub(crate) static CONCURRENCY_MANAGER: LazyLock<ConcurrencyManager> = LazyLock::
#[derive(Clone)]
pub struct ConcurrencyManager {
/// Tiered object cache (L1 + L2) for frequently accessed objects
cache: Arc<TieredObjectCache>,
/// Semaphore to limit concurrent disk reads
disk_read_semaphore: Arc<Semaphore>,
/// Whether object caching is enabled (from RUSTFS_OBJECT_CACHE_ENABLE env var)
cache_enabled: bool,
/// I/O load metrics for adaptive strategy calculation
io_metrics: Arc<Mutex<IoLoadMetrics>>,
/// I/O priority queue for request scheduling
@@ -94,36 +89,17 @@ impl ConcurrencyManager {
/// Create a new concurrency manager with default settings
///
/// Reads configuration from environment variables:
/// - `RUSTFS_OBJECT_CACHE_ENABLE`: Enable/disable object caching (default: true)
/// - `RUSTFS_OBJECT_TIERED_CACHE_ENABLE`: Enable tiered L1+L2 caching (default: true)
/// - `RUSTFS_OBJECT_MAX_CONCURRENT_DISK_READS`: Maximum concurrent disk reads (default: 64)
pub fn new() -> Self {
// Load scheduler configuration once at initialization
let scheduler_config = IoSchedulerConfig::from_env();
let cache_enabled =
rustfs_utils::get_env_bool(rustfs_config::ENV_OBJECT_CACHE_ENABLE, rustfs_config::DEFAULT_OBJECT_CACHE_ENABLE);
let tiered_cache_enabled = rustfs_utils::get_env_bool(
rustfs_config::ENV_OBJECT_TIERED_CACHE_ENABLE,
rustfs_config::DEFAULT_OBJECT_TIERED_CACHE_ENABLE,
);
let max_disk_reads = scheduler_config.max_concurrent_reads;
// Detect storage media
let storage_media =
detect_storage_media(scheduler_config.storage_detection_enabled, &scheduler_config.storage_media_override);
// Create tiered cache configuration
let cache = if tiered_cache_enabled {
Arc::new(TieredObjectCache::new())
} else {
// If tiered cache is disabled, create a simple tiered cache (acts as single-level)
// For now, we always use TieredObjectCache since the configuration is now enabled by default
Arc::new(TieredObjectCache::new())
};
// Initialize I/O pattern detector
let pattern_detector = Arc::new(Mutex::new(IoPatternDetector::new(
scheduler_config.pattern_history_size,
@@ -155,9 +131,7 @@ impl ConcurrencyManager {
};
Self {
cache,
disk_read_semaphore: Arc::new(Semaphore::new(max_disk_reads)),
cache_enabled,
io_metrics: Arc::new(Mutex::new(IoLoadMetrics::new(scheduler_config.load_sample_window))),
priority_queue: Arc::new(IoPriorityQueue::new(queue_config)),
bytes_pool: Arc::new(BytesPool::new_tiered()),
@@ -169,36 +143,11 @@ impl ConcurrencyManager {
}
}
/// Check if object caching is enabled
///
/// Returns true if the `RUSTFS_OBJECT_CACHE_ENABLE` environment variable
/// is set to "true" (case-insensitive). When disabled, cache lookups and
/// writebacks are skipped, reducing memory usage at the cost of repeated
/// disk reads for the same objects.
///
/// # Returns
///
/// `true` if caching is enabled, `false` otherwise
pub fn is_cache_enabled(&self) -> bool {
self.cache_enabled
}
/// Track a GetObject request
pub fn track_request() -> GetObjectGuard {
GetObjectGuard::new()
}
/// Try to get an object from cache
pub async fn get_cached(&self, key: &str) -> Option<Arc<Vec<u8>>> {
self.cache.get_bytes(key).await
}
/// Cache an object for future retrievals
pub async fn cache_object(&self, key: String, data: Vec<u8>) {
let cached_data = Arc::new(data);
self.cache.put_bytes(key, cached_data).await;
}
/// Get the bytes pool for buffer allocation
///
/// Returns a reference to the BytesPool which can be used to acquire
@@ -531,105 +480,6 @@ impl ConcurrencyManager {
&self.scheduler_config
}
/// Get cache statistics
pub async fn cache_stats(&self) -> CacheStats {
self.cache.stats_as_hot_cache().await
}
/// Clear all cached objects
pub async fn clear_cache(&self) {
self.cache.clear().await;
}
/// Reset cache hit/miss metrics counters.
///
/// This is useful for testing to get a clean slate for hit rate calculations.
pub fn reset_cache_metrics(&self) {
self.cache.reset_metrics();
}
/// Check if a key is cached
pub async fn is_cached(&self, key: &str) -> bool {
self.cache.contains(key).await
}
/// Get multiple cached objects in a single operation
pub async fn get_cached_batch(&self, keys: &[String]) -> Vec<Option<Arc<Vec<u8>>>> {
self.cache.get_batch_bytes(keys).await
}
/// Remove a specific object from cache
pub async fn remove_cached(&self, key: &str) -> bool {
self.cache.remove(key).await.is_some()
}
/// Get the most frequently accessed keys
pub async fn get_hot_keys(&self, limit: usize) -> Vec<(String, u64)> {
let keys = self.cache.get_hot_keys(limit).await;
keys.into_iter().map(|(k, v)| (k, v as u64)).collect()
}
/// Get cache hit rate percentage
pub fn cache_hit_rate(&self) -> f64 {
self.cache.hit_rate()
}
/// Warm up cache with frequently accessed objects
///
/// This can be called during server startup or maintenance windows
/// to pre-populate the cache with known hot objects.
pub async fn warm_cache(&self, objects: Vec<(String, Vec<u8>)>) {
if !self.cache_enabled {
debug!("Cache is disabled, skipping warmup");
return;
}
// Cache each object
for (key, data) in objects {
self.cache_object(key, data).await;
}
}
/// Warm up cache with a specific pattern.
///
/// This method supports different warming patterns for more intelligent
/// cache pre-population during server startup or maintenance windows.
///
/// # Arguments
///
/// * `pattern` - The warming pattern to use
///
/// # Returns
///
/// The number of objects successfully warmed
///
/// # Example
///
/// ```ignore
/// // Warm the 100 most recently accessed objects
/// let pattern = WarmupPattern::RecentAccesses { limit: 100 };
/// let warmed = manager.warm_cache_with_pattern(pattern).await;
///
/// // Warm specific keys
/// let keys = vec!["bucket1/key1".to_string(), "bucket1/key2".to_string()];
/// let pattern = WarmupPattern::SpecificKeys(keys);
/// manager.warm_cache_with_pattern(pattern).await;
/// ```
pub async fn warm_cache_with_pattern(&self, pattern: WarmupPattern) -> usize {
if !self.cache_enabled {
debug!("Cache is disabled, skipping warmup");
return 0;
}
debug!("warm_cache_with_pattern called with pattern: {:?}", pattern);
// Delegate to the tiered cache's warm implementation
// Note: This returns the count of keys identified for warming,
// but actual object loading from storage would need to be implemented
// at a higher layer (object_usecase) that has access to storage backends
self.cache.warm(pattern).await
}
/// Get optimized buffer size for a request
///
/// This wraps the advanced buffer sizing logic and makes it accessible
@@ -638,151 +488,6 @@ impl ConcurrencyManager {
get_advanced_buffer_size(file_size, base, sequential)
}
// ============================================
// Response Cache Methods (CachedGetObject)
// ============================================
/// Get a cached GetObject response with full metadata
///
/// This method retrieves a complete GetObject response from the response cache,
/// including body data and all response metadata (e_tag, last_modified, content_type, etc.).
///
/// # Arguments
///
/// * `key` - Cache key in the format "{bucket}/{key}" or "{bucket}/{key}?versionId={version_id}"
///
/// # Returns
///
/// * `Some(Arc<CachedGetObject>)` - Cached response data if found and not expired
/// * `None` - Cache miss
///
/// # Example
///
/// ```ignore
/// let cache_key = format!("{}/{}", bucket, key);
/// if let Some(cached) = manager.get_cached_object(&cache_key).await {
/// // Build response from cached data
/// let output = GetObjectOutput {
/// body: Some(StreamingBlob::from(cached.body.clone())),
/// content_length: Some(cached.content_length),
/// e_tag: cached.e_tag.clone(),
/// last_modified: cached.last_modified.as_ref().map(|s| parse_rfc3339(s)),
/// ..Default::default()
/// };
/// }
/// ```
pub async fn get_cached_object(&self, key: &str) -> Option<Arc<CachedGetObject>> {
self.cache.get_response(key).await
}
/// Cache a complete GetObject response for future retrievals
///
/// This method caches a complete GetObject response including body and all metadata.
/// Objects larger than the maximum cache size (10MB by default) or empty objects
/// are not cached.
///
/// # Arguments
///
/// * `key` - Cache key in the format "{bucket}/{key}" or "{bucket}/{key}?versionId={version_id}"
/// * `response` - The complete cached response to store
///
/// # Example
///
/// ```ignore
/// let cached = CachedGetObject {
/// body: Bytes::from(data),
/// content_length: data.len() as i64,
/// content_type: Some("application/octet-stream".to_string()),
/// e_tag: Some("\"abc123\"".to_string()),
/// last_modified: Some("2024-01-01T00:00:00Z".to_string()),
/// ..Default::default()
/// };
/// manager.put_cached_object(cache_key, cached).await;
/// ```
pub async fn put_cached_object(&self, key: String, response: CachedGetObject) {
self.cache.put_response(key, response).await;
}
/// Invalidate cache entries for a specific object
///
/// This method removes both simple byte cache and response cache entries
/// for the given key. Should be called after write operations (put_object,
/// copy_object, delete_object, etc.) to prevent stale data from being served.
///
/// # Arguments
///
/// * `key` - Cache key to invalidate (e.g., "{bucket}/{key}")
///
/// # Example
///
/// ```ignore
/// // After put_object succeeds
/// let cache_key = format!("{}/{}", bucket, key);
/// manager.invalidate_cache(&cache_key).await;
/// ```
pub async fn invalidate_cache(&self, key: &str) {
self.cache.invalidate(key).await;
}
/// Invalidate cache entries for an object and its latest version
///
/// For versioned buckets, this invalidates both:
/// - The specific version key: "{bucket}/{key}?versionId={version_id}"
/// - The latest version key: "{bucket}/{key}"
///
/// This ensures that after a write/delete, clients don't receive stale data.
/// Should be called after any write operation that modifies object data or creates
/// new versions.
///
/// # Arguments
///
/// * `bucket` - Bucket name
/// * `key` - Object key
/// * `version_id` - Optional version ID (if None, only invalidates the base key)
///
/// # Example
///
/// ```ignore
/// // After delete_object with version
/// manager.invalidate_cache_versioned(&bucket, &key, Some(&version_id)).await;
///
/// // After put_object (invalidates latest)
/// manager.invalidate_cache_versioned(&bucket, &key, None).await;
/// ```
pub async fn invalidate_cache_versioned(&self, bucket: &str, key: &str, version_id: Option<&str>) {
self.cache.invalidate_versioned(bucket, key, version_id).await;
}
/// Generate a cache key for an object
///
/// Creates a cache key in the appropriate format based on whether a version ID
/// is specified. For versioned requests, uses "{bucket}/{key}?versionId={version_id}".
/// For non-versioned requests, uses "{bucket}/{key}".
///
/// # Arguments
///
/// * `bucket` - Bucket name
/// * `key` - Object key
/// * `version_id` - Optional version ID
///
/// # Returns
///
/// Cache key string
pub fn make_cache_key(bucket: &str, key: &str, version_id: Option<&str>) -> String {
match version_id {
Some(vid) => format!("{bucket}/{key}?versionId={vid}"),
None => format!("{bucket}/{key}"),
}
}
/// Get maximum cacheable object size
///
/// Returns the maximum size in bytes for objects that can be cached.
/// Objects larger than this size are not cached to prevent memory exhaustion.
pub fn max_object_size(&self) -> usize {
self.cache.max_object_size()
}
// ============================================
// Priority-Based I/O Scheduling Methods
// ============================================
@@ -868,26 +573,6 @@ impl ConcurrencyManager {
self.disk_read_semaphore.acquire().await
}
/// Build the minimal cache eligibility decision for a GetObject response.
pub fn get_object_cache_eligibility(
&self,
cache_writeback_enabled: bool,
is_part_request: bool,
is_range_request: bool,
encryption_applied: bool,
response_size: i64,
) -> GetObjectCacheEligibility {
GetObjectCacheEligibility {
cache_enabled: self.is_cache_enabled(),
cache_writeback_enabled,
is_part_request,
is_range_request,
encryption_applied,
response_size,
max_cacheable_size: self.max_object_size(),
}
}
/// Get the global concurrency manager instance.
pub fn global() -> &'static Self {
&CONCURRENCY_MANAGER
@@ -907,7 +592,6 @@ impl Default for ConcurrencyManager {
#[cfg(test)]
mod integration_tests {
use super::*;
use bytes::Bytes;
use serial_test::serial;
#[tokio::test]
@@ -926,43 +610,6 @@ mod integration_tests {
assert_eq!(large_priority, IoPriority::Low);
}
#[tokio::test]
#[serial]
async fn test_concurrency_manager_cache_operations() {
let manager = ConcurrencyManager::new();
// Test cache put and get
let obj = CachedGetObject::new(Bytes::from("test data"), 9)
.with_content_type("text/plain".to_string())
.with_e_tag("\"abc123\"".to_string());
manager.put_cached_object("test-key".to_string(), obj).await;
let cached = manager.get_cached_object("test-key").await;
assert!(cached.is_some());
let cached_obj = cached.unwrap();
assert_eq!(cached_obj.content_type, Some("text/plain".to_string()));
assert_eq!(cached_obj.e_tag, Some("\"abc123\"".to_string()));
}
#[tokio::test]
#[serial]
async fn test_concurrency_manager_cache_stats() {
let manager = ConcurrencyManager::new();
// Add some objects
for i in 0..5 {
let obj = CachedGetObject::new(Bytes::from(format!("data{}", i)), 5);
manager.put_cached_object(format!("key{}", i), obj).await;
}
// Get stats
let stats = manager.cache_stats().await;
assert!(stats.entries >= 5);
}
#[tokio::test]
#[serial]
async fn test_concurrency_manager_io_queue_status() {
@@ -1008,44 +655,6 @@ mod integration_tests {
assert_eq!(manager.get_io_priority(50 * 1024 * 1024), IoPriority::Low); // 50MB
}
#[tokio::test]
#[serial]
async fn test_concurrency_manager_cache_invalidation() {
let manager = ConcurrencyManager::new();
// Add an object
let obj = CachedGetObject::new(Bytes::from("test"), 4);
manager.put_cached_object("test-key".to_string(), obj).await;
// Verify it's cached
assert!(manager.is_cached("test-key").await);
// Invalidate
manager.invalidate_cache("test-key").await;
// Should not be cached anymore
assert!(!manager.is_cached("test-key").await);
}
#[tokio::test]
#[serial]
async fn test_concurrency_manager_cache_clear() {
let manager = ConcurrencyManager::new();
// Add multiple objects
for i in 0..10 {
let obj = CachedGetObject::new(Bytes::from(format!("data{}", i)), 5);
manager.put_cached_object(format!("key{}", i), obj).await;
}
// Clear cache
manager.clear_cache().await;
// Verify all are removed
let stats = manager.cache_stats().await;
assert_eq!(stats.entries, 0);
}
#[tokio::test]
#[serial]
async fn test_concurrency_manager_io_strategy() {
+1 -7
View File
@@ -14,14 +14,13 @@
//! Concurrency optimization module for high-performance object retrieval.
//!
//! This module provides concurrency management, I/O scheduling, and object caching
//! This module provides concurrency management and I/O scheduling
//! for high-performance object retrieval operations.
//!
//! # Architecture
//!
//! The module is organized into several components:
//! - **I/O Scheduling**: Adaptive buffer sizing and load management
//! - **Object Caching**: Tiered L1/L2 cache for frequently accessed objects
//! - **Concurrency Management**: Coordination of concurrent GetObject requests
//! - **Request Tracking**: RAII guards for request lifecycle management
//!
@@ -37,7 +36,6 @@
// pub mod io_profile; // Migrated to rustfs-io-core
pub mod io_schedule;
pub mod manager;
pub mod object_cache;
pub mod request_guard;
// ============================================
@@ -54,10 +52,6 @@ pub use io_schedule::{
// Request tracking
pub use request_guard::GetObjectGuard;
// Cache types
#[allow(unused_imports)]
pub use object_cache::{CacheHealthStatus, CacheStats, CachedGetObject};
// Concurrency manager
pub use manager::ConcurrencyManager;
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff