* perf(put): add eager path metrics and isolation tooling * fix(decommission): persist progress adaptively (#3497) Persist decommission progress after either the existing time interval or a migrated-item threshold, and flush progress baselines after bucket and terminal-state saves. Also stabilize the OIDC discovery mock used by the pre-commit gate. * refactor: move bucket operations contract (#3507) * fix(s3): handle multipart flexible checksums (#3508) * fix(io-core): avoid blocking on pooled buffer return * perf(put): add slow inflight diagnostics * perf(put): fix 16KiB regression with threshold and pool bypass - Lower SMALL_EAGER_PUT_MAX_SIZE from 256KB to 8KB so objects >8KiB use the streaming BufReader path (matches baseline behavior) - Add POOL_BYPASE_MAX_SIZE (16KiB) to bypass BytesPool for very small objects, avoiding Small-tier Mutex contention under high concurrency - Add read_small_put_body_exact_direct() for direct Vec<u8> allocation - Fix stale test assertions to match new 8KB threshold Root cause analysis: the 16KiB regression was primarily caused by instrumentation overhead in set_disk.rs (4x Instant::now() + metrics per PUT), not BytesPool contention. Lowering the threshold eliminates the eager-path overhead for 16KiB+ objects. * perf(put): gate stage metrics behind observability flag Add put_stage_metrics_enabled() AtomicBool switch in io-metrics crate. When disabled (default), record_put_object_path() and record_put_object_stage_duration() are no-ops, avoiding unnecessary histogram/counter macro overhead in the PUT hot path. The flag is set to true during startup when OTEL metric export is enabled (rustfs_obs::observability_metric_enabled() == true). This eliminates the per-request metrics overhead that contributed to the 16KiB PUT regression when metrics collection is not active. * perf(put): comprehensive optimization - restore eager path, cache env, remove UUID Change 1: Restore SMALL_EAGER_PUT_MAX_SIZE from 8KB to 1MB - The try_lock() fix (d13a189e3) eliminates the blocking that caused service health timeouts under 512KiB c64 load - Eager path with BytesPool is now safe for objects up to 1MB - Recovers the eager path benefit for 32KiB-256KiB objects Change 2: Adjust POOL_BYPASE_MAX_SIZE from 16KB to 4KB - With eager path restored to 1MB, objects 4KB-1MB benefit from pool reuse - Only ≤4KB objects bypass the pool (allocation cost negligible) Change 3: Cache RUSTFS_ERASURE_ENCODE_MAX_INFLIGHT_BYTES via OnceLock - Eliminates per-encode std::env::var() syscall - Env var still works (read once at first use) Change 4: Replace Uuid::new_v4() with Uuid::nil() in Erasure construction - _id field is unused in hot paths (documented in code) - Eliminates CSPRNG syscall per PUT request Change 5: Add concurrency-aware buffer sizing to PUT path - Reuses get_concurrency_aware_buffer_size() from GET path - Reduces buffer size under high concurrency (0.4x at >8 concurrent) - Lowers memory pressure for >1MB streaming PUTs * chore: add pyroscope feature flag and clean up imports - Add pyroscope feature flag forwarding to rustfs-obs - Remove unused allow(non_upper_case_globals) in globals.rs - Sort imports and fix Cargo.toml formatting consistency * style: fix import ordering and code formatting - Sort imports alphabetically in globals.rs, encode.rs - Fix indentation in erasure_coding encode/erasure - Clean up HashReader formatting in object_usecase.rs * fix(test): use tokio::test for request_logging_layer tests The tests call tokio::spawn via RequestContextLayer, which requires a Tokio runtime. Changed from #[test] + futures::executor::block_on to #[tokio::test] + .await, and replaced tracing::subscriber::with_default with tracing::subscriber::set_default to support async. * fix(bench): normalize no-space throughput/latency parsing in to_bps/to_ms When a benchmark tool prints throughput without a separator (e.g. 123MiB/s), awk '{print $2}' returns empty because the whole string is one field, causing to_bps to return N/A and losing valid measurements in CSV output. Insert a space between number and unit via sed before awk field splitting. Same fix applied to to_ms for latency values like '50ms'. Also add TODO comment on PUT path noting that get_concurrency_aware_buffer_size reads ACTIVE_GET_REQUESTS instead of PUT concurrency (PR #3514 review). Refs: PR #3514 review comments by chatgpt-codex-connector * fix(metrics): correct POOL_BYPASS comments and separate PUT vs generic stage metrics - Fix 3 comment-code mismatches: POOL_BYPASS_MAX_SIZE is 4KiB, not 16KiB - Add generic record_stage_duration() with separate histogram (rustfs_internal_stage_duration_ms) for non-PUT paths - Replace record_put_object_stage_duration with record_stage_duration in metacache_set, store_list_objects, and bucket_lifecycle_ops to avoid polluting PUT-specific dashboards with listing/lifecycle timings - Fix flaky test: serialize tests mutating PUT_STAGE_METRICS_ENABLED with METRICS_FLAG_LOCK mutex and explicitly set desired state at test start Refs: PR #3514 review comments by chatgpt-codex-connector * style: apply cargo fmt to metacache_set.rs --------- Co-authored-by: cxymds <cxymds@gmail.com> Co-authored-by: 安正超 <anzhengchao@gmail.com>
rustfs-io-metrics
· Home · Docs · Issues · Discussions
Overview
rustfs-io-metrics is the metrics and configuration module for RustFS, a distributed object storage system. It provides:
- Cache Configuration: L1/L2 tiered cache configuration management
- Adaptive TTL: Dynamic TTL adjustment based on access frequency
- Metrics Collection: Unified metrics recording and reporting
- Bandwidth Monitoring: Real-time bandwidth observation and analysis
- Performance Metrics: I/O performance metrics collection
- Unified Configuration: Centralized configuration management
- Exporter Boundary: Emit via
metrics, export viarustfs-obs, no Prometheus HTTP endpoint
Features
Cache Configuration
Tiered cache configuration management:
use rustfs_io_metrics::{CacheConfig, CacheConfigError};
// Create configuration
let config = CacheConfig::new();
// Validate configuration
if let Err(e) = config.validate() {
println!("Invalid configuration: {}", e);
}
// Custom configuration
let config = CacheConfig {
max_capacity: 10_000,
default_ttl_seconds: 300,
max_memory_bytes: 100 * 1024 * 1024, // 100 MB
..Default::default()
};
Adaptive TTL
Dynamic TTL adjustment based on access frequency:
use rustfs_io_metrics::{AdaptiveTTL, AdaptiveTTLStats};
use std::time::Duration;
let config = CacheConfig::new().with_ttl_range(60, 300, 3600);
let ttl = AdaptiveTTL::new(config);
// Cold object (few accesses)
let cold_ttl = ttl.calculate_ttl(Duration::from_secs(60), 1, 0.8);
println!("Cold object TTL: {:?}", cold_ttl);
// Hot object (many accesses)
let hot_ttl = ttl.calculate_ttl(Duration::from_secs(60), 100, 0.8);
println!("Hot object TTL: {:?}", hot_ttl);
Access Tracking
Track cache item access patterns:
use rustfs_io_metrics::{AccessTracker, AccessRecord};
use std::time::Duration;
let mut tracker = AccessTracker::new(1000, Duration::from_secs(300));
// Record accesses
tracker.record_access("object-key-1", 1024);
tracker.record_access("object-key-1", 1024);
tracker.record_access("object-key-2", 2048);
// Get access count
let count = tracker.get_access_count("object-key-1");
println!("Access count: {}", count);
// Detect hot/cold
if tracker.is_hot("object-key-1", 1) {
println!("Hot object");
}
// Get top keys
let top_keys = tracker.top_keys(10);
for (key, count) in top_keys {
println!("{}: {} accesses", key, count);
}
Metrics Recording
Unified metrics recording functions:
use rustfs_io_metrics::{
// I/O scheduler metrics
record_io_scheduler_decision,
record_io_strategy_change,
record_io_load_level,
// Cache metrics
record_cache_size,
// Backpressure metrics
record_backpressure_event,
record_backpressure_state,
// Timeout metrics
record_timeout_event,
record_operation_duration,
};
// Record I/O scheduler decision
record_io_scheduler_decision("sequential", "high_priority");
// Record cache size
record_cache_size("L1", 1024, 1);
// Record backpressure event
record_backpressure_event("warning", 0.85);
// Record operation timeout
record_timeout_event("GetObject", Duration::from_secs(30));
Internode Transport Metrics
Internode metrics are recorded by src/internode_metrics.rs. Aggregate metrics
remain unlabeled for compatibility with existing dashboards:
| Metric | Meaning |
|---|---|
rustfs_system_network_internode_sent_bytes_total |
Total internode bytes sent by this node. |
rustfs_system_network_internode_recv_bytes_total |
Total internode bytes received by this node. |
rustfs_system_network_internode_requests_outgoing_total |
Total outgoing internode requests. |
rustfs_system_network_internode_requests_incoming_total |
Total incoming internode requests. |
rustfs_system_network_internode_errors_total |
Total internode errors. |
rustfs_system_network_internode_dial_errors_total |
Failed internode connection attempts. |
rustfs_system_network_internode_dial_avg_time_nanos |
Average internode dial duration. |
Operation-level metrics use the same low-cardinality label set:
| Metric | Labels | Meaning |
|---|---|---|
rustfs_system_network_internode_operation_sent_bytes_total |
operation, backend |
Bytes sent for an internode operation. |
rustfs_system_network_internode_operation_recv_bytes_total |
operation, backend |
Bytes received for an internode operation. |
rustfs_system_network_internode_operation_requests_outgoing_total |
operation, backend |
Outgoing request attempts for an internode operation. |
rustfs_system_network_internode_operation_requests_incoming_total |
operation, backend |
Incoming request attempts for an internode operation. |
rustfs_system_network_internode_operation_errors_total |
operation, backend |
Failed internode operation attempts. |
rustfs_system_network_internode_operation_classified_errors_total |
operation, backend, classification |
Classified internode transport failures. |
rustfs_system_network_internode_operation_retries_total |
operation, backend, classification |
Retry attempts for retryable internode transport failures. |
rustfs_system_network_internode_operation_retry_successes_total |
operation, backend, classification |
Successful recoveries after retryable internode transport failures. |
rustfs_system_storage_erasure_write_quorum_failures_total |
stage, dominant_error |
Erasure write quorum failures grouped by failure stage and dominant error class. |
Current operation values are read_file_stream, put_file_stream,
walk_dir, grpc_read_all, and grpc_write_all. Current backend values are
tcp-http for the InternodeDataTransport TCP/HTTP path and grpc for the
remaining gRPC byte paths. The compatibility wrapper uses unknown only for
callers that have not been classified yet.
Success/failure is intentionally not a high-cardinality label today. Failures
are represented by rustfs_system_network_internode_operation_errors_total;
successful completions are not emitted as a dedicated result-labeled metric.
Adding completion/result labels is a follow-up once stream completion semantics
are defined consistently for request setup, body transfer, and shutdown.
Current low-cardinality classification values come from the TCP/HTTP internode
path and include:
connect_timeoutconnection_refuseddns_resolution_failedconnection_resetbody_stream_abortedhttp_429http_502http_503http_504http_status_otherunknown
scripts/run_internode_transport_baseline.sh --metrics-url ... records metric
deltas with operation and backend columns, so the TCP baseline can attribute
bytes and request/error counts to tcp-http transport operations.
Unified Configuration
Centralized configuration management:
use rustfs_io_metrics::{
IoConfig, CacheSettings, IoSchedulerSettings,
BackpressureSettings, TimeoutSettings,
};
let config = IoConfig::new()
.with_cache(CacheSettings::new()
.with_max_capacity(10_000)
.with_ttl(std::time::Duration::from_secs(300)))
.with_scheduler(IoSchedulerSettings::new()
.with_max_concurrent_reads(64))
.with_backpressure(BackpressureSettings::new())
.with_timeout(TimeoutSettings::new());
// Access configuration
println!("Cache capacity: {}", config.cache.max_capacity);
println!("Max concurrent reads: {}", config.scheduler.max_concurrent_reads);
Module Structure
rustfs-io-metrics/
├── src/
│ ├── lib.rs # Module entry
│ ├── cache_config.rs # Cache configuration
│ ├── adaptive_ttl.rs # Adaptive TTL
│ ├── config.rs # Unified configuration
│ ├── io_metrics.rs # I/O metrics
│ ├── backpressure_metrics.rs # Backpressure metrics
│ ├── deadlock_metrics.rs # Deadlock metrics
│ ├── lock_metrics.rs # Lock metrics
│ ├── timeout_metrics.rs # Timeout metrics
│ ├── internode_metrics.rs # Internode transport metrics
│ ├── bandwidth.rs # Bandwidth monitoring
│ ├── global_metrics.rs # Global metrics
│ └── performance.rs # Performance metrics
└── Cargo.toml
Testing
# Run all tests
cargo test --package rustfs-io-metrics
# Run specific tests
cargo test --package rustfs-io-metrics --lib adaptive_ttl
# Run benchmarks
cargo bench --package rustfs-io-metrics --bench metrics_pipeline
Documentation
This crate records metrics through the Rust metrics crate and leaves
exporting to rustfs-obs or the application-level observability pipeline. It
does not expose Prometheus-compatible HTTP endpoints such as
/rustfs/v2/metrics/cluster or /rustfs/v2/metrics/node.
API documentation can be generated locally:
cargo doc --package rustfs-io-metrics --no-deps --open
Useful source references:
Related Modules
- rustfs-io-core: Core I/O scheduling
- rustfs: Main storage service
License
Apache License 2.0