Skip to content

HeliosDB Nano Phase 3 User Guide

HeliosDB Nano Phase 3 User Guide

Version: v2.0.0-beta Last Updated: November 18, 2025 Status: Production Ready


Table of Contents

  1. Introduction
  2. Product Quantization for Vectors
  3. Quantized HNSW Index
  4. Database Branching
  5. Time-Travel Queries
  6. Materialized Views
  7. System Views
  8. Performance Tuning
  9. Best Practices
  10. Troubleshooting

Introduction

Phase 3 brings enterprise-grade features to HeliosDB Nano:

  • 8-16x vector compression with Product Quantization
  • Git-like database branching for zero-downtime workflows
  • Time-travel queries for point-in-time data access
  • Intelligent materialized views with auto-refresh

What’s New in v2.0

FeatureStatusDescription
Product Quantization✅ Production8-16x vector memory compression
Quantized HNSW✅ ProductionMemory-efficient vector search
SQL Branching🔧 BetaCREATE/DROP/MERGE BRANCH syntax
Time-Travel🔧 BetaAS OF TIMESTAMP/TRANSACTION queries
Materialized Views🔧 BetaAuto-refresh with CPU limits

Product Quantization

Overview

Product Quantization (PQ) compresses high-dimensional vectors by 8-16x with minimal accuracy loss (95-98% recall@10).

How It Works:

  1. Split vector into M sub-vectors
  2. Quantize each sub-vector independently
  3. Store codes instead of full values

Memory Savings:

  • 768-dim vector: 3,072 bytes → 8 bytes (384x compression)
  • 512-dim vector: 2,048 bytes → 8 bytes (256x compression)
  • 256-dim vector: 1,024 bytes → 4 bytes (256x compression)

Quick Start

use heliosdb_nano::vector::quantization::{
ProductQuantizer,
ProductQuantizerConfig,
};
// 1. Configure PQ for your vector dimension
let config = ProductQuantizerConfig::default_for_dimension(768)?;
// 2. Train on your data (10K+ vectors recommended)
let pq = ProductQuantizer::train(config, &training_vectors)?;
// 3. Encode vectors for storage
let quantized = pq.encode(&vector)?;
println!("Compressed from 3KB to {} bytes!", quantized.memory_size());
// 4. Search efficiently
let query = &my_query_vector;
let distance_table = pq.precompute_distance_table(query)?;
let distance = pq.compute_distance_with_table(&distance_table, &quantized)?;

Configuration Options

pub struct ProductQuantizerConfig {
/// Number of sub-quantizers (M)
/// Default: 8 for 768-dim, 4 for 256-dim
pub num_subquantizers: usize,
/// Centroids per sub-quantizer (K)
/// Default: 256 (fits in u8)
pub num_centroids: usize,
/// Vector dimension (D)
/// Must be divisible by num_subquantizers
pub dimension: usize,
/// K-means iterations for training
/// Default: 25
pub training_iterations: usize,
/// Minimum training samples
/// Default: 10,000
pub min_training_samples: usize,
}

Performance Tips

Training:

  • Use 10,000+ vectors for best quality
  • More iterations = better codebook (diminishing returns after 25)
  • Training is one-time cost, amortize over all searches

Search:

  • Always use precompute_distance_table() for batch queries
  • Precompute once, search many times (100-1000x faster)
  • For single query, direct compute_distance() is fine

Memory:

  • Codebook size: M × K × (D/M) × 4 bytes
    • Example (768-dim): 8 × 256 × 96 × 4 = 768KB
  • Per-vector: M bytes
    • Example (M=8): 8 bytes

Quantized HNSW Index

Overview

Combines HNSW graph structure with Product Quantization for memory-efficient approximate nearest neighbor search.

Benefits:

  • 8-16x memory reduction vs standard HNSW
  • 95-98% search accuracy maintained
  • Fast insertion and search

Quick Start

use heliosdb_nano::vector::{
QuantizedHnswIndex,
QuantizedHnswConfig,
};
// 1. Configure index
let config = QuantizedHnswConfig::default_for_dimension(768)?;
// 2. Train index (trains PQ internally)
let index = QuantizedHnswIndex::train(config, &training_vectors)?;
// 3. Insert vectors
for (id, vector) in vectors.iter().enumerate() {
index.insert(id as u64, vector)?;
}
// 4. Search for nearest neighbors
let results = index.search(&query, 10)?; // Top 10
for (id, distance) in results {
println!("Vector {} is {} away", id, distance);
}
// 5. Check memory savings
let stats = index.memory_stats();
println!("Compression: {:.1}x", stats.compression_ratio);
println!("{}", stats.format());

