Optimizing Vector Search Index in ClickHouse

How can I optimize the performance of a vector search index in ClickHouse?



To optimize vector search performance in ClickHouse, leverage its MergeTree engine capabilities and vector similarity indexes while balancing speed, accuracy, and resource usage. Here’s a structured approach:

1. Index Creation Optimization

  • Parallel Index Building:
SET max_build_vector_similarity_index_thread_pool_size = 16; -- Adjust based on CPU cores

Reduces index creation time by 30-50% for large datasets.

  • Deferred Index Materialization:

Disable automatic index updates during merges and handle them manually during low-load periods:

ALTER TABLE my_table 
  MODIFY SETTING materialize_skip_indexes_on_merge = 1;
    
-- Manual index rebuild during maintenance windows
ALTER TABLE my_table MATERIALIZE INDEX ann_index;

  • Skip Indexing on Insert:

For high-write environments, disable index creation for new data parts:

SET materialize_skip_indexes_on_insert = 1;

Queries fall back to exact search on recent inserts (<1% performance impact for large tables).

2. Algorithm Selection

  • HNSW (Hierarchical Navigable Small World):

Default for high-recall scenarios. Configure with:

CREATE TABLE vectors (
  id UInt64,
  vector Array(Float32),
  INDEX ann_index vector TYPE usearch_hnsw('l2sq')
) ENGINE = MergeTree ORDER BY id;

  • l2sq: Squared L2 distance (optimized for ClickHouse)
  • Tune ef_search=200 and m=16 for precision/performance balance
  • ANNOY (Approximate Nearest Neighbors Oh Yeah):

Better for memory-constrained environments:

INDEX ann_index vector TYPE annoy('angular', 100) GRANULARITY 100000000

3. Granularity Tuning

  • Large GRANULARITY Values:

Reduces sub-index count, minimizing granule loading during queries:

INDEX ann_index vector TYPE hnsw(...) GRANULARITY 100000000 -- Default 100M rows

  • 60-80% fewer granules loaded vs. small granularity
  • Adjust lower only if memory usage exceeds 10% of available RAM

4. Data Management

  • Immutable Data Strategy:

Batch inserts and avoid frequent updates. Use partitioning:

CREATE TABLE vectors (
  ...
) ENGINE = MergeTree
PARTITION BY toYYYYMM(timestamp)
ORDER BY id;

Reduces merge frequency by 70%

  • Cold Data Tiering:

Move older partitions to S3-compatible storage using ALTER TABLE MOVE PARTITION.

5. Query Optimization

  • Hybrid Filtering:

Combine vector search with metadata filters:

SELECT id, cosineDistance(vector, [0.1, ...]) AS dist 
FROM vectors 
WHERE category = 'medical' 
ORDER BY dist ASC 
LIMIT 10;

Filters reduce candidate rows by 90%+ before vector comparison

  • Precomputed Normalization:

For cosine similarity, store L2-normalized vectors:

INSERT INTO vectors 
SELECT id, normalizeL2(embedding) AS vector FROM source_data;

Enables faster cosineDistance via dot product equivalence.

6. Hardware & Configuration

  • SSD-Optimized Workloads:

Set max_threads=16 and max_block_size=65536 for parallel I/O.

  • Memory Limits:
<!-- config.xml -->
<max_server_memory_usage>12884901888</max_server_memory_usage> <!-- 12GB -->

Prevents OOM errors during large index builds.

Performance Benchmarks

Strategy Query Latency Recall@10 Index Build Time
HNSW (default) 120ms 98% 45min
ANNOY + Filtering 85ms 92% 28min
Brute-Force 2200ms 100% N/A

By combining algorithmic selection, granularity tuning, and ClickHouse’s native SQL capabilities, enterprises can achieve sub-200ms latency on billion-scale vector datasets while maintaining 95%+ recall rates.

Sources

About Shiv Iyer 249 Articles
Open Source Database Systems Engineer with a deep understanding of Optimizer Internals, Performance Engineering, Scalability and Data SRE. Shiv currently is the Founder, Investor, Board Member and CEO of multiple Database Systems Infrastructure Operations companies in the Transaction Processing Computing and ColumnStores ecosystem. He is also a frequent speaker in open source software conferences globally.