title_wip

ClickHouse therefore provides an optimization which disables rescoring and returns the most similar vectors and their distances directly from the index.
The optimization is enabled by default, see setting [vector\_search\_with\_rescoring](/docs/operations/settings/settings#vector_search_with_rescoring).
The way it works at a high level is that ClickHouse makes the most similar vectors and their distances available as a virtual column `_distances`.
To see this, run a vector search query with `EXPLAIN header = 1`:

```
EXPLAIN header = 1
WITH [0., 2.] AS reference_vec
SELECT id
FROM tab
ORDER BY L2Distance(vec, reference_vec) ASC
LIMIT 3
SETTINGS vector_search_with_rescoring = 0
```

```
Query id: a2a9d0c8-a525-45c1-96ca-c5a11fa66f47

    ┌─explain─────────────────────────────────────────────────────────────────────────────────────────────────┐
 1. │ Expression (Project names)                                                                              │
 2. │ Header: id Int32                                                                                        │
 3. │   Limit (preliminary LIMIT (without OFFSET))                                                            │
 4. │   Header: L2Distance(__table1.vec, _CAST([0., 2.]_Array(Float64), 'Array(Float64)'_String)) Float64     │
 5. │           __table1.id Int32                                                                             │
 6. │     Sorting (Sorting for ORDER BY)                                                                      │
 7. │     Header: L2Distance(__table1.vec, _CAST([0., 2.]_Array(Float64), 'Array(Float64)'_String)) Float64   │
 8. │             __table1.id Int32                                                                           │
 9. │       Expression ((Before ORDER BY + (Projection + Change column names to column identifiers)))         │
10. │       Header: L2Distance(__table1.vec, _CAST([0., 2.]_Array(Float64), 'Array(Float64)'_String)) Float64 │
11. │               __table1.id Int32                                                                         │
12. │         ReadFromMergeTree (default.tab)                                                                 │
13. │         Header: id Int32                                                                                │
14. │                 _distance Float32                                                                       │
    └─────────────────────────────────────────────────────────────────────────────────────────────────────────┘
```

Note

A query run without rescoring (`vector_search_with_rescoring = 0`) and with parallel replicas enabled may fall back to rescoring.

#### Performance tuning[​](#performance-tuning "Direct link to Performance tuning")

**Tuning compression**

In virtually all use cases, the vectors in the underlying column are dense and do not compress well.
As a result, [compression](/docs/sql-reference/statements/create/table#column_compression_codec) slows down inserts and reads into/from the vector column.
We therefore recommend to disable compression.
To do that, specify `CODEC(NONE)` for the vector column like this:

```
CREATE TABLE tab(id Int32, vec Array(Float32) CODEC(NONE), INDEX idx vec TYPE vector_similarity('hnsw', 'L2Distance', 2)) ENGINE = MergeTree ORDER BY id;
```

**Tuning index creation**

The life cycle of vector similarity indexes is tied to the life cycle of parts.
In other words, whenever a new part with defined vector similarity index is created, the index is create as well.
This typically happens when data is [inserted](https://clickhouse.com/docs/guides/inserting-data) or during [merges](https://clickhouse.com/docs/merges).
Unfortunately, HNSW is known for long index creation times which can significantly slow down inserts and merges.
Vector similarity indexes are ideally only used if the data is immutable or rarely changed.

To speed up index creation, the following techniques can be used:

First, index creation can be parallelized.
The maximum number of index creation threads can be configured using server setting [max\_build\_vector\_similarity\_index\_thread\_pool\_size](/docs/operations/server-configuration-parameters/settings#max_build_vector_similarity_index_thread_pool_size).
For optimal performance, the setting value should be configured to the number of CPU cores.

Second, to speed up INSERT statements, users may disable the creation of skipping indexes on newly inserted parts using session setting [materialize\_skip\_indexes\_on\_insert](/docs/operations/settings/settings#materialize_skip_indexes_on_insert).
SELECT queries on such parts will fall back to exact search.
Since inserted parts tend to be small compared to the total table size, the performance impact of that is expected to be negligible.

Third, to speed up merges, users may disable the creation of skipping indexes on merged parts using session setting [materialize\_skip\_indexes\_on\_merge](/docs/operations/settings/merge-tree-settings#materialize_skip_indexes_on_merge).
This, in conjunction with statement [ALTER TABLE [...] MATERIALIZE INDEX [...]](/docs/sql-reference/statements/alter/skipping-index#materialize-index), provides explicit control over the life cycle of vector similarity indexes.
For example, index creation can be deferred until all data was ingested or until a period of low system load such as the weekend.

**Tuning index usage**

SELECT queries need to load vector similarity indexes into main memory to use them.
To avoid that the same vector similarity index is loaded repeatedly into main memory, ClickHouse provides a dedicated in-memory cache for such indexes.
The bigger this cache is, the fewer unnecessary loads will happen.
The maximum cache size can be configured using server setting [vector\_similarity\_index\_cache\_size](/docs/operations/server-configuration-parameters/settings#vector_similarity_index_cache_size).
By default, the cache can grow up to 5 GB in size.

The following log messages (`system.text_log`) indicate that the vector similarity index is being loaded.
If such messages appear repeatedly for different vector search queries, this indicates that the cache size is too low.

```
2026-02-03 07:39:10.351635 [1386] f0ac5c85-1b1c-4f35-8848-87a1d1aa00ba : VectorSimilarityIndex Start loading vector similarity index

<...>

2026-02-03 07:40:25.217603 [1386] f0ac5c85-1b1c-4f35-8848-87a1d1aa00ba : VectorSimilarityIndex Loaded vector similarity index: max_level = 2, connectivity = 64, size = 1808111, capacity = 1808111, memory_usage = 8.00 GiB, bytes_per_vector = 4096, scalar_words = 1024, nodes = 1808111, edges = 51356964, max_edges = 233395072
```

Note

The vector similarity index cache stores vector index granules.
If individual vector index granules are bigger than the cache size, they will not be cached.
Therefore, please make sure to calculate the vector index size (based on the formula in "Estimating storage and memory consumption" or [system.data\_skipping\_indices](/docs/operations/system-tables/data_skipping_indices)) and size the cache correspondingly.

*We reiterate that verifying and, if necessary, increasing the vector index cache should be the first step when investigating slow vector search queries.*

The current size of the vector similarity index cache is shown in [system.metrics](/docs/operations/system-tables/metrics):

```
SELECT metric, value
FROM system.metrics
WHERE metric = 'VectorSimilarityIndexCacheBytes'
```

The cache hits and misses for a query with some query id can be obtained from [system.query\_log](/docs/operations/system-tables/query_log):

```
SYSTEM FLUSH LOGS query_log;

SELECT ProfileEvents['VectorSimilarityIndexCacheHits'], ProfileEvents['VectorSimilarityIndexCacheMisses']
FROM system.query_log
WHERE type = 'QueryFinish' AND query_id = '<...>'
ORDER BY event_time_microseconds;
```

For production use-cases, we recommend that the cache is sized large enough so that all vector indexes remain in memory at all times.

**Tuning quantization**