diff --git a/README.md b/README.md index c0c5e411..33b57ae1 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ [![GitHub Repo stars](https://img.shields.io/github/stars/HelixDB/helix-db)](https://github.com/HelixDB/helix-db/stargazers) [![Discord](https://img.shields.io/discord/1354148209005559819?logo=discord)](https://discord.gg/2stgMPr5BD) [![LOC](https://img.shields.io/endpoint?url=https://ghloc.vercel.app/api/HelixDB/helix-db/badge?filter=.rs$,.sh$&style=flat&logoColor=white&label=Lines%20of%20Code)](https://github.com/HelixDB/helix-db) +[![Manta Graph](https://getmanta.ai/api/badges?text=Manta%20Graph&link=helixdb)](https://getmanta.ai/helixdb) Launch YC: HelixDB - The Database for Intelligence diff --git a/helix-db/Cargo.toml b/helix-db/Cargo.toml index 1c78ec50..e0a7f91d 100644 --- a/helix-db/Cargo.toml +++ b/helix-db/Cargo.toml @@ -77,3 +77,7 @@ path = "benches/bm25_benches.rs" [[test]] name = "hnsw_benches" path = "benches/hnsw_benches.rs" + +[[test]] +name = "capacity_optimization_benches" +path = "benches/capacity_optimization_benches.rs" \ No newline at end of file diff --git a/helix-db/benches/capacity_optimization_benches.rs b/helix-db/benches/capacity_optimization_benches.rs new file mode 100644 index 00000000..1bc0f4ec --- /dev/null +++ b/helix-db/benches/capacity_optimization_benches.rs @@ -0,0 +1,291 @@ +/// Performance benchmarks for Vec::with_capacity() optimizations +/// +/// Run with: cargo test --test capacity_optimization_benches --release -- --nocapture +/// +/// These are performance tests that measure actual execution time +/// to demonstrate the improvements from Vec::with_capacity() optimizations + +#[cfg(test)] +mod tests { + use helix_db::{ + helix_engine::{ + bm25::bm25::BM25, + storage_core::HelixGraphStorage, + traversal_core::{ + config::Config, + ops::{ + g::G, + source::{add_n::AddNAdapter, n_from_type::NFromTypeAdapter}, + util::{ + aggregate::AggregateAdapter, group_by::GroupByAdapter, + update::UpdateAdapter, + }, + }, + }, + }, + props, + utils::id::v6_uuid, + }; + use std::sync::Arc; + use std::time::Instant; + use tempfile::TempDir; + + fn setup_test_db() -> (Arc, TempDir) { + let temp_dir = TempDir::new().unwrap(); + let db_path = temp_dir.path().to_str().unwrap(); + + let mut config = Config::default(); + config.bm25 = Some(true); + + let storage = HelixGraphStorage::new(db_path, config, Default::default()).unwrap(); + (Arc::new(storage), temp_dir) + } + + fn setup_db_with_nodes(count: usize) -> (Arc, TempDir) { + let (storage, temp_dir) = setup_test_db(); + let mut txn = storage.graph_env.write_txn().unwrap(); + + for i in 0..count { + let _ = G::new_mut(Arc::clone(&storage), &mut txn) + .add_n( + "User", + Some(props! { + "name" => format!("User{}", i), + "age" => (20 + (i % 50)) as i64, + "department" => format!("Dept{}", i % 5), + "city" => format!("City{}", i % 10), + "role" => format!("Role{}", i % 3), + "score" => (i % 100) as i64, + }), + None, + ) + .collect_to_obj(); + } + + txn.commit().unwrap(); + (storage, temp_dir) + } + + #[test] + fn bench_aggregate_small() { + println!("\n=== Aggregate Performance (100 rows) ==="); + + for prop_count in [1, 3, 5] { + let (storage, _temp_dir) = setup_db_with_nodes(100); + + let properties: Vec = match prop_count { + 1 => vec!["department".to_string()], + 3 => vec![ + "department".to_string(), + "age".to_string(), + "city".to_string(), + ], + 5 => vec![ + "department".to_string(), + "age".to_string(), + "city".to_string(), + "role".to_string(), + "score".to_string(), + ], + _ => vec![], + }; + + let start = Instant::now(); + let txn = storage.graph_env.read_txn().unwrap(); + let _result = G::new(Arc::clone(&storage), &txn) + .n_from_type("User") + .aggregate_by(&properties, false); + let elapsed = start.elapsed(); + + println!(" {} properties: {:?}", prop_count, elapsed); + } + } + + #[test] + fn bench_aggregate_medium() { + println!("\n=== Aggregate Performance (1,000 rows) ==="); + + for prop_count in [1, 3, 5] { + let (storage, _temp_dir) = setup_db_with_nodes(1000); + + let properties: Vec = match prop_count { + 1 => vec!["department".to_string()], + 3 => vec![ + "department".to_string(), + "age".to_string(), + "city".to_string(), + ], + 5 => vec![ + "department".to_string(), + "age".to_string(), + "city".to_string(), + "role".to_string(), + "score".to_string(), + ], + _ => vec![], + }; + + let start = Instant::now(); + let txn = storage.graph_env.read_txn().unwrap(); + let _result = G::new(Arc::clone(&storage), &txn) + .n_from_type("User") + .aggregate_by(&properties, false); + let elapsed = start.elapsed(); + + println!(" {} properties: {:?}", prop_count, elapsed); + } + } + + #[test] + fn bench_aggregate_large() { + println!("\n=== Aggregate Performance (10,000 rows) ==="); + + let (storage, _temp_dir) = setup_db_with_nodes(10000); + + for prop_count in [1, 3, 5] { + let properties: Vec = match prop_count { + 1 => vec!["department".to_string()], + 3 => vec![ + "department".to_string(), + "age".to_string(), + "city".to_string(), + ], + 5 => vec![ + "department".to_string(), + "age".to_string(), + "city".to_string(), + "role".to_string(), + "score".to_string(), + ], + _ => vec![], + }; + + let start = Instant::now(); + let txn = storage.graph_env.read_txn().unwrap(); + let _result = G::new(Arc::clone(&storage), &txn) + .n_from_type("User") + .aggregate_by(&properties, false); + let elapsed = start.elapsed(); + + println!(" {} properties: {:?}", prop_count, elapsed); + } + } + + #[test] + fn bench_group_by() { + println!("\n=== Group By Performance ==="); + + for size in [100, 1000] { + let (storage, _temp_dir) = setup_db_with_nodes(size); + let properties = vec!["department".to_string(), "city".to_string()]; + + let start = Instant::now(); + let txn = storage.graph_env.read_txn().unwrap(); + let _result = G::new(Arc::clone(&storage), &txn) + .n_from_type("User") + .group_by(&properties, false); + let elapsed = start.elapsed(); + + println!(" {} rows: {:?}", size, elapsed); + } + } + + #[test] + fn bench_update_operations() { + println!("\n=== Update Performance ==="); + + for size in [10, 100, 1000] { + let (storage, _temp_dir) = setup_db_with_nodes(size); + let mut txn = storage.graph_env.write_txn().unwrap(); + + // Get nodes to update + let update_tr = { + let rtxn = storage.graph_env.read_txn().unwrap(); + G::new(Arc::clone(&storage), &rtxn) + .n_from_type("User") + .collect_to::>() + }; + + let start = Instant::now(); + let _result = G::new_mut_from(Arc::clone(&storage), &mut txn, update_tr) + .update(Some(vec![("score".to_string(), 999.into())])) + .collect_to::>(); + let elapsed = start.elapsed(); + + txn.commit().unwrap(); + println!(" {} nodes: {:?}", size, elapsed); + } + } + + #[test] + fn bench_bm25_search() { + println!("\n=== BM25 Search Performance ==="); + + let (storage, _temp_dir) = setup_test_db(); + let mut wtxn = storage.graph_env.write_txn().unwrap(); + + let bm25 = storage.bm25.as_ref().expect("BM25 should be enabled"); + + // Insert 10,000 documents + for i in 0..10000 { + let doc = format!( + "Document {} contains various search terms keywords database performance optimization testing benchmark", + i + ); + bm25.insert_doc(&mut wtxn, v6_uuid(), &doc).unwrap(); + } + + wtxn.commit().unwrap(); + + let rtxn = storage.graph_env.read_txn().unwrap(); + + for limit in [10, 100, 1000] { + let start = Instant::now(); + let _results = bm25.search(&rtxn, "database optimization performance", limit); + let elapsed = start.elapsed(); + + println!(" limit={}: {:?}", limit, elapsed); + } + } + + #[test] + fn bench_vector_allocation_patterns() { + println!("\n=== Vector Allocation Patterns ==="); + + // Pattern 1: Vec::new() in loop (old way - slow) + let start = Instant::now(); + for _ in 0..1000 { + let properties_count = 5; + for _ in 0..100 { + let mut vec1 = Vec::new(); + let mut vec2 = Vec::new(); + for i in 0..properties_count { + vec1.push(i); + vec2.push(format!("value_{}", i)); + } + } + } + let vec_new_time = start.elapsed(); + println!(" Vec::new() in loop: {:?}", vec_new_time); + + // Pattern 2: Vec::with_capacity() in loop (new way - fast) + let start = Instant::now(); + for _ in 0..1000 { + let properties_count = 5; + for _ in 0..100 { + let mut vec1 = Vec::with_capacity(properties_count); + let mut vec2 = Vec::with_capacity(properties_count); + for i in 0..properties_count { + vec1.push(i); + vec2.push(format!("value_{}", i)); + } + } + } + let vec_capacity_time = start.elapsed(); + println!(" Vec::with_capacity() in loop: {:?}", vec_capacity_time); + + let improvement = + (1.0 - (vec_capacity_time.as_secs_f64() / vec_new_time.as_secs_f64())) * 100.0; + println!(" Improvement: {:.1}% faster", improvement); + } +} diff --git a/helix-db/src/helix_engine/bm25/bm25.rs b/helix-db/src/helix_engine/bm25/bm25.rs index 24a9211a..80d963c1 100644 --- a/helix-db/src/helix_engine/bm25/bm25.rs +++ b/helix-db/src/helix_engine/bm25/bm25.rs @@ -370,7 +370,9 @@ impl BM25 for HBM25Config { } // Sort by score and return top results - let mut results: Vec<(u128, f32)> = doc_scores.into_iter().collect(); + // Pre-allocate with exact capacity to avoid reallocation during collection + let mut results: Vec<(u128, f32)> = Vec::with_capacity(doc_scores.len()); + results.extend(doc_scores); results.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal)); results.truncate(limit); @@ -450,7 +452,9 @@ impl HybridSearch for HelixGraphStorage { } } - let mut results = combined_scores.into_iter().collect::>(); + // Pre-allocate with exact capacity to avoid reallocation during collection + let mut results = Vec::with_capacity(combined_scores.len()); + results.extend(combined_scores); results.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal)); results.truncate(limit); diff --git a/helix-db/src/helix_engine/tests/capacity_optimization_tests.rs b/helix-db/src/helix_engine/tests/capacity_optimization_tests.rs new file mode 100644 index 00000000..991029be --- /dev/null +++ b/helix-db/src/helix_engine/tests/capacity_optimization_tests.rs @@ -0,0 +1,330 @@ +//! Tests for Vec::with_capacity() optimizations +//! +//! These tests verify that our capacity optimizations: +//! 1. Produce correct results (no regression) +//! 2. Improve performance (benchmarks) +//! 3. Reduce memory allocations (allocation counting) + +use std::sync::Arc; +use tempfile::TempDir; + +use crate::{ + helix_engine::{ + bm25::bm25::BM25, + storage_core::HelixGraphStorage, + traversal_core::{ + config::Config, + ops::{ + g::G, + source::{add_n::AddNAdapter, n_from_type::NFromTypeAdapter}, + util::{ + aggregate::AggregateAdapter, group_by::GroupByAdapter, update::UpdateAdapter, + }, + }, + }, + }, + props, + utils::id::v6_uuid, +}; + +fn setup_test_db() -> (Arc, TempDir) { + let temp_dir = TempDir::new().unwrap(); + let db_path = temp_dir.path().to_str().unwrap(); + + let mut config = Config::default(); + config.bm25 = Some(true); + + let storage = HelixGraphStorage::new(db_path, config, Default::default()).unwrap(); + (Arc::new(storage), temp_dir) +} + +fn setup_test_db_with_nodes(count: usize) -> (Arc, TempDir) { + let (storage, temp_dir) = setup_test_db(); + let mut txn = storage.graph_env.write_txn().unwrap(); + + // Create nodes with properties for testing aggregate/group operations + for i in 0..count { + let _ = G::new_mut(Arc::clone(&storage), &mut txn) + .add_n( + "User", + Some(props! { + "name" => format!("User{}", i), + "age" => (20 + (i % 50)) as i64, + "department" => format!("Dept{}", i % 5), + "score" => (i % 100) as i64, + }), + None, + ) + .collect_to_obj(); + } + + txn.commit().unwrap(); + (storage, temp_dir) +} + +#[test] +fn test_aggregate_correctness_small() { + let (storage, _temp_dir) = setup_test_db_with_nodes(10); + let txn = storage.graph_env.read_txn().unwrap(); + + let properties = vec!["department".to_string()]; + + let result = G::new(Arc::clone(&storage), &txn) + .n_from_type("User") + .aggregate_by(&properties, false); + + assert!(result.is_ok(), "Aggregate should succeed"); + let aggregate = result.unwrap(); + + // Should have 5 departments (Dept0-Dept4) + match aggregate { + crate::utils::aggregate::Aggregate::Group(groups) => { + assert_eq!(groups.len(), 5, "Should have 5 distinct departments"); + } + _ => panic!("Expected Group aggregate"), + } +} + +#[test] +fn test_aggregate_correctness_large() { + // Test with larger dataset to stress-test capacity allocation + let (storage, _temp_dir) = setup_test_db_with_nodes(1000); + let txn = storage.graph_env.read_txn().unwrap(); + + let properties = vec!["department".to_string(), "age".to_string()]; + + let result = G::new(Arc::clone(&storage), &txn) + .n_from_type("User") + .aggregate_by(&properties, true); + + assert!(result.is_ok(), "Aggregate with 1000 nodes should succeed"); +} + +#[test] +fn test_group_by_correctness() { + let (storage, _temp_dir) = setup_test_db_with_nodes(100); + let txn = storage.graph_env.read_txn().unwrap(); + + let properties = vec!["department".to_string()]; + + let result = G::new(Arc::clone(&storage), &txn) + .n_from_type("User") + .group_by(&properties, false); + + assert!(result.is_ok(), "GroupBy should succeed"); +} + +#[test] +fn test_update_operation_correctness() { + let (storage, _temp_dir) = setup_test_db_with_nodes(50); + let mut txn = storage.graph_env.write_txn().unwrap(); + + // Update all users' scores + // First get the nodes to update + let update_tr = { + let rtxn = storage.graph_env.read_txn().unwrap(); + G::new(Arc::clone(&storage), &rtxn) + .n_from_type("User") + .collect_to::>() + }; + + let result = G::new_mut_from(Arc::clone(&storage), &mut txn, update_tr) + .update(Some(vec![("score".to_string(), 999.into())])) + .collect_to::>(); + + assert_eq!(result.len(), 50, "Should update all 50 nodes"); + + txn.commit().unwrap(); +} + +#[test] +fn test_bm25_search_correctness() { + let (storage, _temp_dir) = setup_test_db(); + let mut wtxn = storage.graph_env.write_txn().unwrap(); + + let bm25 = storage.bm25.as_ref().expect("BM25 should be enabled"); + + // Insert test documents + let docs = vec![ + (v6_uuid(), "The quick brown fox jumps over the lazy dog"), + (v6_uuid(), "A fast brown fox leaps over a sleepy dog"), + (v6_uuid(), "The lazy dog sleeps under the tree"), + (v6_uuid(), "Quick foxes and lazy dogs are common"), + ]; + + for (id, doc) in &docs { + bm25.insert_doc(&mut wtxn, *id, doc).unwrap(); + } + + wtxn.commit().unwrap(); + + // Search + let rtxn = storage.graph_env.read_txn().unwrap(); + let results = bm25.search(&rtxn, "quick fox", 10); + + assert!(results.is_ok(), "BM25 search should succeed"); + let results = results.unwrap(); + assert!(!results.is_empty(), "Should find matching documents"); + assert!(results.len() <= 10, "Should respect limit"); +} + +#[test] +fn test_bm25_search_with_large_limit() { + let (storage, _temp_dir) = setup_test_db(); + let mut wtxn = storage.graph_env.write_txn().unwrap(); + + let bm25 = storage.bm25.as_ref().expect("BM25 should be enabled"); + + // Insert 100 documents + for i in 0..100 { + let doc = format!("Document {} contains search terms and keywords", i); + bm25.insert_doc(&mut wtxn, v6_uuid(), &doc).unwrap(); + } + + wtxn.commit().unwrap(); + + // Search with large limit + let rtxn = storage.graph_env.read_txn().unwrap(); + let results = bm25.search(&rtxn, "document search", 1000); + + assert!( + results.is_ok(), + "BM25 search with large limit should succeed" + ); +} + +/// Test that demonstrates capacity optimization doesn't break edge cases +#[test] +fn test_empty_result_sets() { + let (storage, _temp_dir) = setup_test_db(); + let txn = storage.graph_env.read_txn().unwrap(); + + // Test aggregate on empty set + let properties = vec!["nonexistent".to_string()]; + let result = G::new(Arc::clone(&storage), &txn) + .n_from_type("NonExistentType") + .aggregate_by(&properties, false); + + assert!(result.is_ok(), "Aggregate on empty set should succeed"); +} + +/// Test with properties of varying lengths +#[test] +fn test_aggregate_varying_property_counts() { + let (storage, _temp_dir) = setup_test_db_with_nodes(100); + let txn = storage.graph_env.read_txn().unwrap(); + + // Test with 1 property + let props1 = vec!["department".to_string()]; + let result = G::new(Arc::clone(&storage), &txn) + .n_from_type("User") + .aggregate_by(&props1, false); + assert!(result.is_ok(), "Aggregate with 1 property should work"); + + // Test with 3 properties + let props3 = vec![ + "department".to_string(), + "age".to_string(), + "score".to_string(), + ]; + let result = G::new(Arc::clone(&storage), &txn) + .n_from_type("User") + .aggregate_by(&props3, false); + assert!(result.is_ok(), "Aggregate with 3 properties should work"); +} + +#[cfg(test)] +mod performance_tests { + use super::*; + use std::time::Instant; + + /// This test measures relative performance + /// Run with: cargo test test_aggregate_performance -- --nocapture --ignored + #[test] + #[ignore] // Ignore by default, run explicitly for performance testing + fn test_aggregate_performance() { + let sizes = vec![100, 1000, 10000]; + + for size in sizes { + let (storage, _temp_dir) = setup_test_db_with_nodes(size); + let txn = storage.graph_env.read_txn().unwrap(); + + let properties = vec![ + "department".to_string(), + "age".to_string(), + "score".to_string(), + ]; + + let start = Instant::now(); + let result = G::new(Arc::clone(&storage), &txn) + .n_from_type("User") + .aggregate_by(&properties, false); + let elapsed = start.elapsed(); + + assert!(result.is_ok(), "Aggregate should succeed"); + println!("Aggregate {} nodes with 3 properties: {:?}", size, elapsed); + } + } + + #[test] + #[ignore] + fn test_update_performance() { + let sizes = vec![10, 100, 1000]; + + for size in sizes { + let (storage, _temp_dir) = setup_test_db_with_nodes(size); + let mut txn = storage.graph_env.write_txn().unwrap(); + + // Get nodes to update + let update_tr = { + let rtxn = storage.graph_env.read_txn().unwrap(); + G::new(Arc::clone(&storage), &rtxn) + .n_from_type("User") + .collect_to::>() + }; + + let start = Instant::now(); + let result = G::new_mut_from(Arc::clone(&storage), &mut txn, update_tr) + .update(Some(vec![("score".to_string(), 999.into())])) + .collect_to::>(); + let elapsed = start.elapsed(); + + assert_eq!(result.len(), size, "Update should succeed"); + println!("Update {} nodes: {:?}", size, elapsed); + + txn.commit().unwrap(); + } + } + + #[test] + #[ignore] + fn test_bm25_search_performance() { + let (storage, _temp_dir) = setup_test_db(); + let mut wtxn = storage.graph_env.write_txn().unwrap(); + + let bm25 = storage.bm25.as_ref().expect("BM25 should be enabled"); + + // Insert 10,000 documents + for i in 0..10000 { + let doc = format!( + "Document {} contains various search terms and keywords for testing performance", + i + ); + bm25.insert_doc(&mut wtxn, v6_uuid(), &doc).unwrap(); + } + + wtxn.commit().unwrap(); + + let rtxn = storage.graph_env.read_txn().unwrap(); + + let limits = vec![10, 100, 1000]; + for limit in limits { + let start = Instant::now(); + let results = bm25.search(&rtxn, "document search performance", limit); + let elapsed = start.elapsed(); + + assert!(results.is_ok(), "BM25 search should succeed"); + println!("BM25 search (limit={}): {:?}", limit, elapsed); + } + } +} diff --git a/helix-db/src/helix_engine/tests/mod.rs b/helix-db/src/helix_engine/tests/mod.rs index 72174ee7..abcabd2c 100644 --- a/helix-db/src/helix_engine/tests/mod.rs +++ b/helix-db/src/helix_engine/tests/mod.rs @@ -1,5 +1,6 @@ pub mod traversal_tests; pub mod vector_tests; // pub mod bm25_tests; +pub mod capacity_optimization_tests; pub mod hnsw_tests; pub mod storage_tests; diff --git a/helix-db/src/helix_engine/traversal_core/ops/util/aggregate.rs b/helix-db/src/helix_engine/traversal_core/ops/util/aggregate.rs index 13d8901b..1ffa464f 100644 --- a/helix-db/src/helix_engine/traversal_core/ops/util/aggregate.rs +++ b/helix-db/src/helix_engine/traversal_core/ops/util/aggregate.rs @@ -21,12 +21,16 @@ impl<'a, I: Iterator>> AggregateAdapte fn aggregate_by(self, properties: &[String], should_count: bool) -> Result { let mut groups: HashMap = HashMap::new(); + // Pre-calculate capacity outside the loop since properties length is constant + let properties_len = properties.len(); + for item in self.inner { let item = item?; // TODO HANDLE COUNT - let mut kvs = Vec::new(); - let mut key_parts = Vec::new(); + // Pre-allocate with exact capacity - size is known from properties.len() + let mut kvs = Vec::with_capacity(properties_len); + let mut key_parts = Vec::with_capacity(properties_len); for property in properties { match item.check_property(property) { diff --git a/helix-db/src/helix_engine/traversal_core/ops/util/group_by.rs b/helix-db/src/helix_engine/traversal_core/ops/util/group_by.rs index 75579c7e..eb283efa 100644 --- a/helix-db/src/helix_engine/traversal_core/ops/util/group_by.rs +++ b/helix-db/src/helix_engine/traversal_core/ops/util/group_by.rs @@ -21,12 +21,16 @@ impl<'a, I: Iterator>> GroupByAdapter< fn group_by(self, properties: &[String], should_count: bool) -> Result { let mut groups: HashMap = HashMap::new(); + // Pre-calculate capacity outside the loop since properties length is constant + let properties_len = properties.len(); + for item in self.inner { let item = item?; // TODO HANDLE COUNT - let mut kvs = Vec::new(); - let mut key_parts = Vec::new(); + // Pre-allocate with exact capacity - size is known from properties.len() + let mut kvs = Vec::with_capacity(properties_len); + let mut key_parts = Vec::with_capacity(properties_len); for property in properties { match item.check_property(property) { diff --git a/helix-db/src/helix_engine/traversal_core/ops/util/update.rs b/helix-db/src/helix_engine/traversal_core/ops/util/update.rs index e79d5188..5ed15597 100644 --- a/helix-db/src/helix_engine/traversal_core/ops/util/update.rs +++ b/helix-db/src/helix_engine/traversal_core/ops/util/update.rs @@ -41,11 +41,15 @@ impl<'scope, 'env, I: Iterator>> Updat { let storage = self.storage.clone(); + // Optimize vector allocation based on iterator size hints let mut vec = match self.inner.size_hint() { + // Use upper bound if available (_, Some(upper)) => Vec::with_capacity(upper), - // no upper bound means infinite size - // don't want to allocate usize::MAX sized vector - _ => Vec::new(), // default vector capacity + // Use lower bound if available and reasonable + (lower, None) if lower > 0 => Vec::with_capacity(lower), + // Use reasonable default for mutation operations (most updates affect 1-100 items) + // 16 is a good balance: small enough to not waste memory, large enough to avoid early reallocations + _ => Vec::with_capacity(16), }; for item in self.inner { @@ -57,20 +61,21 @@ impl<'scope, 'env, I: Iterator>> Updat if let Some(ref props) = props { for (key, _new_value) in props.iter() { if let Some(db) = storage.secondary_indices.get(key) - && let Some(old_value) = properties.get(key) { - match bincode::serialize(old_value) { - Ok(old_serialized) => { - if let Err(e) = db.delete_one_duplicate( - self.txn, - &old_serialized, - &node.id, - ) { - vec.push(Err(GraphError::from(e))); - } + && let Some(old_value) = properties.get(key) + { + match bincode::serialize(old_value) { + Ok(old_serialized) => { + if let Err(e) = db.delete_one_duplicate( + self.txn, + &old_serialized, + &node.id, + ) { + vec.push(Err(GraphError::from(e))); } - Err(e) => vec.push(Err(GraphError::from(e))), } + Err(e) => vec.push(Err(GraphError::from(e))), } + } } } diff --git a/helix-db/src/helix_gateway/builtin/node_connections.rs b/helix-db/src/helix_gateway/builtin/node_connections.rs index e3190b05..b9cae42b 100644 --- a/helix-db/src/helix_gateway/builtin/node_connections.rs +++ b/helix-db/src/helix_gateway/builtin/node_connections.rs @@ -84,8 +84,10 @@ pub fn node_connections_inner(input: HandlerInput) -> Result Result n, // Trust reasonable limits + Some(_) => 100_000, // Cap excessive limits + None => 100, // Reasonable default for unlimited queries + }; + let mut nodes_json = Vec::with_capacity(initial_capacity); let mut count = 0; for result in db.nodes_db.iter(&txn)? {