fix(index): publish honest HNSW verification (successor to #180) - #197
PeterGuy326 wants to merge 2 commits into
Conversation
Review SummaryThanks for the thorough and honest verification work — the counterexample evidence and explicit documentation of the text planner limitation are genuinely valuable. A few issues found: 1. Migration number references are wrong (Bug)The HNSW indexes live in migration 0025, but two comments say "migration 0024":
The PR description itself says "#183 owns 0024" and this PR is 0025, so these are clearly typos. 2. Broken link to
|
waterbro-8
left a comment
There was a problem hiding this comment.
Code review(draft,给作者的修改清单,非批准)
先说我为什么信这份:它最有价值的地方不是加了三个索引,而是它自己把没达成的验收标准写清楚了,并且拒绝为了变绿而放宽标准。这个下面单独说。
一、动已应用的迁移 0001 / 0019 —— 查过了,是安全的
这是看到 diff 的第一反应(改已应用迁移通常是红线)。逐一核过:
0001_init.sql那处:删掉两行-- HNSW index will be added by worker...注释,换成一行注释。0019_versioned_index_generations.sql那处:同样只是把"将来要怎么做"的注释,改成"已经在哪做了"。- 整个 diff 里新增的 DDL 只有 0025 里的三个
CREATE INDEX。0001 / 0019 的 hunk 内不含任何CREATE/ALTER。 - 另外确认 main 上没有迁移的校验和/完整性测试(
verify.sh只用goose validate和版本号断言,不比对文件内容哈希),所以改注释不会触发"迁移被篡改"的判定。
结论:是安全的注释更新,不是改历史 schema。
二、docs/VALIDATION_HNSW.md 里那段"剩余验收边界",我认为是这份 PR 最好的部分
它明说:加了索引不等于#173 达成。现行的 text 查询是"先按 file 做 DISTINCT ON、再全局 top-k",这个形状 planner 用不上 HNSW。于是它给出了:
- 一个可回滚的反例(2000 文件合成语料,把某个文件的 101 个近邻 chunk 与其余 1999 个分开,默认
ef_search=40):shipping 查询返回 10 个文件,索引化的 anti-join 只返回 1 个——因为 HNSW 扫出 40 个候选 chunk,其中 39 个在 per-file 去重时被吃掉。 - 一个独立的精确距离反例,证明固定的过采样倍数救不了:某个文件占掉 81 个最近 chunk 时,
8*k=80个候选全被它吃掉,去重后只剩 1 个文件。 - 明确写出"未改动基线不等于豁免"(an unchanged baseline is not a waiver),并且没有调弱任何 planner 设置或验收标准。
这段的价值在于:它把一个"看起来加个索引就完事"的改动,如实降级成"索引已就绪、但 shipping 查询尚未受益"。请保持这个姿态,不要为了让 verify_hnsw_indexes.sh 变绿而删掉它。
顺带一个我需要点明的结论:这条 PR 落地后 #173 仍然不该被关掉——索引在,但 shipping text 路由不走它,验收标准未达成。这跟你的文档一致,我只是把它说得更明确一点,免得合并时被当成"#173 已完成"。
三、必须改的一条:三处注释写的是 0024,实际是 0025
HNSW 索引在 0025_ann_hnsw_indexes.sql,0024 是 #194 的 lexical search 迁移(你自己的 VALIDATION_HNSW.md 第 22 行就写对了:"Migration 0024 belongs to #183 and 0026 to #185")。但三处代码/迁移注释指错了地方:
| 位置 | 现在写的 | 应为 |
|---|---|---|
server/internal/db/migrations/0001_init.sql(替换后的那行注释) |
migration 0024 |
migration 0025 |
server/internal/db/migrations/0019_...sql |
are in migration 0024 |
are in migration 0025 |
server/internal/face/face.go(包注释) |
The HNSW index (migration 0024) is available... |
migration 0025 |
这不是排版问题:0024 在部署序列里排在 0025 之前,而注释说"HNSW 在 0024",将来有人按部署顺序排查"为什么 0024 之后还没有 HNSW 索引"就会误判。而且它和同一份 PR 里的文档自相矛盾。三处各改一个数字。
四、其余几点(非阻塞)
scripts/verify_hnsw_indexes.sh的设计是对的:只读、强制库名以_test结尾、校验索引的amname='hnsw'且indisvalid且vector_cosine_ops、并且要求语料里真有非 NULL 向量(rows -gt 0)——否则空表建索引也会"通过",那就什么都没证明。用EXPLAIN (ANALYZE, BUFFERS)而不是只EXPLAIN,能让读路径真的执行、向量/模式错误藏不住。没有SET enable_seqscan = off,这点很诚实。- text 那条探针刻意复刻了
runTextANN的形状(DISTINCT ON (f.id)在前),不是拿一个ORDER BY distance LIMIT糊弄过去——这跟文档里"简单探针证明不了这条查询用了索引"的说法是一致的。 - face 索引没有 shipping 查询,文档也这么写了(当前聚类是 Go 里的 O(n))。所以它的证据只是"DDL 合法 + 表里有数据",不是"脸检索变快了"。我不认为这是问题,但请保持文档里这句,别让人误会。
EXPECTED_MIGRATION_HEAD25 与新增 0025 一致。注意这条分支还带着 #194 的 0024(VALIDATION_HNSW.md说部署顺序 #194 → #197 → #195),所以 25 是建立在这条链完整的前提上的——如果 #194 没先落地,这里的 25 就不成立。
五、我没有声称验证的事
我没在本机跑 PostgreSQL + pgvector,文档里那些 planner/反例结论我没有复现,我核对的是它们是否与代码一致、以及是否有"为变绿而放宽标准"的痕迹——没有。合并前请以真实 MEM_TEST_DB 上的结果为准。
(本次为独立评审:我不是本 PR 作者,也不是其 head 的推送者。draft 状态下我不投批准票,以上清单改完后我再来复评。)
…ytefolk#197) Three comments referenced "migration 0024" for the HNSW index, but 0024 is the lexical search migration from bytefolk#194. The HNSW indexes live in 0025. Fix the number in 0001_init.sql, 0019_versioned_index_generations.sql and face.go.
PeterGuy326
left a comment
There was a problem hiding this comment.
Current-head follow-up review at fdccb43:
The three previous blocking documentation errors are fixed: 0001_init.sql, 0019_versioned_index_generations.sql, and face.go now all point to migration 0025. The HNSW validation script remains fail-closed and syntactically valid; affected local Go tests passed.
The current CI failure is an infrastructure/image pull failure (minio/minio denied), not a failure in the HNSW code path. However, this PR is still Draft and the shipping text planner acceptance is explicitly unmet: the current DISTINCT ON query still does not use HNSW safely while preserving result semantics. Therefore this is not an approval or merge recommendation. Keep #173 open/HOLD, restore green exact-head CI, and resolve the text planner acceptance before requesting final approval.
db452f6 to
25a50ea
Compare
fdccb43 to
0a48f84
Compare
Review SummaryStatus: Fix Required 🔧 Found one issue causing CI failure: Problem
The test SQL at line 61 uses FixCreated PR #218 with the fix: use separate parameters instead of casting. -- Before:
VALUES($1,$2,'fixture', $3,0,'fixture',$4,$1::text,$5,$5)
-- After:
VALUES($1,$2,'fixture',$3,0,'fixture',$4,$5,$6,$6)Once #218 is merged into this branch, CI should pass. The HNSW migration and index creation logic itself looks correct. |
#218) Refs #197 Fixes the failing PostgreSQL integration test in #197. ## Problem `TestTextANNFileSemanticsPostgres` fails with: ``` ERROR: inconsistent types deduced for parameter $1 (SQLSTATE 42P08) ``` The test SQL uses `$1::text` to cast a UUID parameter to text for the `storage_key` column, but `$1` is already used as a UUID in the first position. PostgreSQL cannot deduce a consistent type for the parameter. ## Solution Use separate parameters for the UUID and its text representation instead of casting: ```sql -- Before: VALUES($1,$2,'fixture', $3,0,'fixture',$4,$1::text,$5,$5) -- After: VALUES($1,$2,'fixture',$3,0,'fixture',$4,$5,$6,$6) ``` Where `$5` is `id.String()` (the text representation) and `$6` is the timestamp. ## Validation - [x] Fix resolves the type deduction error - [ ] CI passes on this PR - [ ] Can be cherry-picked into #197 This is a minimal fix for the test infrastructure, not a change to the HNSW migration or search logic. Co-authored-by: liyuanyang <liyuanyang@users.noreply.github.com>
) ## Linked draft successor — original #183 remains open Refs #176. ## Current follow-up: `c0421168bd145077bf164f91c0d2b454a79760ef` - Fixed the remaining exported MCP route enum: `tools/list` now includes `lexical`. The in-process MCP regression reproduced the missing enum before the fix and now verifies both schema and `tools/call` HTTP forwarding. All 9 MCP tests pass on Linux; affected-package vet passes. - Added a strict, populated PostgreSQL sequential-upgrade regression and a DB-free embedded-migration continuity guard. Actual PostgreSQL 16.14 / pgvector 0.8.2 advances 23 → 24, verifies lexical backfill and full history, and accepts ordinary production startup afterward. No `AllowMissing` option or migration renumbering. - Cumulative source/base order is #194 → #197 → #195. #197 remains HOLD for text-query planner acceptance; #195 remains HOLD behind it. See `docs/MIGRATION_SEQUENCE.md` for deployment and existing-gap recovery boundaries. - Tests ran at `2f2e965b6794863d7f5a38a748262da4691beb35`; this final head only corrects the documented verification command to `scripts/verify.sh integration`. Fresh exact-head CI and independent review remain required. The earlier evidence below is historical, not a fresh-head approval. Preserves @sun-970 / liyuanyang's complete authored chain through `13ebe9efa6ba3c733199d374e8db3d107f598ca2` for #176. This draft makes the bounded reviewed corrections accessible; it does not replace independent review, CI, or human approval. Do not close #183 before a replacement is verified and merged. The original fork's Git-data write returned HTTP 404 and its repository permissions report `push:false`. No ACL override was attempted. This canonical branch preserves original commit identities and hashes. Added blobs, trees, and commits were checked against local Git hashes; no force update was used. ## Corrections - `scripts/verify.sh`: expect migration 24, matching this branch's lexical migration. The former expectation of 23 caused the PostgreSQL validation failure. - Refresh audited Web development dependencies, traceable to #192 commit `11e02e21ef2c3dbd2dae26e4376872e54e78ecb5` via a separate `cherry-pick -x` commit. Audit threshold unchanged. - No additional search/ranking changes beyond original #183. ## Validation at head `3d885a8427e06fd7175011ef0188a06f32028a3f` - PASS: migration to 24 on PostgreSQL 16.14 / pgvector 0.8.2 and PostgreSQL 17.10 / pgvector 0.8.3. - PASS: `TestLexicalSearchWithoutWorker`, five cases: lexical without worker, text/auto fail closed, CJK trigram, path restriction. Go tests cross-compiled for Linux and executed against real disposable PostgreSQL with `GOMAXPROCS=2` and serial package builds. - PASS: audited lockfile check; shared fix from #192 remains explicit, not attributed to this feature. - NOT CLAIMED: provider-backed vector retrieval or production benchmark quality. - REQUIRED: fresh CI on this exact head and independent review. Original-head CI/review is not approval of this draft. Original feature scope: model-free file-corpus lexical route, migration 0024, API/CLI/docs, managed-provider bypass. Scope excludes tokenizer, generation lifecycle, and ranking redesign.
|
#218 (the 42P08 I am not approving #197 yet: the PR still marks text-planner acceptance as HOLD, and this is in the #194 → #197 → #195 sequence. #194 is now on |
|
#218 的 42P08 测试修复已 squash 进本分支。 本 PR 仍标 planner HOLD,且与 main 可能冲突。请 @PeterGuy326 在 CI 绿且 HOLD 解除后再找非作者 CODEOWNER 批,不要现在合。 |
平台组 Review(崔泽生,assigned by @冯浩然)逐文件核过 diff,结论:索引基础部分质量过关,可以合;text planner HOLD 是正确的判断,不应为了变绿而放宽。 一、索引 DDL 与迁移——没有问题
二、text planner HOLD——同意,且补充一个可行方向
可行方向(供后续 issue 参考,不阻塞本 PR): 两阶段查询——Phase 1 用 HNSW 拿 WITH candidates AS (
SELECT DISTINCT ON (e.file_id)
e.file_id, e.embedding <=> $1::vector AS dist
FROM embeddings_text e
WHERE e.file_id IN (SELECT id FROM files WHERE user_id = $2 AND ...)
ORDER BY e.file_id, e.embedding <=> $1::vector
LIMIT $3 -- K * OVERSAMPLE
)
SELECT ... ORDER BY dist LIMIT $4 -- K关键约束:
这个设计变更应独立成 issue,不在 #197 范围。 三、合并阻塞项
建议:rebase 到 main 后即可合。text planner 集成拆成后续 issue(#173 保持 open 跟踪)。本 PR 合入后 #173 的"索引基础"子项完成,剩余"查询规划器集成"作为独立交付。 四、未声称验证的事同 waterbro-8:我没有在本机跑 PostgreSQL + pgvector 复现反例。我核对的是 diff 与文档的一致性、以及是否有"为变绿而放宽标准"的痕迹——没有。 |
#218) Refs #197 Fixes the failing PostgreSQL integration test in #197. ## Problem `TestTextANNFileSemanticsPostgres` fails with: ``` ERROR: inconsistent types deduced for parameter $1 (SQLSTATE 42P08) ``` The test SQL uses `$1::text` to cast a UUID parameter to text for the `storage_key` column, but `$1` is already used as a UUID in the first position. PostgreSQL cannot deduce a consistent type for the parameter. ## Solution Use separate parameters for the UUID and its text representation instead of casting: ```sql -- Before: VALUES($1,$2,'fixture', $3,0,'fixture',$4,$1::text,$5,$5) -- After: VALUES($1,$2,'fixture',$3,0,'fixture',$4,$5,$6,$6) ``` Where `$5` is `id.String()` (the text representation) and `$6` is the timestamp. ## Validation - [x] Fix resolves the type deduction error - [ ] CI passes on this PR - [ ] Can be cherry-picked into #197 This is a minimal fix for the test infrastructure, not a change to the HNSW migration or search logic. Co-authored-by: liyuanyang <liyuanyang@users.noreply.github.com>
4536392 to
95af9fe
Compare
Rebase 完成(按平台组结论)对照 https://github.com/bytefolk/mem/pull/197#issuecomment-5724905145:索引基础可以合;text planner HOLD 保持;#173 不关。 已做
没有改 还不能合的原因
请 @PeterGuy326 或 @Bindy-lbb 在本 head 上 Approve。CI 绿且 Approve 后即可 squash 合。合入后 #173 保持 open,只把“索引基础”记完成,planner 集成另跟。 重叠 PR:#219 也含 0025 + text continuation。#197 合入前不要合 #219;合入后 #219 需要丢掉重复 DDL,只留 planner(且平台组要求真实语料 recall,不是合成 fixture)。 |
|
崔泽生平台组 review:索引基础可合,text planner HOLD 应保留,不要为变绿放宽。同意。 本 PR 与 main 冲突,rebase 需要作者 @PeterGuy326。合入后 #173 仍应保持 open(shipping text 还不走 HNSW)。 #219 是另一条「改查询形状」的后续,不替代本 PR 的索引 DDL;按崔泽生的边界,#197 先合索引,planner 另开。 |
Rebase 后 CI(本 head
|
## Summary Completes the work [#213](#213) left undone against [#173](#173). - Migration **0025** (0024 is already lexical from #194) adds cosine HNSW indexes on `embeddings_text` (768), `embeddings_visual` (512), and `embeddings_face` (512) with `vector_cosine_ops`. - Shipping **text** search no longer uses `DISTINCT ON (f.id) ORDER BY f.id, distance` as the primary plan. That shape cannot use HNSW. It now walks `ORDER BY embedding <=> $1 LIMIT n`, keeps the first sighting of each file, excludes selected files, and **falls back** to the original exact `DISTINCT ON` query if a bounded scan underfills (one file owning many near chunks). - Relator text neighbors use the same continuation/fallback. - Visual already matched HNSW. Face clustering stays in-process; the face index is DDL only. - `index_generation_vectors` is not indexed (scope boundary). - Transactional `CREATE INDEX` (not `CONCURRENTLY`): a failed concurrent build leaves an `INVALID` index that `IF NOT EXISTS` will skip. - Wrong dimensions fail at the `vector(N)` column. Recall is **not** claimed; harness is [#175](#175). Does not reopen #213. #197 remains the earlier HOLD attempt; this branch is based on current `main` (schema 24) and takes 0025. ## Changes - `server/internal/db/migrations/0025_ann_hnsw_indexes.sql` - `server/internal/search/search.go` — text continuation + exact fallback - `server/internal/relator/relator.go` — same policy - `TestHNSWMigrationPostgres` — populated 24→25→24→25, ingest, dimension rejection, EXPLAIN - `TestTextANNFileSemanticsPostgres` — 101-chunk file still returns 10 eligible files - `scripts/verify_hnsw_indexes.sh` + `scripts/verify.sh` head 25 - `docs/VALIDATION_HNSW.md`, SPEC, CHANGELOG ## Validation ledger | ID | Criterion | Command | Status | | --- | --- | --- | --- | | V1 | Server builds, unit contracts | `make test-server` | Pending CI (sandbox has Go 1.22; module requires 1.25) | | V2 | Worker | `make test-worker` | Not affected | | V3 | Web | `make test-web` | Not affected | | V4 | Race | `make test-race` | Pending CI | | V5 | Fresh schema, rollback, PostgreSQL | `make test-integration` | Pending CI — `EXPECTED_MIGRATION_HEAD=25`; `TestHNSWMigrationPostgres` + `verify_hnsw_indexes.sh` | | V6 | DB race | `make test-integration-race` | Pending CI | | V7–V9 | Acceptance / MCP / visual quality | — | Not affected | | V10 | Recall | `make test-recall` | Not measured. Recorded harness: #175 | | Text semantics | 101-chunk file still yields k files | `TestTextANNFileSemanticsPostgres` | Pending CI | | Planner | EXPLAIN uses HNSW for text cosine-order and visual | `TestHNSWMigrationPostgres` + `scripts/verify_hnsw_indexes.sh` | Pending CI. No `enable_seqscan=off`. | Local sandbox could not run Docker or download Go 1.25, so EXPLAIN was not executed here. CI `memory-validation.yml` / `verify.sh integration` is the evidence path. ## Design notes - Defaults `m=16`, `ef_construction=64`. No iterative-scan GUC. - Empty `uuid[]` exclude lists are never sent as SQL NULL (`ANY(NULL)` would drop all rows). - Editing 0001/0019 is comment-only. Goose does not checksum like Flyway. Fixes #173 --------- Co-authored-by: waterbro-8 <318569545+waterbro-8@users.noreply.github.com>
Refs #173.
Scope after platform review
Index foundation only: migration 0025 cosine HNSW on
embeddings_text/embeddings_visual/embeddings_face, fail-closed verification, and honest docs. Shipping text search still usesDISTINCT ONand does not claim planner use.Per #197 (comment) (xiaocui-big / 平台组): index DDL quality is mergeable; text planner HOLD is correct and must not be weakened to go green. Planner integration stays on #173 (keep that issue open).
Rebase
Rebased onto
mainat884b54d(#109). The lexical commit was already on main via #194 and was skipped. Remaining commits:d4509bcHNSW foundation without overstating planner use95af9fefix(test): resolve parameter type inconsistency in HNSW semantics test #218 42P08 parameter-type fix in the semantics testHead:
95af9fe8b8436006b30faf2366e890a374969ce7Not in this PR
runTextANNrewrite / continuation / oversampling policyFresh CI on this exact head is required after the rebase. CODEOWNER approval is still required; last pusher is waterbro-8 so that account will not self-approve.