mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-10 07:06:53 +00:00
d4beb1cc0b
* Refactor: reimplement lock Signed-off-by: junxiang Mu <1948535941@qq.com> * Fix: fix test case failed Signed-off-by: junxiang Mu <1948535941@qq.com> * Improve: lock pref Signed-off-by: junxiang Mu <1948535941@qq.com> * fix(lock): Fix resource cleanup issue when batch lock acquisition fails Ensure that the locks already acquired are properly released when batch lock acquisition fails to avoid memory leaks Improve the lock protection mechanism to prevent double release issues Add complete Apache license declarations to all files Signed-off-by: junxiang Mu <1948535941@qq.com> --------- Signed-off-by: junxiang Mu <1948535941@qq.com>
325 lines
13 KiB
Rust
325 lines
13 KiB
Rust
// Copyright 2024 RustFS Team
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// Benchmarks comparing fast lock vs old lock performance
|
|
|
|
#[cfg(test)]
|
|
#[allow(dead_code)] // Temporarily disable benchmark tests
|
|
mod benchmarks {
|
|
use super::super::*;
|
|
use std::sync::Arc;
|
|
use std::time::{Duration, Instant};
|
|
use tokio::task;
|
|
|
|
/// Benchmark single-threaded lock operations
|
|
#[tokio::test]
|
|
async fn bench_single_threaded_fast_locks() {
|
|
let manager = Arc::new(FastObjectLockManager::new());
|
|
let iterations = 10000;
|
|
|
|
// Warm up
|
|
for i in 0..100 {
|
|
let _guard = manager
|
|
.acquire_write_lock("bucket", &format!("warm_{}", i), "owner")
|
|
.await
|
|
.unwrap();
|
|
}
|
|
|
|
// Benchmark write locks
|
|
let start = Instant::now();
|
|
for i in 0..iterations {
|
|
let _guard = manager
|
|
.acquire_write_lock("bucket", &format!("object_{}", i), "owner")
|
|
.await
|
|
.unwrap();
|
|
}
|
|
let duration = start.elapsed();
|
|
|
|
println!("Fast locks: {} write locks in {:?}", iterations, duration);
|
|
println!("Average: {:?} per lock", duration / iterations);
|
|
|
|
let metrics = manager.get_metrics();
|
|
println!("Fast path rate: {:.2}%", metrics.shard_metrics.fast_path_rate() * 100.0);
|
|
|
|
// Should be much faster than old implementation
|
|
assert!(duration.as_millis() < 1000, "Should complete 10k locks in <1s");
|
|
assert!(metrics.shard_metrics.fast_path_rate() > 0.95, "Should have >95% fast path rate");
|
|
}
|
|
|
|
/// Benchmark concurrent lock operations
|
|
#[tokio::test]
|
|
async fn bench_concurrent_fast_locks() {
|
|
let manager = Arc::new(FastObjectLockManager::new());
|
|
let concurrent_tasks = 100;
|
|
let iterations_per_task = 100;
|
|
|
|
let start = Instant::now();
|
|
|
|
let mut handles = Vec::new();
|
|
for task_id in 0..concurrent_tasks {
|
|
let manager_clone = manager.clone();
|
|
let handle = task::spawn(async move {
|
|
for i in 0..iterations_per_task {
|
|
let object_name = format!("obj_{}_{}", task_id, i);
|
|
let _guard = manager_clone
|
|
.acquire_write_lock("bucket", &object_name, &format!("owner_{}", task_id))
|
|
.await
|
|
.unwrap();
|
|
|
|
// Simulate some work
|
|
tokio::task::yield_now().await;
|
|
}
|
|
});
|
|
handles.push(handle);
|
|
}
|
|
|
|
// Wait for all tasks
|
|
for handle in handles {
|
|
handle.await.unwrap();
|
|
}
|
|
|
|
let duration = start.elapsed();
|
|
let total_ops = concurrent_tasks * iterations_per_task;
|
|
|
|
println!("Concurrent fast locks: {} operations across {} tasks in {:?}",
|
|
total_ops, concurrent_tasks, duration);
|
|
println!("Throughput: {:.2} ops/sec", total_ops as f64 / duration.as_secs_f64());
|
|
|
|
let metrics = manager.get_metrics();
|
|
println!("Fast path rate: {:.2}%", metrics.shard_metrics.fast_path_rate() * 100.0);
|
|
println!("Contention events: {}", metrics.shard_metrics.contention_events);
|
|
|
|
// Should maintain high throughput even with concurrency
|
|
assert!(duration.as_millis() < 5000, "Should complete concurrent ops in <5s");
|
|
}
|
|
|
|
/// Benchmark contended lock operations
|
|
#[tokio::test]
|
|
async fn bench_contended_locks() {
|
|
let manager = Arc::new(FastObjectLockManager::new());
|
|
let concurrent_tasks = 50;
|
|
let shared_objects = 10; // High contention on few objects
|
|
let iterations_per_task = 50;
|
|
|
|
let start = Instant::now();
|
|
|
|
let mut handles = Vec::new();
|
|
for task_id in 0..concurrent_tasks {
|
|
let manager_clone = manager.clone();
|
|
let handle = task::spawn(async move {
|
|
for i in 0..iterations_per_task {
|
|
let object_name = format!("shared_{}", i % shared_objects);
|
|
|
|
// Mix of read and write operations
|
|
if i % 3 == 0 {
|
|
// Write operation
|
|
if let Ok(_guard) = manager_clone
|
|
.acquire_write_lock("bucket", &object_name, &format!("owner_{}", task_id))
|
|
.await
|
|
{
|
|
tokio::task::yield_now().await;
|
|
}
|
|
} else {
|
|
// Read operation
|
|
if let Ok(_guard) = manager_clone
|
|
.acquire_read_lock("bucket", &object_name, &format!("owner_{}", task_id))
|
|
.await
|
|
{
|
|
tokio::task::yield_now().await;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
handles.push(handle);
|
|
}
|
|
|
|
// Wait for all tasks
|
|
for handle in handles {
|
|
handle.await.unwrap();
|
|
}
|
|
|
|
let duration = start.elapsed();
|
|
|
|
println!("Contended locks: {} tasks on {} objects in {:?}",
|
|
concurrent_tasks, shared_objects, duration);
|
|
|
|
let metrics = manager.get_metrics();
|
|
println!("Total acquisitions: {}", metrics.shard_metrics.total_acquisitions());
|
|
println!("Fast path rate: {:.2}%", metrics.shard_metrics.fast_path_rate() * 100.0);
|
|
println!("Average wait time: {:?}", metrics.shard_metrics.avg_wait_time());
|
|
println!("Timeout rate: {:.2}%", metrics.shard_metrics.timeout_rate() * 100.0);
|
|
|
|
// Even with contention, should maintain reasonable performance
|
|
assert!(metrics.shard_metrics.timeout_rate() < 0.1, "Should have <10% timeout rate");
|
|
assert!(metrics.shard_metrics.avg_wait_time() < Duration::from_millis(100), "Avg wait should be <100ms");
|
|
}
|
|
|
|
/// Benchmark batch operations
|
|
#[tokio::test]
|
|
async fn bench_batch_operations() {
|
|
let manager = FastObjectLockManager::new();
|
|
let batch_sizes = vec![10, 50, 100, 500];
|
|
|
|
for batch_size in batch_sizes {
|
|
// Create batch request
|
|
let mut batch = BatchLockRequest::new("batch_owner");
|
|
for i in 0..batch_size {
|
|
batch = batch.add_write_lock("bucket", &format!("batch_obj_{}", i));
|
|
}
|
|
|
|
let start = Instant::now();
|
|
let result = manager.acquire_locks_batch(batch).await;
|
|
let duration = start.elapsed();
|
|
|
|
assert!(result.all_acquired, "Batch should succeed");
|
|
println!("Batch size {}: {:?} ({:.2} μs per lock)",
|
|
batch_size,
|
|
duration,
|
|
duration.as_micros() as f64 / batch_size as f64);
|
|
|
|
// Batch should be much faster than individual acquisitions
|
|
assert!(duration.as_millis() < batch_size as u128 / 10,
|
|
"Batch should be 10x+ faster than individual locks");
|
|
}
|
|
}
|
|
|
|
/// Benchmark version-specific locks
|
|
#[tokio::test]
|
|
async fn bench_versioned_locks() {
|
|
let manager = Arc::new(FastObjectLockManager::new());
|
|
let objects = 100;
|
|
let versions_per_object = 10;
|
|
|
|
let start = Instant::now();
|
|
|
|
let mut handles = Vec::new();
|
|
for obj_id in 0..objects {
|
|
let manager_clone = manager.clone();
|
|
let handle = task::spawn(async move {
|
|
for version in 0..versions_per_object {
|
|
let _guard = manager_clone
|
|
.acquire_write_lock_versioned(
|
|
"bucket",
|
|
&format!("obj_{}", obj_id),
|
|
&format!("v{}", version),
|
|
"version_owner"
|
|
)
|
|
.await
|
|
.unwrap();
|
|
}
|
|
});
|
|
handles.push(handle);
|
|
}
|
|
|
|
for handle in handles {
|
|
handle.await.unwrap();
|
|
}
|
|
|
|
let duration = start.elapsed();
|
|
let total_ops = objects * versions_per_object;
|
|
|
|
println!("Versioned locks: {} version locks in {:?}", total_ops, duration);
|
|
println!("Throughput: {:.2} locks/sec", total_ops as f64 / duration.as_secs_f64());
|
|
|
|
let metrics = manager.get_metrics();
|
|
println!("Fast path rate: {:.2}%", metrics.shard_metrics.fast_path_rate() * 100.0);
|
|
|
|
// Versioned locks should not interfere with each other
|
|
assert!(metrics.shard_metrics.fast_path_rate() > 0.9, "Should maintain high fast path rate");
|
|
}
|
|
|
|
/// Compare with theoretical maximum performance
|
|
#[tokio::test]
|
|
async fn bench_theoretical_maximum() {
|
|
let manager = Arc::new(FastObjectLockManager::new());
|
|
let iterations = 100000;
|
|
|
|
// Measure pure fast path performance (no contention)
|
|
let start = Instant::now();
|
|
for i in 0..iterations {
|
|
let _guard = manager
|
|
.acquire_write_lock("bucket", &format!("unique_{}", i), "owner")
|
|
.await
|
|
.unwrap();
|
|
}
|
|
let duration = start.elapsed();
|
|
|
|
println!("Theoretical maximum: {} unique locks in {:?}", iterations, duration);
|
|
println!("Rate: {:.2} locks/sec", iterations as f64 / duration.as_secs_f64());
|
|
println!("Latency: {:?} per lock", duration / iterations);
|
|
|
|
let metrics = manager.get_metrics();
|
|
println!("Fast path rate: {:.2}%", metrics.shard_metrics.fast_path_rate() * 100.0);
|
|
|
|
// Should achieve very high performance with no contention
|
|
assert!(metrics.shard_metrics.fast_path_rate() > 0.99, "Should be nearly 100% fast path");
|
|
assert!(duration.as_secs_f64() / (iterations as f64) < 0.0001, "Should be <100μs per lock");
|
|
}
|
|
|
|
/// Performance regression test
|
|
#[tokio::test]
|
|
async fn performance_regression_test() {
|
|
let manager = Arc::new(FastObjectLockManager::new());
|
|
|
|
// This test ensures we maintain performance targets
|
|
let test_cases = vec![
|
|
("single_thread", 1, 10000),
|
|
("low_contention", 10, 1000),
|
|
("high_contention", 100, 100),
|
|
];
|
|
|
|
for (test_name, threads, ops_per_thread) in test_cases {
|
|
let start = Instant::now();
|
|
|
|
let mut handles = Vec::new();
|
|
for thread_id in 0..threads {
|
|
let manager_clone = manager.clone();
|
|
let handle = task::spawn(async move {
|
|
for op_id in 0..ops_per_thread {
|
|
let object = if threads == 1 {
|
|
format!("obj_{}_{}", thread_id, op_id)
|
|
} else {
|
|
format!("obj_{}", op_id % 100) // Create contention
|
|
};
|
|
|
|
let owner = format!("owner_{}", thread_id);
|
|
let _guard = manager_clone
|
|
.acquire_write_lock("bucket", object, owner)
|
|
.await
|
|
.unwrap();
|
|
}
|
|
});
|
|
handles.push(handle);
|
|
}
|
|
|
|
for handle in handles {
|
|
handle.await.unwrap();
|
|
}
|
|
|
|
let duration = start.elapsed();
|
|
let total_ops = threads * ops_per_thread;
|
|
let ops_per_sec = total_ops as f64 / duration.as_secs_f64();
|
|
|
|
println!("{}: {:.2} ops/sec", test_name, ops_per_sec);
|
|
|
|
// Performance targets (adjust based on requirements)
|
|
match test_name {
|
|
"single_thread" => assert!(ops_per_sec > 50000.0, "Single thread should exceed 50k ops/sec"),
|
|
"low_contention" => assert!(ops_per_sec > 20000.0, "Low contention should exceed 20k ops/sec"),
|
|
"high_contention" => assert!(ops_per_sec > 5000.0, "High contention should exceed 5k ops/sec"),
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
} |