first commit
This commit is contained in:
21
.gitignore
vendored
Normal file
21
.gitignore
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# ---> Rust
|
||||||
|
# Generated by Cargo
|
||||||
|
# will have compiled files and executables
|
||||||
|
debug/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# These are backup files generated by rustfmt
|
||||||
|
**/*.rs.bk
|
||||||
|
|
||||||
|
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||||
|
*.pdb
|
||||||
|
|
||||||
|
# Database files
|
||||||
|
*.db
|
||||||
|
|
||||||
|
# RustRover
|
||||||
|
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||||
|
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||||
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||||
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
|
#.idea/
|
1218
Cargo.lock
generated
Normal file
1218
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
26
Cargo.toml
Normal file
26
Cargo.toml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
[package]
|
||||||
|
name = "tesnig"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "oversdb"
|
||||||
|
path = "bin/oversdb.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "skytable"
|
||||||
|
path = "bin/skytable.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "redis"
|
||||||
|
path = "bin/redis.rs"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "benchmark_oversdb"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
skytable = "0.8.12"
|
||||||
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
|
async-trait = "0.1"
|
||||||
|
redis = { version = "0.32.4", features = ["tokio-comp"] }
|
133
bin/oversdb.rs
Normal file
133
bin/oversdb.rs
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
use benchmark_oversdb::{StressTester, OversDBClient};
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), String> {
|
||||||
|
let args: Vec<String> = std::env::args().collect();
|
||||||
|
|
||||||
|
if args.len() < 2 {
|
||||||
|
println!("OversDB Stress Test & Benchmark Tool");
|
||||||
|
println!("====================================");
|
||||||
|
println!();
|
||||||
|
println!("Usage: {} <test_type> [options]", args[0]);
|
||||||
|
println!();
|
||||||
|
println!("Test types:");
|
||||||
|
println!(" quick - Quick test (1000 ops, 10 clients)");
|
||||||
|
println!(" standard - Standard test (10000 ops, 50 clients)");
|
||||||
|
println!(" intensive - Intensive test (100000 ops, 100 clients)");
|
||||||
|
println!(" extreme - Extreme test (1000000 ops, 200 clients)");
|
||||||
|
println!(" debug - Debug test (100 ops, 5 clients)");
|
||||||
|
println!(" custom <ops> <clients> <value_size> - Custom parameters");
|
||||||
|
println!();
|
||||||
|
println!("Examples:");
|
||||||
|
println!(" {} quick", args[0]);
|
||||||
|
println!(" {} custom 50000 75 1024", args[0]);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let tester = StressTester::new("167.99.33.194:6601".to_string());
|
||||||
|
|
||||||
|
println!("🔧 Connecting to OversDB server at 167.99.33.194:6601...");
|
||||||
|
match OversDBClient::connect("167.99.33.194:6601").await {
|
||||||
|
Ok(_) => println!("✓ Connection successful!"),
|
||||||
|
Err(e) => {
|
||||||
|
println!("✗ Failed to connect: {}", e);
|
||||||
|
println!("Make sure the OversDB server is running on 167.99.33.194:6601");
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
|
||||||
|
let (ops, clients, value_size) = match args[1].as_str() {
|
||||||
|
"quick" => (1_000, 10, 256),
|
||||||
|
"standard" => (10_000, 50, 256),
|
||||||
|
"intensive" => (100_000, 100, 256),
|
||||||
|
"extreme" => (1_000_000, 200, 256),
|
||||||
|
"debug" => (100, 5, 64), // Small debug test
|
||||||
|
"custom" => {
|
||||||
|
if args.len() < 5 {
|
||||||
|
return Err("Custom test requires: custom <ops> <clients> <value_size>".to_string());
|
||||||
|
}
|
||||||
|
let ops = args[2].parse().map_err(|_| "Invalid operations count")?;
|
||||||
|
let clients = args[3].parse().map_err(|_| "Invalid client count")?;
|
||||||
|
let value_size = args[4].parse().map_err(|_| "Invalid value size")?;
|
||||||
|
(ops, clients, value_size)
|
||||||
|
},
|
||||||
|
_ => return Err("Unknown test type. Use: quick, standard, intensive, extreme, debug, or custom".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("🎯 Test Configuration:");
|
||||||
|
println!(" Total operations: {}", ops);
|
||||||
|
println!(" Concurrent clients: {}", clients);
|
||||||
|
println!(" Value size: {} bytes", value_size);
|
||||||
|
println!();
|
||||||
|
|
||||||
|
// Prepare some data for read tests
|
||||||
|
println!("📝 Preparing test data...");
|
||||||
|
let mut prep_client = OversDBClient::connect("167.99.33.194:6601").await?;
|
||||||
|
let prep_value = vec![0x42u8; value_size];
|
||||||
|
|
||||||
|
for i in 0..10 {
|
||||||
|
for j in 0..100 {
|
||||||
|
let key = format!("worker{}:key{}", i, j);
|
||||||
|
let _ = prep_client.create("benchmark", "test", key.as_bytes(), &prep_value, 0).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!("✓ Test data prepared");
|
||||||
|
println!();
|
||||||
|
|
||||||
|
// Run benchmarks
|
||||||
|
let mut results = Vec::new();
|
||||||
|
|
||||||
|
// CREATE benchmark
|
||||||
|
match tester.benchmark_create(ops, clients, value_size).await {
|
||||||
|
Ok(result) => {
|
||||||
|
result.print();
|
||||||
|
results.push(result);
|
||||||
|
},
|
||||||
|
Err(e) => println!("CREATE benchmark failed: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
// READ benchmark
|
||||||
|
match tester.benchmark_read(ops, clients).await {
|
||||||
|
Ok(result) => {
|
||||||
|
result.print();
|
||||||
|
results.push(result);
|
||||||
|
},
|
||||||
|
Err(e) => println!("READ benchmark failed: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
// MIXED benchmark
|
||||||
|
match tester.benchmark_mixed(ops, clients, value_size).await {
|
||||||
|
Ok(result) => {
|
||||||
|
result.print();
|
||||||
|
results.push(result);
|
||||||
|
},
|
||||||
|
Err(e) => println!("MIXED benchmark failed: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Summary
|
||||||
|
println!("📈 SUMMARY");
|
||||||
|
println!("==========");
|
||||||
|
for result in &results {
|
||||||
|
let error_rate = if result.total_operations + result.errors > 0 {
|
||||||
|
format!("{:.1}%", (result.errors as f64 / (result.total_operations + result.errors) as f64) * 100.0)
|
||||||
|
} else {
|
||||||
|
"0.0%".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("{:8} | {:>10.0} ops/sec | {:>8.2}μs avg latency | {} errors ({})",
|
||||||
|
result.operation,
|
||||||
|
result.ops_per_second,
|
||||||
|
result.avg_latency_us,
|
||||||
|
result.errors,
|
||||||
|
error_rate
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(best) = results.iter().filter(|r| r.ops_per_second > 0.0).max_by(|a, b| a.ops_per_second.partial_cmp(&b.ops_per_second).unwrap()) {
|
||||||
|
println!();
|
||||||
|
println!("🏆 Best performance: {} with {:.0} ops/sec", best.operation, best.ops_per_second);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
188
bin/redis.rs
Normal file
188
bin/redis.rs
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
use benchmark_oversdb::{StressTester, RedisClient};
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), String> {
|
||||||
|
let args: Vec<String> = std::env::args().collect();
|
||||||
|
|
||||||
|
if args.len() < 2 {
|
||||||
|
println!("Redis Stress Test & Benchmark Tool");
|
||||||
|
println!("==================================");
|
||||||
|
println!();
|
||||||
|
println!("Usage: {} <test_type> [options]", args[0]);
|
||||||
|
println!();
|
||||||
|
println!("Test types:");
|
||||||
|
println!(" quick - Quick test (1000 ops, 10 clients)");
|
||||||
|
println!(" standard - Standard test (10000 ops, 50 clients)");
|
||||||
|
println!(" intensive - Intensive test (100000 ops, 100 clients)");
|
||||||
|
println!(" extreme - Extreme test (1000000 ops, 200 clients)");
|
||||||
|
println!(" debug - Debug test (100 ops, 5 clients)");
|
||||||
|
println!(" custom <ops> <clients> <value_size> - Custom parameters");
|
||||||
|
println!();
|
||||||
|
println!("Environment variables:");
|
||||||
|
println!(" REDIS_HOST - Server host (default: 127.0.0.1)");
|
||||||
|
println!(" REDIS_PORT - Server port (default: 6379)");
|
||||||
|
println!(" REDIS_PASSWORD - Password (optional)");
|
||||||
|
println!();
|
||||||
|
println!("Examples:");
|
||||||
|
println!(" {} quick", args[0]);
|
||||||
|
println!(" {} custom 50000 75 1024", args[0]);
|
||||||
|
println!(" REDIS_PASSWORD=mypass {} standard", args[0]);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get connection details from environment or use defaults
|
||||||
|
let host = env::var("REDIS_HOST").unwrap_or_else(|_| "127.0.0.1".to_string());
|
||||||
|
let port = env::var("REDIS_PORT").unwrap_or_else(|_| "6379".to_string());
|
||||||
|
let password = env::var("REDIS_PASSWORD").unwrap_or_else(|_| "".to_string());
|
||||||
|
let addr = format!("{}:{}", host, port);
|
||||||
|
|
||||||
|
let tester = StressTester::new(addr.clone(), "".to_string(), password.clone());
|
||||||
|
|
||||||
|
println!("🔧 Connecting to Redis server at {}...", addr);
|
||||||
|
if password != "" {
|
||||||
|
println!(" Using password authentication");
|
||||||
|
}
|
||||||
|
|
||||||
|
match RedisClient::connect_with_auth(&addr, &password).await {
|
||||||
|
Ok(mut client) => {
|
||||||
|
println!("✓ Connection successful!");
|
||||||
|
|
||||||
|
// Test connection with PING
|
||||||
|
match client.ping().await {
|
||||||
|
Ok(response) => println!("✓ Server responded: {}", response),
|
||||||
|
Err(e) => {
|
||||||
|
println!("⚠ Warning: PING failed: {}", e);
|
||||||
|
println!(" Connection might still work for basic operations");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional: Clear database for clean benchmark
|
||||||
|
if env::var("REDIS_FLUSH_DB").unwrap_or_else(|_| "false".to_string()) == "true" {
|
||||||
|
println!("🧹 Flushing database...");
|
||||||
|
match client.flush_db().await {
|
||||||
|
Ok(_) => println!("✓ Database flushed"),
|
||||||
|
Err(e) => println!("⚠ Warning: Failed to flush database: {}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
println!("✗ Failed to connect: {}", e);
|
||||||
|
println!("Make sure the Redis server is running on {}", addr);
|
||||||
|
println!("Check your connection details and authentication:");
|
||||||
|
println!(" REDIS_HOST=your_host REDIS_PORT=your_port {} <test_type>", args[0]);
|
||||||
|
if password == "" {
|
||||||
|
println!(" If authentication is required:");
|
||||||
|
println!(" REDIS_PASSWORD=your_pass {} <test_type>", args[0]);
|
||||||
|
}
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
|
||||||
|
let (ops, clients, value_size) = match args[1].as_str() {
|
||||||
|
"quick" => (1_000, 10, 256),
|
||||||
|
"standard" => (10_000, 50, 256),
|
||||||
|
"intensive" => (100_000, 100, 256),
|
||||||
|
"extreme" => (1_000_000, 200, 256),
|
||||||
|
"debug" => (100, 5, 64),
|
||||||
|
"custom" => {
|
||||||
|
if args.len() < 5 {
|
||||||
|
return Err("Custom test requires: custom <ops> <clients> <value_size>".to_string());
|
||||||
|
}
|
||||||
|
let ops = args[2].parse().map_err(|_| "Invalid operations count")?;
|
||||||
|
let clients = args[3].parse().map_err(|_| "Invalid client count")?;
|
||||||
|
let value_size = args[4].parse().map_err(|_| "Invalid value size")?;
|
||||||
|
(ops, clients, value_size)
|
||||||
|
},
|
||||||
|
_ => return Err("Unknown test type. Use: quick, standard, intensive, extreme, debug, or custom".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("🎯 Test Configuration:");
|
||||||
|
println!(" Total operations: {}", ops);
|
||||||
|
println!(" Concurrent clients: {}", clients);
|
||||||
|
println!(" Value size: {} bytes", value_size);
|
||||||
|
println!();
|
||||||
|
|
||||||
|
// Prepare some data for read tests
|
||||||
|
println!("📝 Preparing test data...");
|
||||||
|
let mut prep_client = RedisClient::connect_with_auth(&addr, &password).await?;
|
||||||
|
let prep_value = vec![0x42u8; value_size];
|
||||||
|
|
||||||
|
for i in 0..10 {
|
||||||
|
for j in 0..100 {
|
||||||
|
let key = format!("worker{}:key{}", i, j);
|
||||||
|
let _ = prep_client.set(&key, &prep_value).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!("✓ Test data prepared");
|
||||||
|
println!();
|
||||||
|
|
||||||
|
// Run benchmarks
|
||||||
|
let mut results = Vec::new();
|
||||||
|
|
||||||
|
// SET benchmark
|
||||||
|
match tester.redis_benchmark_set(ops, clients, value_size).await {
|
||||||
|
Ok(result) => {
|
||||||
|
result.print();
|
||||||
|
results.push(result);
|
||||||
|
},
|
||||||
|
Err(e) => println!("SET benchmark failed: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET benchmark
|
||||||
|
match tester.redis_benchmark_get(ops, clients).await {
|
||||||
|
Ok(result) => {
|
||||||
|
result.print();
|
||||||
|
results.push(result);
|
||||||
|
},
|
||||||
|
Err(e) => println!("GET benchmark failed: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
// MIXED benchmark
|
||||||
|
match tester.redis_benchmark_mixed(ops, clients, value_size).await {
|
||||||
|
Ok(result) => {
|
||||||
|
result.print();
|
||||||
|
results.push(result);
|
||||||
|
},
|
||||||
|
Err(e) => println!("MIXED benchmark failed: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Summary
|
||||||
|
println!("📈 SUMMARY");
|
||||||
|
println!("==========");
|
||||||
|
for result in &results {
|
||||||
|
let error_rate = if result.total_operations + result.errors > 0 {
|
||||||
|
format!("{:.1}%", (result.errors as f64 / (result.total_operations + result.errors) as f64) * 100.0)
|
||||||
|
} else {
|
||||||
|
"0.0%".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("{:8} | {:>10.0} ops/sec | {:>8.2}μs avg latency | {} errors ({})",
|
||||||
|
result.operation,
|
||||||
|
result.ops_per_second,
|
||||||
|
result.avg_latency_us,
|
||||||
|
result.errors,
|
||||||
|
error_rate
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(best) = results.iter().filter(|r| r.ops_per_second > 0.0).max_by(|a, b| a.ops_per_second.partial_cmp(&b.ops_per_second).unwrap()) {
|
||||||
|
println!();
|
||||||
|
println!("🏆 Best performance: {} with {:.0} ops/sec", best.operation, best.ops_per_second);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional Redis-specific information
|
||||||
|
println!();
|
||||||
|
println!("📊 Redis Connection Info:");
|
||||||
|
println!(" Server: {}", addr);
|
||||||
|
println!(" Authentication: {}", if password != "" { "Yes" } else { "No" });
|
||||||
|
println!();
|
||||||
|
println!("💡 Tips for better Redis performance:");
|
||||||
|
println!(" - Use Redis pipelining for batch operations");
|
||||||
|
println!(" - Consider Redis Cluster for horizontal scaling");
|
||||||
|
println!(" - Monitor memory usage with 'redis-cli info memory'");
|
||||||
|
println!(" - Use appropriate data structures (strings, hashes, sets, etc.)");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
161
bin/skytable.rs
Normal file
161
bin/skytable.rs
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
use benchmark_oversdb::{StressTester, SkytableClient};
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), String> {
|
||||||
|
let args: Vec<String> = std::env::args().collect();
|
||||||
|
|
||||||
|
if args.len() < 2 {
|
||||||
|
println!("Skytable Stress Test & Benchmark Tool");
|
||||||
|
println!("=====================================");
|
||||||
|
println!();
|
||||||
|
println!("Usage: {} <test_type> [options]", args[0]);
|
||||||
|
println!();
|
||||||
|
println!("Test types:");
|
||||||
|
println!(" quick - Quick test (1000 ops, 10 clients)");
|
||||||
|
println!(" standard - Standard test (10000 ops, 50 clients)");
|
||||||
|
println!(" intensive - Intensive test (100000 ops, 100 clients)");
|
||||||
|
println!(" extreme - Extreme test (1000000 ops, 200 clients)");
|
||||||
|
println!(" debug - Debug test (100 ops, 5 clients)");
|
||||||
|
println!(" custom <ops> <clients> <value_size> - Custom parameters");
|
||||||
|
println!();
|
||||||
|
println!("Environment variables:");
|
||||||
|
println!(" SKYTABLE_HOST - Server host (default: 127.0.0.1)");
|
||||||
|
println!(" SKYTABLE_USERNAME - Username (default: root)");
|
||||||
|
println!(" SKYTABLE_PASSWORD - Password (default: password)");
|
||||||
|
println!();
|
||||||
|
println!("Examples:");
|
||||||
|
println!(" {} quick", args[0]);
|
||||||
|
println!(" {} custom 50000 75 1024", args[0]);
|
||||||
|
println!(" SKYTABLE_PASSWORD=mypass {} standard", args[0]);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get connection details from environment or use defaults
|
||||||
|
let host = env::var("SKYTABLE_HOST").unwrap_or_else(|_| "127.0.0.1".to_string());
|
||||||
|
let username = env::var("SKYTABLE_USERNAME").unwrap_or_else(|_| "root".to_string());
|
||||||
|
let password = env::var("SKYTABLE_PASSWORD").unwrap_or_else(|_| "password".to_string());
|
||||||
|
|
||||||
|
let tester = StressTester::new(host.clone(), username.clone(), password.clone());
|
||||||
|
|
||||||
|
println!("🔧 Connecting to Skytable server at {}:2003...", host);
|
||||||
|
println!(" Using credentials: {}/{}", username, password);
|
||||||
|
|
||||||
|
match SkytableClient::connect_with_auth(&host, &username, &password).await {
|
||||||
|
Ok(mut client) => {
|
||||||
|
println!("✓ Connection successful!");
|
||||||
|
// Setup benchmark space
|
||||||
|
match client.setup_benchmark_space().await {
|
||||||
|
Ok(_) => println!("✓ Benchmark space setup complete!"),
|
||||||
|
Err(e) => {
|
||||||
|
println!("⚠ Warning: Benchmark space setup failed: {}", e);
|
||||||
|
println!(" This might be normal if using basic key-value mode");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
println!("✗ Failed to connect: {}", e);
|
||||||
|
println!("Make sure the Skytable server is running on {}:2003", host);
|
||||||
|
println!("and that you have the correct credentials");
|
||||||
|
println!("You can set credentials using environment variables:");
|
||||||
|
println!(" SKYTABLE_USERNAME=your_user SKYTABLE_PASSWORD=your_pass {} <test_type>", args[0]);
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
|
||||||
|
let (ops, clients, value_size) = match args[1].as_str() {
|
||||||
|
"quick" => (1_000, 10, 256),
|
||||||
|
"standard" => (10_000, 50, 256),
|
||||||
|
"intensive" => (100_000, 100, 256),
|
||||||
|
"extreme" => (1_000_000, 200, 256),
|
||||||
|
"debug" => (100, 5, 64),
|
||||||
|
"custom" => {
|
||||||
|
if args.len() < 5 {
|
||||||
|
return Err("Custom test requires: custom <ops> <clients> <value_size>".to_string());
|
||||||
|
}
|
||||||
|
let ops = args[2].parse().map_err(|_| "Invalid operations count")?;
|
||||||
|
let clients = args[3].parse().map_err(|_| "Invalid client count")?;
|
||||||
|
let value_size = args[4].parse().map_err(|_| "Invalid value size")?;
|
||||||
|
(ops, clients, value_size)
|
||||||
|
},
|
||||||
|
_ => return Err("Unknown test type. Use: quick, standard, intensive, extreme, debug, or custom".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("🎯 Test Configuration:");
|
||||||
|
println!(" Total operations: {}", ops);
|
||||||
|
println!(" Concurrent clients: {}", clients);
|
||||||
|
println!(" Value size: {} bytes", value_size);
|
||||||
|
println!();
|
||||||
|
|
||||||
|
// Prepare some data for read tests
|
||||||
|
println!("📝 Preparing test data...");
|
||||||
|
let mut prep_client = SkytableClient::connect_with_auth(&host, &username, &password).await?;
|
||||||
|
prep_client.setup_benchmark_space().await.ok(); // Ignore errors
|
||||||
|
let prep_value = vec![0x42u8; value_size];
|
||||||
|
|
||||||
|
for i in 0..10 {
|
||||||
|
for j in 0..100 {
|
||||||
|
let key = format!("worker{}:key{}", i, j);
|
||||||
|
let _ = prep_client.set(&key, &prep_value).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!("✓ Test data prepared");
|
||||||
|
println!();
|
||||||
|
|
||||||
|
// Run benchmarks
|
||||||
|
let mut results = Vec::new();
|
||||||
|
|
||||||
|
// SET benchmark
|
||||||
|
match tester.skytable_benchmark_set(ops, clients, value_size).await {
|
||||||
|
Ok(result) => {
|
||||||
|
result.print();
|
||||||
|
results.push(result);
|
||||||
|
},
|
||||||
|
Err(e) => println!("SET benchmark failed: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET benchmark
|
||||||
|
match tester.skytable_benchmark_get(ops, clients).await {
|
||||||
|
Ok(result) => {
|
||||||
|
result.print();
|
||||||
|
results.push(result);
|
||||||
|
},
|
||||||
|
Err(e) => println!("GET benchmark failed: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
// MIXED benchmark
|
||||||
|
match tester.skytable_benchmark_mixed(ops, clients, value_size).await {
|
||||||
|
Ok(result) => {
|
||||||
|
result.print();
|
||||||
|
results.push(result);
|
||||||
|
},
|
||||||
|
Err(e) => println!("MIXED benchmark failed: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Summary
|
||||||
|
println!("📈 SUMMARY");
|
||||||
|
println!("==========");
|
||||||
|
for result in &results {
|
||||||
|
let error_rate = if result.total_operations + result.errors > 0 {
|
||||||
|
format!("{:.1}%", (result.errors as f64 / (result.total_operations + result.errors) as f64) * 100.0)
|
||||||
|
} else {
|
||||||
|
"0.0%".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("{:8} | {:>10.0} ops/sec | {:>8.2}μs avg latency | {} errors ({})",
|
||||||
|
result.operation,
|
||||||
|
result.ops_per_second,
|
||||||
|
result.avg_latency_us,
|
||||||
|
result.errors,
|
||||||
|
error_rate
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(best) = results.iter().filter(|r| r.ops_per_second > 0.0).max_by(|a, b| a.ops_per_second.partial_cmp(&b.ops_per_second).unwrap()) {
|
||||||
|
println!();
|
||||||
|
println!("🏆 Best performance: {} with {:.0} ops/sec", best.operation, best.ops_per_second);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
1072
src/lib.rs
Normal file
1072
src/lib.rs
Normal file
File diff suppressed because it is too large
Load Diff
131
src/oversdb.rs
Normal file
131
src/oversdb.rs
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
use tokio::net::TcpStream;
|
||||||
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum CrudOperation {
|
||||||
|
Create = 0,
|
||||||
|
Read = 1,
|
||||||
|
Update = 2,
|
||||||
|
Delete = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct OversDBClient {
|
||||||
|
stream: TcpStream,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OversDBClient {
|
||||||
|
pub async fn connect(addr: &str) -> Result<Self, String> {
|
||||||
|
let stream = TcpStream::connect(addr).await.map_err(|e| e.to_string())?;
|
||||||
|
Ok(Self { stream })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_packet(
|
||||||
|
&mut self,
|
||||||
|
operation: CrudOperation,
|
||||||
|
db_name: &str,
|
||||||
|
table_name: &str,
|
||||||
|
key: &[u8],
|
||||||
|
payload: &[u8],
|
||||||
|
) -> Result<Vec<u8>, String> {
|
||||||
|
// Build packet according to protocol
|
||||||
|
let mut packet = Vec::new();
|
||||||
|
|
||||||
|
// [2 bits CRUD][6 bits reserved]
|
||||||
|
packet.push((operation.clone() as u8) << 6);
|
||||||
|
|
||||||
|
// [1 byte db_len][N bytes db_name]
|
||||||
|
packet.push(db_name.len() as u8);
|
||||||
|
packet.extend_from_slice(db_name.as_bytes());
|
||||||
|
|
||||||
|
// [1 byte table_len][N bytes table_name]
|
||||||
|
packet.push(table_name.len() as u8);
|
||||||
|
packet.extend_from_slice(table_name.as_bytes());
|
||||||
|
|
||||||
|
// [2 bytes key_len][N bytes key]
|
||||||
|
packet.extend_from_slice(&(key.len() as u16).to_be_bytes());
|
||||||
|
packet.extend_from_slice(key);
|
||||||
|
|
||||||
|
// [4 bytes payload_len][N bytes payload]
|
||||||
|
packet.extend_from_slice(&(payload.len() as u32).to_be_bytes());
|
||||||
|
packet.extend_from_slice(payload);
|
||||||
|
|
||||||
|
// Send packet
|
||||||
|
self.stream.write_all(&packet).await.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
// Read response
|
||||||
|
match operation {
|
||||||
|
CrudOperation::Read => {
|
||||||
|
// Read success byte
|
||||||
|
let mut success = [0u8; 1];
|
||||||
|
self.stream.read_exact(&mut success).await.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
if success[0] == 1 {
|
||||||
|
// Read value length
|
||||||
|
let mut len_bytes = [0u8; 4];
|
||||||
|
self.stream.read_exact(&mut len_bytes).await.map_err(|e| e.to_string())?;
|
||||||
|
let len = u32::from_be_bytes(len_bytes) as usize;
|
||||||
|
|
||||||
|
// Read value
|
||||||
|
let mut value = vec![0u8; len];
|
||||||
|
self.stream.read_exact(&mut value).await.map_err(|e| e.to_string())?;
|
||||||
|
Ok(value)
|
||||||
|
} else {
|
||||||
|
Err("Key not found".to_string())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
// Read success byte for Create/Update/Delete
|
||||||
|
let mut success = [0u8; 1];
|
||||||
|
self.stream.read_exact(&mut success).await.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
if success[0] == 1 {
|
||||||
|
Ok(vec![1])
|
||||||
|
} else {
|
||||||
|
Err("Operation failed".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn create(&mut self, db_name: &str, table_name: &str, key: &[u8], value: &[u8], ttl: u64) -> Result<(), String> {
|
||||||
|
let mut payload = Vec::new();
|
||||||
|
payload.extend_from_slice(&ttl.to_be_bytes());
|
||||||
|
payload.extend_from_slice(value);
|
||||||
|
|
||||||
|
let result = self.send_packet(CrudOperation::Create, db_name, table_name, key, &payload).await?;
|
||||||
|
|
||||||
|
if result[0] == 1 {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err("Create failed".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn read(&mut self, db_name: &str, table_name: &str, key: &[u8]) -> Result<Vec<u8>, String> {
|
||||||
|
self.send_packet(CrudOperation::Read, db_name, table_name, key, &[]).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn update(&mut self, db_name: &str, table_name: &str, key: &[u8], value: &[u8], ttl: u64) -> Result<(), String> {
|
||||||
|
let mut payload = Vec::new();
|
||||||
|
payload.extend_from_slice(&ttl.to_be_bytes());
|
||||||
|
payload.extend_from_slice(value);
|
||||||
|
|
||||||
|
let result = self.send_packet(CrudOperation::Update, db_name, table_name, key, &payload).await?;
|
||||||
|
|
||||||
|
if result[0] == 1 {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err("Update failed".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete(&mut self, db_name: &str, table_name: &str, key: &[u8]) -> Result<(), String> {
|
||||||
|
let result = self.send_packet(CrudOperation::Delete, db_name, table_name, key, &[]).await?;
|
||||||
|
|
||||||
|
if result[0] == 1 {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err("Delete failed".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
283
src/redis_client.rs
Normal file
283
src/redis_client.rs
Normal file
@ -0,0 +1,283 @@
|
|||||||
|
use redis::{Client, Connection, Commands, RedisResult, ConnectionLike, Cmd};
|
||||||
|
use tokio::task;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum RedisOperation {
|
||||||
|
Set,
|
||||||
|
Get,
|
||||||
|
Update,
|
||||||
|
Delete,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RedisClient {
|
||||||
|
client: Client,
|
||||||
|
connection: Option<Connection>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RedisClient {
|
||||||
|
pub async fn connect(addr: &str, password: &str) -> Result<Self, String> {
|
||||||
|
Self::connect_with_auth(addr, password).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn connect_with_auth(addr: &str, password: &str) -> Result<Self, String> {
|
||||||
|
let addr_clone = addr.to_string();
|
||||||
|
let password_clone = password.to_string();
|
||||||
|
|
||||||
|
// Create connection in blocking task since redis-rs is synchronous
|
||||||
|
let (client, connection) = task::spawn_blocking(move || {
|
||||||
|
let redis_url = if password_clone != "" {
|
||||||
|
format!("redis://:{}@{}", password_clone, addr_clone)
|
||||||
|
} else {
|
||||||
|
format!("redis://{}", addr_clone)
|
||||||
|
};
|
||||||
|
|
||||||
|
let client = Client::open(redis_url)
|
||||||
|
.map_err(|e| format!("Failed to create Redis client: {}", e))?;
|
||||||
|
|
||||||
|
let connection = client.get_connection()
|
||||||
|
.map_err(|e| format!("Failed to connect to Redis: {}", e))?;
|
||||||
|
|
||||||
|
Ok::<(Client, Connection), String>((client, connection))
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Task join error: {}", e))??;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
client,
|
||||||
|
connection: Some(connection),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set(&mut self, key: &str, value: &[u8]) -> Result<(), String> {
|
||||||
|
let key = key.to_string();
|
||||||
|
let value = value.to_vec();
|
||||||
|
|
||||||
|
let mut conn = self.connection.take().unwrap();
|
||||||
|
let result = task::spawn_blocking(move || {
|
||||||
|
// Use binary-safe SET command
|
||||||
|
let result: RedisResult<()> = conn.set(&key, &value);
|
||||||
|
(conn, result)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Task join error: {}", e))?;
|
||||||
|
|
||||||
|
self.connection = Some(result.0);
|
||||||
|
result.1.map_err(|e| format!("SET failed: {}", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get(&mut self, key: &str) -> Result<Vec<u8>, String> {
|
||||||
|
let key = key.to_string();
|
||||||
|
|
||||||
|
let mut conn = self.connection.take().unwrap();
|
||||||
|
let result = task::spawn_blocking(move || {
|
||||||
|
// Use binary-safe GET command
|
||||||
|
let result: RedisResult<Vec<u8>> = conn.get(&key);
|
||||||
|
(conn, result)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Task join error: {}", e))?;
|
||||||
|
|
||||||
|
self.connection = Some(result.0);
|
||||||
|
result.1.map_err(|e| format!("GET failed: {}", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn update(&mut self, key: &str, value: &[u8]) -> Result<(), String> {
|
||||||
|
// Redis UPDATE is the same as SET - it overwrites existing values
|
||||||
|
self.set(key, value).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete(&mut self, key: &str) -> Result<(), String> {
|
||||||
|
let key = key.to_string();
|
||||||
|
|
||||||
|
let mut conn = self.connection.take().unwrap();
|
||||||
|
let result = task::spawn_blocking(move || {
|
||||||
|
let result: RedisResult<i32> = conn.del(&key);
|
||||||
|
(conn, result)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Task join error: {}", e))?;
|
||||||
|
|
||||||
|
self.connection = Some(result.0);
|
||||||
|
result.1.map(|_| ()).map_err(|e| format!("DELETE failed: {}", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn exists(&mut self, key: &str) -> Result<bool, String> {
|
||||||
|
let key = key.to_string();
|
||||||
|
|
||||||
|
let mut conn = self.connection.take().unwrap();
|
||||||
|
let result = task::spawn_blocking(move || {
|
||||||
|
let result: RedisResult<bool> = conn.exists(&key);
|
||||||
|
(conn, result)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Task join error: {}", e))?;
|
||||||
|
|
||||||
|
self.connection = Some(result.0);
|
||||||
|
result.1.map_err(|e| format!("EXISTS failed: {}", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn ping(&mut self) -> Result<String, String> {
|
||||||
|
let mut conn = self.connection.take().unwrap();
|
||||||
|
let result = task::spawn_blocking(move || {
|
||||||
|
let result: RedisResult<String> = redis::cmd("PING").query(&mut conn);
|
||||||
|
(conn, result)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Task join error: {}", e))?;
|
||||||
|
|
||||||
|
self.connection = Some(result.0);
|
||||||
|
result.1.map_err(|e| format!("PING failed: {}", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn flush_db(&mut self) -> Result<(), String> {
|
||||||
|
let mut conn = self.connection.take().unwrap();
|
||||||
|
let result = task::spawn_blocking(move || {
|
||||||
|
let result: RedisResult<()> = redis::cmd("FLUSHDB").query(&mut conn);
|
||||||
|
(conn, result)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Task join error: {}", e))?;
|
||||||
|
|
||||||
|
self.connection = Some(result.0);
|
||||||
|
result.1.map_err(|e| format!("FLUSHDB failed: {}", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional utility methods for benchmarking
|
||||||
|
pub async fn set_with_expiry(&mut self, key: &str, value: &[u8], ttl_seconds: u64) -> Result<(), String> {
|
||||||
|
let key = key.to_string();
|
||||||
|
let value = value.to_vec();
|
||||||
|
|
||||||
|
let mut conn = self.connection.take().unwrap();
|
||||||
|
let result = task::spawn_blocking(move || {
|
||||||
|
let result: RedisResult<()> = conn.set_ex(&key, &value, ttl_seconds);
|
||||||
|
(conn, result)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Task join error: {}", e))?;
|
||||||
|
|
||||||
|
self.connection = Some(result.0);
|
||||||
|
result.1.map_err(|e| format!("SET with expiry failed: {}", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn mset(&mut self, pairs: &[(&str, &[u8])]) -> Result<(), String> {
|
||||||
|
if pairs.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let pairs: Vec<(String, Vec<u8>)> = pairs.iter()
|
||||||
|
.map(|(k, v)| (k.to_string(), v.to_vec()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut conn = self.connection.take().unwrap();
|
||||||
|
let result = task::spawn_blocking(move || {
|
||||||
|
let mut cmd = redis::cmd("MSET");
|
||||||
|
for (key, value) in &pairs {
|
||||||
|
cmd.arg(key).arg(value);
|
||||||
|
}
|
||||||
|
let result: RedisResult<()> = cmd.query(&mut conn);
|
||||||
|
(conn, result)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Task join error: {}", e))?;
|
||||||
|
|
||||||
|
self.connection = Some(result.0);
|
||||||
|
result.1.map_err(|e| format!("MSET failed: {}", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn mget(&mut self, keys: &[&str]) -> Result<Vec<Option<Vec<u8>>>, String> {
|
||||||
|
if keys.is_empty() {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
let keys: Vec<String> = keys.iter().map(|k| k.to_string()).collect();
|
||||||
|
|
||||||
|
let mut conn = self.connection.take().unwrap();
|
||||||
|
let result = task::spawn_blocking(move || {
|
||||||
|
let result: RedisResult<Vec<Option<Vec<u8>>>> = conn.get(&keys);
|
||||||
|
(conn, result)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Task join error: {}", e))?;
|
||||||
|
|
||||||
|
self.connection = Some(result.0);
|
||||||
|
result.1.map_err(|e| format!("MGET failed: {}", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn incr(&mut self, key: &str) -> Result<i64, String> {
|
||||||
|
let key = key.to_string();
|
||||||
|
|
||||||
|
let mut conn = self.connection.take().unwrap();
|
||||||
|
let result = task::spawn_blocking(move || {
|
||||||
|
let result: RedisResult<i64> = conn.incr(&key, 1);
|
||||||
|
(conn, result)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Task join error: {}", e))?;
|
||||||
|
|
||||||
|
self.connection = Some(result.0);
|
||||||
|
result.1.map_err(|e| format!("INCR failed: {}", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn ttl(&mut self, key: &str) -> Result<i64, String> {
|
||||||
|
let key = key.to_string();
|
||||||
|
|
||||||
|
let mut conn = self.connection.take().unwrap();
|
||||||
|
let result = task::spawn_blocking(move || {
|
||||||
|
let result: RedisResult<i64> = redis::cmd("TTL").arg(&key).query(&mut conn);
|
||||||
|
(conn, result)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Task join error: {}", e))?;
|
||||||
|
|
||||||
|
self.connection = Some(result.0);
|
||||||
|
result.1.map_err(|e| format!("TTL failed: {}", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get connection info for benchmarking
|
||||||
|
pub async fn info(&mut self, section: Option<&str>) -> Result<String, String> {
|
||||||
|
let section = section.map(|s| s.to_string());
|
||||||
|
|
||||||
|
let mut conn = self.connection.take().unwrap();
|
||||||
|
let result = task::spawn_blocking(move || {
|
||||||
|
let result: RedisResult<String> = match section {
|
||||||
|
Some(s) => redis::cmd("INFO").arg(s).query(&mut conn),
|
||||||
|
None => redis::cmd("INFO").query(&mut conn),
|
||||||
|
};
|
||||||
|
(conn, result)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Task join error: {}", e))?;
|
||||||
|
|
||||||
|
self.connection = Some(result.0);
|
||||||
|
result.1.map_err(|e| format!("INFO failed: {}", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pipeline for batch operations (advanced feature)
|
||||||
|
pub async fn pipeline_set(&mut self, operations: &[(&str, &[u8])]) -> Result<Vec<bool>, String> {
|
||||||
|
if operations.is_empty() {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
let operations: Vec<(String, Vec<u8>)> = operations.iter()
|
||||||
|
.map(|(k, v)| (k.to_string(), v.to_vec()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut conn = self.connection.take().unwrap();
|
||||||
|
let result = task::spawn_blocking(move || {
|
||||||
|
let mut pipe = redis::pipe();
|
||||||
|
let mut pipe = pipe.atomic();
|
||||||
|
|
||||||
|
for (key, value) in &operations {
|
||||||
|
pipe = pipe.set(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
let result: RedisResult<Vec<bool>> = pipe.query(&mut conn);
|
||||||
|
(conn, result)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Task join error: {}", e))?;
|
||||||
|
|
||||||
|
self.connection = Some(result.0);
|
||||||
|
result.1.map_err(|e| format!("Pipeline SET failed: {}", e))
|
||||||
|
}
|
||||||
|
}
|
135
src/skytable_client.rs
Normal file
135
src/skytable_client.rs
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
use skytable::{Config, query};
|
||||||
|
use tokio::task;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum SkytableOperation {
|
||||||
|
Set,
|
||||||
|
Get,
|
||||||
|
Update,
|
||||||
|
Delete,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SkytableClient {
|
||||||
|
connection: Option<skytable::Connection>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SkytableClient {
|
||||||
|
pub async fn connect(addr: &str, login: &str, password: &str) -> Result<Self, String> {
|
||||||
|
Self::connect_with_auth(addr, login, password).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn connect_with_auth(addr: &str, username: &str, password: &str) -> Result<Self, String> {
|
||||||
|
let addr_clone = addr.to_string();
|
||||||
|
let username_clone = username.to_string();
|
||||||
|
let password_clone = password.to_string();
|
||||||
|
|
||||||
|
// Create connection in blocking task since Skytable is synchronous
|
||||||
|
let connection = task::spawn_blocking(move || {
|
||||||
|
Config::new(&addr_clone, 2003, &username_clone, &password_clone).connect()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Task join error: {}", e))?
|
||||||
|
.map_err(|e| format!("Failed to connect to Skytable: {}", e))?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
connection: Some(connection),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set(&mut self, key: &str, value: &[u8]) -> Result<(), String> {
|
||||||
|
let key = key.to_string();
|
||||||
|
let value = value.to_vec();
|
||||||
|
|
||||||
|
let mut conn = self.connection.take().unwrap();
|
||||||
|
let result = task::spawn_blocking(move || {
|
||||||
|
let q = query!("INSERT INTO benchmark.kvstore (?, ?)", key, value);
|
||||||
|
let result = conn.query_parse::<()>(&q);
|
||||||
|
(conn, result)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Task join error: {}", e))?;
|
||||||
|
|
||||||
|
self.connection = Some(result.0);
|
||||||
|
result.1.map_err(|e| format!("SET failed: {}", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get(&mut self, key: &str) -> Result<(Vec<u8>,), String> {
|
||||||
|
let key = key.to_string();
|
||||||
|
|
||||||
|
let mut conn = self.connection.take().unwrap();
|
||||||
|
let result = task::spawn_blocking(move || {
|
||||||
|
let q = query!("SELECT v FROM benchmark.kvstore WHERE k = ?", key);
|
||||||
|
let result = conn.query_parse::<(Vec<u8>,)>(&q);
|
||||||
|
(conn, result)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Task join error: {}", e))?;
|
||||||
|
|
||||||
|
self.connection = Some(result.0);
|
||||||
|
result.1.map_err(|e| format!("GET failed: {}", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn update(&mut self, key: &str, value: &[u8]) -> Result<(), String> {
|
||||||
|
let key = key.to_string();
|
||||||
|
let value = value.to_vec();
|
||||||
|
|
||||||
|
let mut conn = self.connection.take().unwrap();
|
||||||
|
let result = task::spawn_blocking(move || {
|
||||||
|
let q = query!("UPDATE benchmark.kvstore SET v = ? WHERE k = ?", value, key);
|
||||||
|
let result = conn.query_parse::<()>(&q);
|
||||||
|
(conn, result)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Task join error: {}", e))?;
|
||||||
|
|
||||||
|
self.connection = Some(result.0);
|
||||||
|
result.1.map_err(|e| format!("UPDATE failed: {}", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete(&mut self, key: &str) -> Result<(), String> {
|
||||||
|
let key = key.to_string();
|
||||||
|
|
||||||
|
let mut conn = self.connection.take().unwrap();
|
||||||
|
let result = task::spawn_blocking(move || {
|
||||||
|
let q = query!("DELETE FROM benchmark.kvstore WHERE k = ?", key);
|
||||||
|
let result = conn.query_parse::<()>(&q);
|
||||||
|
(conn, result)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Task join error: {}", e))?;
|
||||||
|
|
||||||
|
self.connection = Some(result.0);
|
||||||
|
result.1.map_err(|e| format!("DELETE failed: {}", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn setup_benchmark_space(&mut self) -> Result<(), String> {
|
||||||
|
let mut conn = self.connection.take().unwrap();
|
||||||
|
let result = task::spawn_blocking(move || {
|
||||||
|
// Create space
|
||||||
|
let create_space = query!("CREATE SPACE IF NOT EXISTS benchmark");
|
||||||
|
let _ = conn.query_parse::<bool>(&create_space); // Ignore errors if exists
|
||||||
|
|
||||||
|
// Create model with binary support
|
||||||
|
let create_model = query!("CREATE MODEL IF NOT EXISTS benchmark.kvstore (k: string, v: binary)");
|
||||||
|
let result = conn.query_parse::<bool>(&create_model);
|
||||||
|
|
||||||
|
(conn, result)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Task join error: {}", e))?;
|
||||||
|
|
||||||
|
self.connection = Some(result.0);
|
||||||
|
|
||||||
|
// Ignore errors if space/model already exists
|
||||||
|
match result.1 {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(e) => {
|
||||||
|
if e.to_string().contains("already exists") || e.to_string().contains("exists") {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(format!("Setup failed: {}", e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user