Configuration Options

pub struct QuantizedHnswConfig {
/// HNSW graph connectivity
pub max_connections: usize, // Default: 16
/// Build quality (larger = better accuracy)
pub ef_construction: usize, // Default: 200
/// Search quality (larger = better recall)
pub ef_search: usize, // Default: 200
/// Vector dimension
pub dimension: usize,
/// Distance metric (L2, Cosine, etc.)
pub distance_metric: DistanceMetric,
/// Product Quantization config
pub pq_config: ProductQuantizerConfig,
/// Use PQ for storage (vs keeping originals)
pub use_pq_storage: bool, // Default: true
}

Memory Usage Example

Scenario: 100,000 vectors (768-dim)
Without PQ (standard HNSW):
- Vectors: 100K × 768 × 4 = 307 MB
- Graph: ~50 MB (estimate)
- Total: ~357 MB
With PQ (Quantized HNSW):
- Quantized vectors: 100K × 8 = 800 KB
- Codebook: 768 KB
- Graph: ~50 MB
- Total: ~51.6 MB
Compression: 6.9x

Database Branching

Overview

Create isolated database branches for testing, development, or rollback scenarios.

SQL Syntax

Create Branch

-- Create branch from current state
CREATE DATABASE BRANCH staging FROM CURRENT AS OF NOW;
-- Create branch from specific point in time
CREATE DATABASE BRANCH test FROM main
AS OF TIMESTAMP '2025-11-15 06:00:00';
-- Create branch from transaction
CREATE DATABASE BRANCH backup FROM CURRENT
AS OF TRANSACTION 987654;
-- With options
CREATE DATABASE BRANCH prod_copy FROM main AS OF NOW
WITH (
replication_factor = 3,
region = 'us-west-2'
);

Drop Branch

