Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
This commit is contained in:
434
crates/ruvector-graph/tests/performance_tests.rs
Normal file
434
crates/ruvector-graph/tests/performance_tests.rs
Normal file
@@ -0,0 +1,434 @@
|
||||
//! Performance and regression tests
|
||||
//!
|
||||
//! Benchmark tests to ensure performance doesn't degrade over time.
|
||||
|
||||
use ruvector_graph::{Edge, GraphDB, Label, Node, Properties, PropertyValue};
|
||||
use std::time::Instant;
|
||||
|
||||
// ============================================================================
|
||||
// Baseline Performance Tests
|
||||
// ============================================================================
|
||||
|
||||
#[test]
|
||||
fn test_node_creation_performance() {
|
||||
let db = GraphDB::new();
|
||||
let num_nodes = 10_000;
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..num_nodes {
|
||||
let mut props = Properties::new();
|
||||
props.insert("id".to_string(), PropertyValue::Integer(i));
|
||||
|
||||
let node = Node::new(
|
||||
format!("node_{}", i),
|
||||
vec![Label {
|
||||
name: "Benchmark".to_string(),
|
||||
}],
|
||||
props,
|
||||
);
|
||||
|
||||
db.create_node(node).unwrap();
|
||||
}
|
||||
|
||||
let duration = start.elapsed();
|
||||
|
||||
println!("Created {} nodes in {:?}", num_nodes, duration);
|
||||
println!(
|
||||
"Rate: {:.2} nodes/sec",
|
||||
num_nodes as f64 / duration.as_secs_f64()
|
||||
);
|
||||
|
||||
// Baseline: Should create at least 10k nodes/sec
|
||||
assert!(
|
||||
duration.as_secs() < 5,
|
||||
"Node creation too slow: {:?}",
|
||||
duration
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_node_retrieval_performance() {
|
||||
let db = GraphDB::new();
|
||||
let num_nodes = 10_000;
|
||||
|
||||
// Setup
|
||||
for i in 0..num_nodes {
|
||||
db.create_node(Node::new(format!("node_{}", i), vec![], Properties::new()))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Measure retrieval
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..num_nodes {
|
||||
let node = db.get_node(&format!("node_{}", i));
|
||||
assert!(node.is_some());
|
||||
}
|
||||
|
||||
let duration = start.elapsed();
|
||||
|
||||
println!("Retrieved {} nodes in {:?}", num_nodes, duration);
|
||||
println!(
|
||||
"Rate: {:.2} reads/sec",
|
||||
num_nodes as f64 / duration.as_secs_f64()
|
||||
);
|
||||
|
||||
// Should be very fast for in-memory lookups
|
||||
assert!(
|
||||
duration.as_secs() < 1,
|
||||
"Node retrieval too slow: {:?}",
|
||||
duration
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edge_creation_performance() {
|
||||
let db = GraphDB::new();
|
||||
let num_nodes = 1000;
|
||||
let edges_per_node = 10;
|
||||
|
||||
// Create nodes
|
||||
for i in 0..num_nodes {
|
||||
db.create_node(Node::new(format!("n{}", i), vec![], Properties::new()))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Create edges
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..num_nodes {
|
||||
for j in 0..edges_per_node {
|
||||
let to = (i + j + 1) % num_nodes;
|
||||
let edge = Edge::new(
|
||||
format!("e_{}_{}", i, j),
|
||||
format!("n{}", i),
|
||||
format!("n{}", to),
|
||||
"CONNECTS".to_string(),
|
||||
Properties::new(),
|
||||
);
|
||||
|
||||
db.create_edge(edge).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
let duration = start.elapsed();
|
||||
let total_edges = num_nodes * edges_per_node;
|
||||
|
||||
println!("Created {} edges in {:?}", total_edges, duration);
|
||||
println!(
|
||||
"Rate: {:.2} edges/sec",
|
||||
total_edges as f64 / duration.as_secs_f64()
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: Implement graph traversal methods
|
||||
// #[test]
|
||||
// fn test_traversal_performance() {
|
||||
// let db = GraphDB::new();
|
||||
// let num_nodes = 1000;
|
||||
//
|
||||
// // Create chain
|
||||
// for i in 0..num_nodes {
|
||||
// db.create_node(Node::new(format!("n{}", i), vec![], Properties::new())).unwrap();
|
||||
// }
|
||||
//
|
||||
// for i in 0..num_nodes - 1 {
|
||||
// db.create_edge(Edge::new(
|
||||
// format!("e{}", i),
|
||||
// format!("n{}", i),
|
||||
// format!("n{}", i + 1),
|
||||
// RelationType { name: "NEXT".to_string() },
|
||||
// Properties::new(),
|
||||
// )).unwrap();
|
||||
// }
|
||||
//
|
||||
// // Measure traversal
|
||||
// let start = Instant::now();
|
||||
// let path = db.traverse("n0", "NEXT", 100).unwrap();
|
||||
// let duration = start.elapsed();
|
||||
//
|
||||
// assert_eq!(path.len(), 100);
|
||||
// println!("Traversed 100 hops in {:?}", duration);
|
||||
// }
|
||||
|
||||
// ============================================================================
|
||||
// Scalability Tests
|
||||
// ============================================================================
|
||||
|
||||
#[test]
|
||||
fn test_large_graph_creation() {
|
||||
let db = GraphDB::new();
|
||||
let num_nodes = 100_000;
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..num_nodes {
|
||||
if i % 10_000 == 0 {
|
||||
println!("Created {} nodes...", i);
|
||||
}
|
||||
|
||||
let node = Node::new(format!("large_{}", i), vec![], Properties::new());
|
||||
|
||||
db.create_node(node).unwrap();
|
||||
}
|
||||
|
||||
let duration = start.elapsed();
|
||||
|
||||
println!("Created {} nodes in {:?}", num_nodes, duration);
|
||||
println!(
|
||||
"Rate: {:.2} nodes/sec",
|
||||
num_nodes as f64 / duration.as_secs_f64()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore] // Long-running test
|
||||
fn test_million_node_graph() {
|
||||
let db = GraphDB::new();
|
||||
let num_nodes = 1_000_000;
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..num_nodes {
|
||||
if i % 100_000 == 0 {
|
||||
println!("Created {} nodes...", i);
|
||||
}
|
||||
|
||||
let node = Node::new(format!("mega_{}", i), vec![], Properties::new());
|
||||
|
||||
db.create_node(node).unwrap();
|
||||
}
|
||||
|
||||
let duration = start.elapsed();
|
||||
|
||||
println!("Created {} nodes in {:?}", num_nodes, duration);
|
||||
println!(
|
||||
"Rate: {:.2} nodes/sec",
|
||||
num_nodes as f64 / duration.as_secs_f64()
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Memory Usage Tests
|
||||
// ============================================================================
|
||||
|
||||
#[test]
|
||||
fn test_memory_efficiency() {
|
||||
let db = GraphDB::new();
|
||||
let num_nodes = 10_000;
|
||||
|
||||
for i in 0..num_nodes {
|
||||
let mut props = Properties::new();
|
||||
props.insert("data".to_string(), PropertyValue::String("x".repeat(100)));
|
||||
|
||||
let node = Node::new(format!("mem_{}", i), vec![], props);
|
||||
|
||||
db.create_node(node).unwrap();
|
||||
}
|
||||
|
||||
// TODO: Measure actual memory usage
|
||||
// This would require platform-specific APIs
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Property-based Performance Tests
|
||||
// ============================================================================
|
||||
|
||||
#[test]
|
||||
fn test_property_heavy_nodes() {
|
||||
let db = GraphDB::new();
|
||||
let num_nodes = 1_000;
|
||||
let props_per_node = 50;
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..num_nodes {
|
||||
let mut props = Properties::new();
|
||||
|
||||
for j in 0..props_per_node {
|
||||
props.insert(format!("prop_{}", j), PropertyValue::Integer(j as i64));
|
||||
}
|
||||
|
||||
let node = Node::new(format!("heavy_{}", i), vec![], props);
|
||||
|
||||
db.create_node(node).unwrap();
|
||||
}
|
||||
|
||||
let duration = start.elapsed();
|
||||
|
||||
println!(
|
||||
"Created {} property-heavy nodes in {:?}",
|
||||
num_nodes, duration
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Query Performance Tests (TODO)
|
||||
// ============================================================================
|
||||
|
||||
// #[test]
|
||||
// fn test_simple_query_performance() {
|
||||
// let db = setup_benchmark_graph(10_000);
|
||||
//
|
||||
// let start = Instant::now();
|
||||
// let results = db.execute("MATCH (n:Person) RETURN n LIMIT 100").unwrap();
|
||||
// let duration = start.elapsed();
|
||||
//
|
||||
// assert_eq!(results.len(), 100);
|
||||
// println!("Simple query took: {:?}", duration);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_aggregation_performance() {
|
||||
// let db = setup_benchmark_graph(100_000);
|
||||
//
|
||||
// let start = Instant::now();
|
||||
// let results = db.execute("MATCH (n:Person) RETURN COUNT(n)").unwrap();
|
||||
// let duration = start.elapsed();
|
||||
//
|
||||
// println!("Aggregation over 100k nodes took: {:?}", duration);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_join_performance() {
|
||||
// let db = setup_benchmark_graph(10_000);
|
||||
//
|
||||
// let start = Instant::now();
|
||||
// let results = db.execute("
|
||||
// MATCH (a:Person)-[:KNOWS]->(b:Person)
|
||||
// WHERE a.age > 30
|
||||
// RETURN a, b
|
||||
// ").unwrap();
|
||||
// let duration = start.elapsed();
|
||||
//
|
||||
// println!("Join query took: {:?}", duration);
|
||||
// }
|
||||
|
||||
// ============================================================================
|
||||
// Index Performance Tests (TODO)
|
||||
// ============================================================================
|
||||
|
||||
// #[test]
|
||||
// fn test_indexed_lookup_performance() {
|
||||
// let db = GraphDB::new();
|
||||
//
|
||||
// // Create index
|
||||
// db.create_index("Person", "email").unwrap();
|
||||
//
|
||||
// // Insert data
|
||||
// for i in 0..100_000 {
|
||||
// db.execute(&format!(
|
||||
// "CREATE (:Person {{email: 'user{}@example.com'}})",
|
||||
// i
|
||||
// )).unwrap();
|
||||
// }
|
||||
//
|
||||
// // Measure lookup
|
||||
// let start = Instant::now();
|
||||
// let results = db.execute("MATCH (n:Person {email: 'user50000@example.com'}) RETURN n").unwrap();
|
||||
// let duration = start.elapsed();
|
||||
//
|
||||
// assert_eq!(results.len(), 1);
|
||||
// println!("Indexed lookup took: {:?}", duration);
|
||||
// assert!(duration.as_millis() < 10); // Should be very fast
|
||||
// }
|
||||
|
||||
// ============================================================================
|
||||
// Regression Tests
|
||||
// ============================================================================
|
||||
|
||||
#[test]
|
||||
fn test_regression_node_creation() {
|
||||
let db = GraphDB::new();
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..1000 {
|
||||
db.create_node(Node::new(format!("regr_{}", i), vec![], Properties::new()))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let duration = start.elapsed();
|
||||
|
||||
// Baseline threshold - should not regress beyond this
|
||||
// Adjust based on baseline measurements
|
||||
assert!(
|
||||
duration.as_millis() < 500,
|
||||
"Regression detected: {:?}",
|
||||
duration
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_regression_node_retrieval() {
|
||||
let db = GraphDB::new();
|
||||
|
||||
// Setup
|
||||
for i in 0..1000 {
|
||||
db.create_node(Node::new(format!("regr_{}", i), vec![], Properties::new()))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..1000 {
|
||||
let _ = db.get_node(&format!("regr_{}", i));
|
||||
}
|
||||
|
||||
let duration = start.elapsed();
|
||||
|
||||
// Should be very fast
|
||||
assert!(
|
||||
duration.as_millis() < 100,
|
||||
"Regression detected: {:?}",
|
||||
duration
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Helper Functions
|
||||
// ============================================================================
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn setup_benchmark_graph(num_nodes: usize) -> GraphDB {
|
||||
let db = GraphDB::new();
|
||||
|
||||
for i in 0..num_nodes {
|
||||
let mut props = Properties::new();
|
||||
props.insert(
|
||||
"name".to_string(),
|
||||
PropertyValue::String(format!("Person{}", i)),
|
||||
);
|
||||
props.insert(
|
||||
"age".to_string(),
|
||||
PropertyValue::Integer((20 + (i % 60)) as i64),
|
||||
);
|
||||
|
||||
db.create_node(Node::new(
|
||||
format!("person_{}", i),
|
||||
vec![Label {
|
||||
name: "Person".to_string(),
|
||||
}],
|
||||
props,
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Create some edges
|
||||
for i in 0..num_nodes / 10 {
|
||||
let from = i;
|
||||
let to = (i + 1) % num_nodes;
|
||||
|
||||
db.create_edge(Edge::new(
|
||||
format!("knows_{}", i),
|
||||
format!("person_{}", from),
|
||||
format!("person_{}", to),
|
||||
"KNOWS".to_string(),
|
||||
Properties::new(),
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
db
|
||||
}
|
||||
Reference in New Issue
Block a user