HeliosDB Nano Phase 3 v2.0 Testing Strategy
HeliosDB Nano Phase 3 v2.0 Testing Strategy
Version: 1.0 Created: 2025-11-17 Purpose: Comprehensive testing strategy for Phase 3 features Target Coverage: 90%+ for new features
Executive Summary
This document outlines the comprehensive testing strategy for HeliosDB Nano Phase 3 v2.0 features, including:
- SQL Wrapper Layer
- Product Quantization (PQ) for vectors
- Incremental Materialized Views
- FSST/ALP Compression
- Vectorized Execution
- Time-Series Optimizations
Quality Goals:
- ✅ 90%+ code coverage for new Phase 3 features
- ✅ 100% critical path coverage
- ✅ Zero regressions in existing features
- ✅ Performance targets met for all features
Testing Pyramid
/\ /E2E\ <- 10% (High-value integration scenarios) /------\ /Integr.\ <- 30% (Feature integration tests) /----------\ / Unit \ <- 60% (Fast, focused unit tests) /--------------\Distribution:
- Unit Tests (60%): Fast, isolated, comprehensive
- Integration Tests (30%): Feature interactions, end-to-end flows
- E2E Tests (10%): Critical user scenarios
Test Categories
1. Unit Tests
1.1 SQL Wrapper Tests
Location: tests/unit/sql_wrapper/
Coverage:
- SQL parser extensions (CREATE MATERIALIZED VIEW, USING hnsw, etc.)
- System view implementations (pg_mv_staleness, pg_vector_index_stats)
- SQL validator logic
- Query rewriting rules
Example Test Structure:
#[test]fn test_parse_create_materialized_view_with_auto_refresh() { let sql = r#" CREATE MATERIALIZED VIEW user_stats AS SELECT user_id, COUNT(*) as order_count FROM orders GROUP BY user_id WITH (auto_refresh = true, max_cpu_percent = 15) "#;
let result = parse_sql(sql); assert!(result.is_ok());
let stmt = result.unwrap(); assert!(matches!(stmt, Statement::CreateMaterializedView(_)));
let options = extract_options(&stmt); assert_eq!(options.get("auto_refresh"), Some(&"true".to_string())); assert_eq!(options.get("max_cpu_percent"), Some(&"15".to_string()));}
#[test]fn test_parse_vector_index_with_pq() { let sql = r#" CREATE INDEX vec_idx ON documents USING hnsw (embedding vector_cosine_ops) WITH (quantization = 'product', pq_subquantizers = 8, m = 16, ef_construction = 200) "#;
let result = parse_sql(sql); assert!(result.is_ok());
let stmt = result.unwrap(); let options = extract_index_options(&stmt); assert_eq!(options.get("quantization"), Some(&"product".to_string())); assert_eq!(options.get("pq_subquantizers"), Some(&"8".to_string()));}1.2 Product Quantization Tests
Location: tests/unit/product_quantization/
Coverage:
- K-means clustering algorithm
- Vector encoding/decoding
- Codebook training
- Asymmetric Distance Computation (ADC)
- Sub-quantizer splitting logic
- SIMD optimizations
Example Test Structure:
#[test]fn test_kmeans_clustering_convergence() { let vectors = generate_random_vectors(1000, 96); // 1000 vectors, 96 dims let k = 256; // 256 centroids
let kmeans = KMeans::new(k, 100); // 100 iterations let (centroids, labels) = kmeans.fit(&vectors);
assert_eq!(centroids.len(), k); assert_eq!(labels.len(), vectors.len());
// Verify convergence: all vectors assigned to nearest centroid for (i, &label) in labels.iter().enumerate() { let assigned_centroid = ¢roids[label]; let distance = euclidean_distance(&vectors[i], assigned_centroid);
// Check all other centroids are farther for (j, centroid) in centroids.iter().enumerate() { if j != label { let other_distance = euclidean_distance(&vectors[i], centroid); assert!(distance <= other_distance); } } }}
#[test]fn test_product_quantizer_encode_decode() { let dim = 768; let num_subquantizers = 8; let subdim = dim / num_subquantizers; // 96
let pq = ProductQuantizer::new(dim, num_subquantizers, 256);
// Train on sample data let training_vectors = generate_random_vectors(10000, dim); pq.train(&training_vectors).unwrap();
// Test encoding/decoding let original = generate_random_vector(dim); let encoded = pq.encode(&original).unwrap(); let decoded = pq.decode(&encoded).unwrap();
assert_eq!(encoded.len(), num_subquantizers); // 8 bytes assert_eq!(decoded.len(), dim);
// Quantization error should be reasonable (<10% on average) let error = mean_squared_error(&original, &decoded); assert!(error < 0.1);}
#[test]fn test_asymmetric_distance_computation() { let dim = 768; let pq = ProductQuantizer::new(dim, 8, 256);
let training_vectors = generate_random_vectors(10000, dim); pq.train(&training_vectors).unwrap();
let query = generate_random_vector(dim); let vector = generate_random_vector(dim); let encoded_vector = pq.encode(&vector).unwrap();
// Compute distance using ADC let adc_distance = pq.compute_asymmetric_distance(&query, &encoded_vector);
// Compute ground truth distance let decoded_vector = pq.decode(&encoded_vector).unwrap(); let true_distance = euclidean_distance(&query, &decoded_vector);
// ADC should approximate true distance closely (<5% error) let relative_error = (adc_distance - true_distance).abs() / true_distance; assert!(relative_error < 0.05);}1.3 Compression Algorithm Tests
Location: tests/unit/compression/
Coverage:
- FSST codec correctness
- ALP codec correctness
- Compression ratio validation
- Compression/decompression speed
- Edge cases (empty data, single value, etc.)
Example Test Structure:
#[test]fn test_fsst_compression_correctness() { let data = b"The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy cat.";
let codec = FsstCodec::new(); let compressed = codec.compress(data).unwrap(); let decompressed = codec.decompress(&compressed).unwrap();
assert_eq!(data, &decompressed[..]); assert!(compressed.len() < data.len()); // Should compress}
#[test]fn test_fsst_compression_ratio_with_repetitive_text() { let repetitive_text = "SELECT * FROM users WHERE user_id = 123; ".repeat(100); let data = repetitive_text.as_bytes();
let codec = FsstCodec::new(); let compressed = codec.compress(data).unwrap();
let ratio = data.len() as f64 / compressed.len() as f64; assert!(ratio >= 2.0); // At least 2x compression for repetitive text}
// tests/unit/compression/alp_tests.rs
#[test]fn test_alp_float_compression() { let floats: Vec<f64> = (0..1000) .map(|i| 100.0 + (i as f64) * 0.1) // Values with similar exponent .collect();
let codec = AlpCodec::new(); let compressed = codec.compress(&floats).unwrap(); let decompressed = codec.decompress(&compressed).unwrap();
// Verify correctness for (i, &original) in floats.iter().enumerate() { assert!((original - decompressed[i]).abs() < 1e-10); }
// Verify compression ratio let original_bytes = floats.len() * 8; let ratio = original_bytes as f64 / compressed.len() as f64; assert!(ratio >= 2.0); // At least 2x compression}1.4 Incremental MV Tests
Location: tests/unit/incremental_mv/
Coverage:
- Delta tracking logic
- Refresh scheduling
- CPU budget enforcement
- Staleness calculation
- View dependency tracking
2. Integration Tests
2.1 End-to-End SQL Tests
Location: tests/integration/sql_e2e/
Coverage:
- Complete SQL workflows
- Feature interactions
- Transaction handling
- Error scenarios
Example Test Structure:
#[tokio::test]async fn test_incremental_mv_auto_refresh_workflow() { let db = setup_test_db().await;
// Create base table db.execute("CREATE TABLE orders (user_id INT, amount DECIMAL, created_at TIMESTAMP)") .await.unwrap();
// Create materialized view with auto-refresh db.execute(r#" CREATE MATERIALIZED VIEW user_totals AS SELECT user_id, SUM(amount) as total, COUNT(*) as order_count FROM orders GROUP BY user_id WITH (auto_refresh = true, max_cpu_percent = 15, refresh_interval = '1 second') "#).await.unwrap();
// Insert data db.execute("INSERT INTO orders VALUES (1, 100.00, NOW())").await.unwrap(); db.execute("INSERT INTO orders VALUES (1, 50.00, NOW())").await.unwrap(); db.execute("INSERT INTO orders VALUES (2, 200.00, NOW())").await.unwrap();
// Wait for auto-refresh tokio::time::sleep(Duration::from_secs(2)).await;
// Query MV let results = db.query("SELECT * FROM user_totals ORDER BY user_id").await.unwrap();
assert_eq!(results.len(), 2); assert_eq!(results[0].get::<i32>("user_id"), 1); assert_eq!(results[0].get::<f64>("total"), 150.0); assert_eq!(results[0].get::<i64>("order_count"), 2);
// Check staleness let staleness = db.query("SELECT * FROM pg_mv_staleness() WHERE view_name = 'user_totals'") .await.unwrap(); assert!(staleness[0].get::<i64>("staleness_sec") < 5);}2.2 Vector Search with PQ Tests
Location: tests/integration/vector_search_pq/
Example Test Structure:
#[tokio::test]async fn test_vector_search_with_product_quantization() { let db = setup_test_db().await;
// Create table with vector column db.execute("CREATE TABLE documents (id SERIAL PRIMARY KEY, embedding VECTOR(768), content TEXT)") .await.unwrap();
// Create PQ index db.execute(r#" CREATE INDEX vec_idx ON documents USING hnsw (embedding vector_cosine_ops) WITH ( quantization = 'product', pq_subquantizers = 8, m = 16, ef_construction = 200 ) "#).await.unwrap();
// Insert vectors let num_vectors = 10000; for i in 0..num_vectors { let vec = generate_random_vector(768); db.execute(&format!( "INSERT INTO documents (embedding, content) VALUES ('{}', 'doc{}')", vec_to_string(&vec), i )).await.unwrap(); }
// Search let query = generate_random_vector(768); let results = db.query(&format!( "SELECT id, content FROM documents ORDER BY embedding <=> '{}' LIMIT 10", vec_to_string(&query) )).await.unwrap();
assert_eq!(results.len(), 10);
// Check index stats let stats = db.query("SELECT * FROM pg_vector_index_stats('vec_idx')").await.unwrap(); assert_eq!(stats[0].get::<String>("quantization"), "product");
let memory_bytes: i64 = stats[0].get("memory_bytes"); let uncompressed_size = num_vectors * 768 * 4; let compression_ratio = uncompressed_size as f64 / memory_bytes as f64;
// Should have 8-16x compression assert!(compression_ratio >= 8.0); assert!(compression_ratio <= 20.0);}
#[tokio::test]async fn test_pq_search_accuracy() { let db = setup_test_db().await;
// Create table and indexes (both full and PQ) db.execute("CREATE TABLE docs (id SERIAL, embedding VECTOR(768))").await.unwrap();
// Full precision index for ground truth db.execute("CREATE INDEX vec_idx_full ON docs USING hnsw (embedding vector_cosine_ops)") .await.unwrap();
// PQ index db.execute(r#" CREATE INDEX vec_idx_pq ON docs USING hnsw (embedding vector_cosine_ops) WITH (quantization = 'product', pq_subquantizers = 8) "#).await.unwrap();
// Insert test data let num_vectors = 1000; for i in 0..num_vectors { let vec = generate_random_vector(768); db.execute(&format!("INSERT INTO docs (embedding) VALUES ('{}')", vec_to_string(&vec))) .await.unwrap(); }
// Test multiple queries let mut recalls = Vec::new(); for _ in 0..100 { let query = generate_random_vector(768);
// Ground truth (full precision) let ground_truth: Vec<i32> = db.query(&format!( "SELECT id FROM docs ORDER BY embedding <=> '{}' LIMIT 10", vec_to_string(&query) )).await.unwrap() .into_iter() .map(|row| row.get("id")) .collect();
// PQ results (force using PQ index) let pq_results: Vec<i32> = db.query(&format!( "SELECT id FROM docs USE INDEX (vec_idx_pq) ORDER BY embedding <=> '{}' LIMIT 10", vec_to_string(&query) )).await.unwrap() .into_iter() .map(|row| row.get("id")) .collect();
// Calculate recall@10 let recall = calculate_recall(&ground_truth, &pq_results); recalls.push(recall); }
let avg_recall = recalls.iter().sum::<f64>() / recalls.len() as f64;
// PQ should maintain 95%+ recall@10 assert!(avg_recall >= 0.95, "Average recall: {:.2}%", avg_recall * 100.0);}3. Performance Benchmarks
Location: benches/phase3_benchmarks.rs
Coverage:
- PQ search speed
- MV refresh speed
- Compression speed
- Decompression speed
- Memory usage
Example Benchmark Structure:
use criterion::{black_box, criterion_group, criterion_main, Criterion, BenchmarkId};
fn bench_pq_search(c: &mut Criterion) { let mut group = c.benchmark_group("pq_search");
for num_vectors in [10_000, 100_000, 1_000_000] { let index = create_pq_index(num_vectors, 768, 8); let query = generate_random_vector(768);
group.bench_with_input( BenchmarkId::new("search", num_vectors), &num_vectors, |b, _| { b.iter(|| { let results = index.search(black_box(&query), 10); black_box(results); }); }, ); }
group.finish();}
fn bench_pq_encoding(c: &mut Criterion) { let pq = ProductQuantizer::new(768, 8, 256); let training_data = generate_random_vectors(10000, 768); pq.train(&training_data).unwrap();
let vector = generate_random_vector(768);
c.bench_function("pq_encode", |b| { b.iter(|| { let encoded = pq.encode(black_box(&vector)).unwrap(); black_box(encoded); }); });}
fn bench_fsst_compression(c: &mut Criterion) { let mut group = c.benchmark_group("fsst_compression");
let text_samples = vec![ ("small", generate_text(1024)), // 1 KB ("medium", generate_text(10240)), // 10 KB ("large", generate_text(102400)), // 100 KB ];
for (name, text) in text_samples { let codec = FsstCodec::new();
group.bench_with_input( BenchmarkId::new("compress", name), &text, |b, data| { b.iter(|| { let compressed = codec.compress(black_box(data)).unwrap(); black_box(compressed); }); }, );
let compressed = codec.compress(&text).unwrap(); group.bench_with_input( BenchmarkId::new("decompress", name), &compressed, |b, data| { b.iter(|| { let decompressed = codec.decompress(black_box(data)).unwrap(); black_box(decompressed); }); }, ); }
group.finish();}
fn bench_incremental_mv_refresh(c: &mut Criterion) { let db = setup_test_db(); create_mv_with_base_data(&db, 10000); // 10K rows
c.bench_function("mv_incremental_refresh", |b| { b.iter(|| { // Insert 10 new rows for i in 0..10 { db.execute(&format!("INSERT INTO orders VALUES ({}, 100.00)", i)); } // Measure incremental refresh time db.refresh_mv("user_totals"); }); });}
criterion_group!( benches, bench_pq_search, bench_pq_encoding, bench_fsst_compression, bench_incremental_mv_refresh);criterion_main!(benches);4. Compatibility Tests
Location: tests/compatibility/phase3_compatibility.rs
Coverage:
- Backward compatibility with existing features
- Data migration scenarios
- Version upgrade tests
- Feature flag combinations
Performance Targets
Product Quantization
- PQ Search (1M vectors): <10ms for k=10
- PQ Encoding: <1ms per vector
- Memory Reduction: 8-16x
- Recall@10: >95%
Incremental MVs
- Incremental Refresh: <100ms for <1000 delta rows
- CPU Usage: <15% when enabled
- Staleness: <5 seconds with auto-refresh
Compression
- FSST Compression Speed: >500 MB/sec
- FSST Decompression Speed: >2 GB/sec
- ALP Compression Speed: >400 MB/sec
- ALP Decompression Speed: >800 MB/sec
Test Infrastructure
Test Helpers
Location: tests/test_helpers.rs
Common utilities:
- Database setup/teardown
- Random data generation
- Vector generation
- Assertion helpers
- Performance measurement utilities
CI/CD Integration
GitHub Actions Workflow:
name: Phase 3 Tests
on: [push, pull_request]
jobs: unit-tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Run unit tests run: cargo test --lib --features phase3
integration-tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Run integration tests run: cargo test --test '*' --features phase3
benchmarks: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Run benchmarks run: cargo bench --features phase3 -- --save-baseline main
coverage: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Generate coverage report run: | cargo install cargo-tarpaulin cargo tarpaulin --out Xml --features phase3 - name: Upload coverage uses: codecov/codecov-action@v3Test Execution Strategy
Local Development
# Run all unit testscargo test --lib --features phase3
# Run integration testscargo test --test '*' --features phase3
# Run specific test suitecargo test --test product_quantization_tests
# Run benchmarkscargo bench --features phase3
# Generate coverage reportcargo tarpaulin --out Html --features phase3CI Pipeline
- PR Checks: Unit + Integration tests (must pass)
- Nightly: Full test suite + benchmarks
- Release: Full suite + performance validation
Test Metrics and Reporting
Coverage Requirements
- Unit Tests: >90% line coverage
- Integration Tests: 100% critical path coverage
- Overall: >85% coverage for Phase 3 code
Performance Regression Detection
- Benchmark results tracked over time
- Alert on >10% performance regression
- Block merges that fail performance targets
Test Maintenance
Best Practices
- Test Naming: Use descriptive names (test_feature_scenario_expected)
- Test Isolation: Each test should be independent
- Test Data: Use factories/builders for test data
- Assertions: One logical assertion per test
- Documentation: Comment complex test scenarios
Review Checklist
- All new code has corresponding tests
- Tests cover edge cases
- Performance tests included for critical paths
- Tests are deterministic (no flaky tests)
- Test documentation updated
Appendix: Test File Organization
HeliosDB Nano/├── tests/│ ├── unit/│ │ ├── sql_wrapper/│ │ │ ├── parser_tests.rs│ │ │ ├── system_views_tests.rs│ │ │ └── validator_tests.rs│ │ ├── product_quantization/│ │ │ ├── kmeans_tests.rs│ │ │ ├── codec_tests.rs│ │ │ ├── adc_tests.rs│ │ │ └── simd_tests.rs│ │ ├── compression/│ │ │ ├── fsst_tests.rs│ │ │ ├── alp_tests.rs│ │ │ └── ml_selection_tests.rs│ │ └── incremental_mv/│ │ ├── delta_tracking_tests.rs│ │ ├── refresh_tests.rs│ │ └── cpu_budget_tests.rs│ ├── integration/│ │ ├── sql_e2e/│ │ │ ├── materialized_view_integration.rs│ │ │ ├── vector_search_integration.rs│ │ │ └── compression_pipeline_integration.rs│ │ └── vector_search_pq/│ │ ├── pq_search_integration.rs│ │ └── pq_accuracy_tests.rs│ ├── compatibility/│ │ └── phase3_compatibility.rs│ └── test_helpers.rs├── benches/│ ├── phase3_benchmarks.rs│ ├── pq_benchmarks.rs│ └── compression_benchmarks.rs└── docs/ └── testing/ ├── PHASE3_TEST_STRATEGY.md (this file) └── TEST_RESULTS.md (generated)Next Steps
- Week 1: Implement test infrastructure and helpers
- Week 2: Write unit tests for SQL Wrapper
- Week 3: Write unit tests for Product Quantization
- Week 4: Write integration tests
- Week 5: Create benchmarks and performance tests
- Week 6: CI/CD integration and documentation
Status: Draft Approval: Pending review Contact: Testing Team Lead