matrixorigin / matrixorigin/matrixone

[Bug]: HNSW rewrite returns different values for the same l2_distance expression

Open
#29,050 2 comments 0 reactions 0 assignees View on GitHub
area/optimizer kind/bug needs-triage
Dominant language
Go
Stars
1.9k
Forks
311
Avg merge
1d 3h
Merged PRs (30d)
768

Description

## 问题

HNSW rewrite 只把直接投影和排序中的 `l2_distance` 替换成索引返回的 score;同一个函数嵌套在 `ROUND()`、比较表达式或 WHERE 中时仍按标量函数计算。两个计算路径的精度不同,因此同一行、同一 SQL 表达式会在一条结果中同时得到互相矛盾的值。

这不是 ANN 召回差异:HNSW 与精确路径选择了相同的行,错误发生在返回值和表达式一致性上。

## 环境

- MatrixOne `main`
- commit: `01d60e1c4ded1b0f3fc1a4ecd75ce54e95e23b90`
- 本地 2 CN / 1 TN / 1 LogService
- 同步 HNSW,`vector_l2_ops`

## 最小复现

```sql
set experimental_hnsw_index=1;

create table t(id bigint primary key,v vecf32(4));
insert into t values
(29,'[2.189,-1.676,2.08,-1.578]'),
(99,'[10,10,10,10]');

create index hx using hnsw on t(v)
op_type 'vector_l2_ops'
m=16 ef_construction=128 ef_search=128 max_index_capacity=75;
alter table t alter reindex hx hnsw force_sync;

select id,
l2_distance(v,'[1.125,-0.625,0.375,1.75]') direct_d,
round(l2_distance(v,'[1.125,-0.625,0.375,1.75]'),14) wrapped_d,
l2_distance(v,'[1.125,-0.625,0.375,1.75]') = 4.02731990814209 eq,
l2_distance(v,'[1.125,-0.625,0.375,1.75]') <= 4.02731990814209 le
from t
where l2_distance(v,'[1.125,-0.625,0.375,1.75]') <= 4.02731990814209
order by l2_distance(v,'[1.125,-0.625,0.375,1.75]')
limit 10;
```

## 实际结果

```text
id direct_d wrapped_d eq le
29 4.027320069947357 4.02731990814209 1 1
```

`direct_d` 大于比较边界,但同一个 `l2_distance` 的 `=` 和 `<=` 同时为真。给查询加 `BY RANK WITH OPTION 'mode=force'` 后,直接投影也返回标量值 `4.02731990814209`。

## 预期结果

同一行中语义相同的函数调用必须得到同一个 SQL 值。向量索引可以近似选择候选行,但访问路径不应让直接投影、函数嵌套和谓词分别观察到不同的 `l2_distance` 值。

## 稳定性与控制

- 3 个独立数据库和 HNSW 索引;
- 两个 CN;
- 每个索引、每个 CN 连续执行 3 次;
- 18/18 次均返回上述矛盾结果;
- `mode=force` 18/18 次返回一致的标量值;
- HNSW 与精确路径的行集都只有 `id=29`,排除了召回率或 Top-K tie;
- `EXPLAIN` 确认错误查询使用 `hnsw_search`,WHERE 在回表 scan 上按标量函数执行。

## 定位

`pkg/sql/plan/apply_indices_hnsw.go` 用 HNSW table function 的 `score` 列替换 Top-K 的排序表达式,并通过 `vectorRemapForChildProject` 重映射直接投影;原表上的 residual filter 保留标量 `l2_distance`。`vecf32` 标量实现返回 float32 舍入后的距离,而 HNSW score 路径返回另一精度的值,于是相同函数根据表达式位置产生不同结果。

这与 #29040 不同:#29040 是 IVFFLAT 推送距离范围后改变行成员关系;本问题是 HNSW 选中相同行后,直接投影与同查询内的标量表达式值不一致,修复路径也位于 HNSW rewrite/remap。

Contributor guide

Open the contributing guide

Research direction

Start in pkg/sql/plan/apply_indices_hnsw.go, focusing on the HNSW score replacement and vectorRemapForChildProject. Reproduce the SQL example with experimental_hnsw_index enabled and compare direct projection, ROUND(), predicates, and the force-ranked path. Done means equivalent l2_distance expressions observe one consistent SQL value while HNSW and exact paths retain the same selected rows.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
databases
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.