-- Drop branch (fails if doesn't exist)
DROP DATABASE BRANCH staging;
-- Drop if exists
DROP DATABASE BRANCH IF EXISTS staging;

Merge Branch

-- Merge with conflict resolution
MERGE DATABASE BRANCH staging INTO main
WITH (
conflict_resolution = 'branch_wins',
delete_branch_after = true
);
-- Conflict resolution options:
-- 'branch_wins' - Source branch changes win
-- 'target_wins' - Target branch changes win
-- 'fail' - Fail on any conflict

Use Cases

Development Workflow:

-- 1. Create dev branch
CREATE DATABASE BRANCH dev FROM main AS OF NOW;
-- 2. Make changes in dev branch
-- ... development work ...
-- 3. Test thoroughly
-- ... testing ...
-- 4. Merge back to main
MERGE DATABASE BRANCH dev INTO main
WITH (conflict_resolution = 'branch_wins');
-- 5. Clean up
DROP DATABASE BRANCH dev;

Blue-Green Deployment:

-- 1. Create green deployment
CREATE DATABASE BRANCH green FROM blue AS OF NOW;
-- 2. Deploy new version to green
-- ... deployment ...
-- 3. Switch traffic to green (application-level)
-- 4. If success, make green the new blue
MERGE DATABASE BRANCH green INTO blue;
-- 5. If rollback needed, just switch back to blue

Time-Travel Queries

Overview

Query historical data as it existed at a specific point in time.

SQL Syntax

AS OF TIMESTAMP

-- Query data as of specific timestamp
SELECT * FROM orders
AS OF TIMESTAMP '2025-11-15 06:00:00'
WHERE user_id = 123;
-- Compare current vs historical
SELECT
current.total as current_total,
historical.total as yesterday_total
FROM orders current
JOIN orders AS OF TIMESTAMP '2025-11-17 00:00:00' historical
ON current.order_id = historical.order_id;

AS OF TRANSACTION

-- Query as of transaction ID
SELECT * FROM orders
AS OF TRANSACTION 987654
WHERE status = 'pending';

AS OF SCN

-- Query as of System Change Number
SELECT * FROM orders
AS OF SCN 123456789;

Use Cases

Audit and Compliance:

-- Show what user saw at time of complaint
SELECT * FROM product_catalog
AS OF TIMESTAMP '2025-11-15 14:23:00'
WHERE product_id = 'WIDGET-123';

Data Recovery:

-- Find accidentally deleted rows
SELECT * FROM orders
AS OF TIMESTAMP '2025-11-18 00:00:00'
WHERE order_id NOT IN (SELECT order_id FROM orders);

Debugging:

-- Compare data before/after bug
SELECT
before.*,
after.*
FROM orders AS OF TIMESTAMP '2025-11-17 23:59:59' before
FULL OUTER JOIN orders after USING (order_id)
WHERE before.total != after.total;

Materialized Views

Overview

Precomputed query results that can auto-refresh with CPU awareness.

SQL Syntax

Create Materialized View

CREATE MATERIALIZED VIEW user_stats AS
SELECT
user_id,
COUNT(*) as order_count,
SUM(total) as total_revenue,
AVG(total) as avg_order_value
FROM orders
GROUP BY user_id
WITH (
auto_refresh = true, -- Enable auto-refresh
max_cpu_percent = 15, -- CPU limit (0-100)
threshold_table_size = '1GB', -- Min base table size
threshold_dml_rate = 100, -- DMLs/sec trigger
lazy_update = true, -- Defer during high load
lazy_catchup_window = '1 hour' -- Max staleness allowed
);

Refresh Materialized View

-- Manual refresh
REFRESH MATERIALIZED VIEW user_stats;
-- Concurrent refresh (non-blocking)
REFRESH MATERIALIZED VIEW CONCURRENTLY user_stats;

Drop Materialized View

-- Drop view
DROP MATERIALIZED VIEW user_stats;
-- Drop if exists
DROP MATERIALIZED VIEW IF EXISTS user_stats;

Configuration Options

OptionTypeDefaultDescription
auto_refreshboolfalseEnable background auto-refresh
max_cpu_percentfloat15.0Maximum CPU usage (0-100)
threshold_table_sizestring’0’Minimum base table size for refresh
threshold_dml_rateint0DMLs/sec to trigger refresh
lazy_updateboolfalseDefer refresh during high load
lazy_catchup_windowstring’1h’Maximum staleness allowed

Use Cases

Analytics Dashboard:

CREATE MATERIALIZED VIEW daily_metrics AS
SELECT
DATE(created_at) as date,
COUNT(*) as orders,
SUM(total) as revenue
FROM orders
GROUP BY DATE(created_at)
WITH (
auto_refresh = true,
max_cpu_percent = 10,
threshold_dml_rate = 50
);

Real-Time Aggregations:

CREATE MATERIALIZED VIEW product_popularity AS
SELECT
product_id,
COUNT(*) as view_count,
COUNT(DISTINCT user_id) as unique_viewers
FROM product_views
GROUP BY product_id
WITH (
auto_refresh = true,
max_cpu_percent = 20,
lazy_catchup_window = '5 minutes'
);

System Views

Overview

Query system metadata and statistics.

Available System Views

pg_database_branches()

-- List all branches
SELECT * FROM pg_database_branches();
-- Output columns:
-- branch_name, parent_branch, created_at, size_bytes

pg_compare_branches()

-- Compare two branches
SELECT * FROM pg_compare_branches('main', 'staging');
-- Output columns:
-- table_name, rows_in_main, rows_in_staging, difference

pg_mv_staleness()

-- Check materialized view freshness
SELECT * FROM pg_mv_staleness();
-- Output columns:
-- view_name, last_refresh, staleness_sec, auto_refresh

pg_mv_cpu_usage()

-- Monitor MV CPU usage
SELECT * FROM pg_mv_cpu_usage();
-- Output columns:
-- view_name, max_cpu_percent, current_cpu_percent, status

pg_vector_index_stats()

-- Vector index statistics
SELECT * FROM pg_vector_index_stats();
-- Output columns:
-- index_name, num_vectors, memory_bytes, compression_ratio

Performance Tuning

Product Quantization Tuning

Accuracy vs Compression Tradeoff:

// Higher accuracy, less compression
let config = ProductQuantizerConfig {
num_subquantizers: 4, // Fewer = better accuracy
num_centroids: 256,
dimension: 768,
..Default::default()
};
// Higher compression, lower accuracy
let config = ProductQuantizerConfig {
num_subquantizers: 16, // More = better compression
num_centroids: 256,
dimension: 768,
..Default::default()
};

Training Quality:

  • More training samples = better codebook (10K minimum, 100K+ ideal)
  • More iterations = better convergence (25 recommended, 50 for critical applications)

HNSW Tuning

Build Quality:

let config = QuantizedHnswConfig {
ef_construction: 200, // Default
// For better accuracy: 400-800
// For faster build: 100-150
..Default::default()
};

Search Quality:

let config = QuantizedHnswConfig {
ef_search: 200, // Default
// For better recall: 400-1000
// For faster search: 50-100
..Default::default()
};

Materialized View Tuning

CPU Budget:

-- Conservative (low impact)
WITH (max_cpu_percent = 10)
-- Balanced
WITH (max_cpu_percent = 15)
-- Aggressive (faster refresh)
WITH (max_cpu_percent = 30)

Refresh Triggers:

-- Refresh frequently
WITH (
threshold_dml_rate = 10, -- Low threshold
lazy_catchup_window = '1 minute' -- Short window
)
-- Refresh infrequently
WITH (
threshold_dml_rate = 1000, -- High threshold
lazy_catchup_window = '1 hour' -- Long window
)

Best Practices

Vector Workloads

  1. Train on Representative Data

    • Use production-like vectors for training
    • Include edge cases and outliers
    • Retrain periodically as data distribution changes
  2. Batch Operations

    • Always use precompute_distance_table() for multiple searches
    • Encode vectors in batches for better throughput
    • Amortize training cost over many vectors
  3. Monitor Accuracy

    • Periodically check recall@k on validation set
    • Alert if accuracy drops below threshold
    • Consider retraining if drift detected

Database Branching

  1. Branch Naming Convention

    <purpose>-<timestamp>
    Examples:
    - dev-20251118
    - staging-v2.0
    - backup-before-migration
  2. Branch Lifecycle

    • Create → Work → Test → Merge → Delete
    • Don’t accumulate stale branches
    • Document branch purpose in metadata
  3. Merge Conflicts

    • Use branch_wins for development → production
    • Use target_wins for rollbacks
    • Use fail for manual conflict resolution

Materialized Views

  1. Start Conservative

    -- Begin with safe settings
    WITH (
    max_cpu_percent = 10,
    lazy_update = true
    )
    -- Gradually increase if needed
  2. Monitor Performance

    -- Check regularly
    SELECT * FROM pg_mv_staleness();
    SELECT * FROM pg_mv_cpu_usage();
  3. Index the MV

    -- Create indexes on frequently queried columns
    CREATE INDEX idx_user_stats_revenue
    ON user_stats(total_revenue DESC);

Troubleshooting

Product Quantization Issues

Problem: Low search accuracy (recall <90%)

Solutions:

  • Use more training samples (100K+ recommended)
  • Reduce number of sub-quantizers (try M=4 instead of M=8)
  • Increase training iterations (try 50 instead of 25)
  • Check training data quality (outliers, distribution)

Problem: Slow training

Solutions:

  • Reduce training set size (10K is minimum)
  • Reduce training iterations
  • Use smaller num_centroids (128 instead of 256)

Problem: Out of memory during training

Solutions:

  • Train in batches
  • Reduce num_centroids
  • Use dimensionality reduction first

HNSW Issues

Problem: Slow insertion

Solutions:

  • Reduce ef_construction
  • Increase max_connections (paradoxically can be faster)
  • Batch insertions

Problem: Low search recall

Solutions:

  • Increase ef_search
  • Retrain with more representative data
  • Check PQ configuration

Materialized View Issues

Problem: MV is stale

Solutions:

-- Check staleness
SELECT * FROM pg_mv_staleness()
WHERE view_name = 'my_view';
-- Manual refresh if needed
REFRESH MATERIALIZED VIEW my_view;
-- Adjust auto-refresh settings
ALTER MATERIALIZED VIEW my_view
SET (threshold_dml_rate = 50); -- Lower threshold

Problem: High CPU usage

Solutions:

-- Lower CPU budget
ALTER MATERIALIZED VIEW my_view
SET (max_cpu_percent = 10);
-- Enable lazy updates
ALTER MATERIALIZED VIEW my_view
SET (lazy_update = true);

Migration Guide

From v1.x to v2.0

Vector Indexes:

-- Old (v1.x): Standard HNSW
CREATE INDEX vec_idx ON docs USING hnsw (embedding);
-- New (v2.0): Quantized HNSW
CREATE INDEX vec_idx ON docs
USING hnsw (embedding vector_cosine_ops)
WITH (quantization = 'product', pq_subquantizers = 8);

No Breaking Changes:

  • All v1.x queries continue to work
  • Vector indexes auto-upgraded to use PQ
  • Existing data preserved

Getting Help

Documentation:

  • Full API docs: docs/api/
  • Examples: examples/
  • Benchmarks: benches/

Support:

Contributing:

  • Contribution guide: CONTRIBUTING.md
  • Code of conduct: CODE_OF_CONDUCT.md

User Guide Version: 1.0 Last Updated: November 18, 2025 Applies to: HeliosDB Nano v2.0.0-beta and later