matrixorigin / matrixorigin/matrixone
[Bug]: Catalog tombstone bailout causes repeated full IVF index builds (3 retries, 109 minutes)
- Dominant language
- Go
- Stars
- 1.9k
- Forks
- 311
- Avg merge
- 1d 3h
- Merged PRs (30d)
- 768
Description
### Is there an existing issue for the same bug?
- [x] I have checked the existing issues.
相关历史问题:#21456(已关闭)也报告过长时间 IVF CREATE INDEX 在内部目录写入阶段出现 `def changed` 并重做。本报告提供 main 新现场的完整时间线和明确的 `mo_tables / tombstone_rows_bailout` 证据;不预先断言与旧问题完全同根因,也不认为旧问题关闭即证明此路径已修复。
### Branch Name
main
### Commit ID
`6eee64625e7e2cefd0f3dfeb61606f637111e057`
历史对照:`9ae27d218`。两次 MO revision 不同,因此对照耗时不能直接当作同条件代码二分结论。
### Other Environment Information
- TKE Main Family Weekly,1 TP CN + 2 AP CN。
- 本次 IVF 使用租户 `tp_tail_index`,连接路由参数 `workload=tp`。
- CN:14 CPU / 48Gi;TN:14 CPU / 32Gi;CN memory cache 12Gi。
- Namespace:`mo-main-family-34095969029-1`。
- IVF 执行 Pod:`nightly-regression-dis-tp-cn-dnw8l`。
- Run:https://github.com/matrixorigin/mo-nightly-regression/actions/runs/34095969029
- IVF Job:https://github.com/matrixorigin/mo-nightly-regression/actions/runs/34095969029/job/101743926213
- Runner PR:matrixorigin/mo-nightly-regression#1543;本次 workflow revision `b9381fd49c41f19ba50e5e1a44d5571db3f7ef23`。
- vector benchmark revision:`heni02/vector_benchmark@0bae04528673d7756c4ee2478ac8e4e41f54df1b`。
- 数据:1,000 万行,VECF32(768),同一 S3 CSV;索引 lists=3162、float32、L2、kmeans_train_percent=2、kmeans_max_iteration=20。
- 用户后来取消查询阶段并删除了集群;本文依据保存的 GitHub 日志和历史 Loki/Prometheus/Pyroscope,不依赖仍存活的集群。
### Actual Behavior
**Runner 只执行了一次 CREATE INDEX,但 MO 在索引收尾阶段连续三次返回 `txn need retry in rc mode, def changed`,使整个索引构建执行了四遍,总耗时 109 分 36 秒。**
主要耗时不是 LOAD,也不是 Runner 外层反复调用或等待:
| 阶段 | 本次 Family | 旧 Main 对照 |
|---|---:|---:|
| S3 LOAD,affected_rows=10000000 | 2561.35s(42分41秒) | 2017.60s(33分38秒) |
| CREATE INDEX | 6575.73s(109分36秒) | 1309.06s(21分49秒) |
| 准备合计 | 9137.28s(152分17秒) | 3327.15s(55分27秒) |
旧 Main IVF Job:https://github.com/matrixorigin/mo-nightly-regression/actions/runs/34041258710/job/101564244290
两次数据源、vector benchmark revision、向量维度和 CREATE INDEX 参数相同。旧实例的 CN 内存为55Gi,拓扑和并发负载不同,不能把所有差距都归因于某个新 MO commit。
#### 四遍构建的证据
以下均为 **2026-09-07 UTC**(北京时间 +8 小时),同一事务:
`d55a2fdfd745180818d2fadb8b696704`
| 遍数 | clustering_start | 千万行 CENTROIDX 映射完成 | 映射耗时 | 结果 |
|---|---|---|---|---|
| 1 | 13:18:00.471 | 13:43:29.525 | 24m6.084s | 13:44:00.195 def changed |
| 2 | 13:45:13.968 | 14:12:04.626 | 25m26.369s | 14:12:17.576 def changed |
| 3 | 14:12:17.823 | 14:39:24.784 | 25m42.605s | 14:39:36.642 def changed |
| 4 | 14:39:36.752 | 15:07:22.266 | 26m19.836s | 15:07:35 客户端确认建索引成功 |
每遍映射 SQL 都记录 `AffectedRows=10000000`,底层隐藏索引表 UUID 每遍不同。不是同一行日志被重复显示,也不是只重试一条轻量注册语句:采样、聚类、全量映射确实再次执行。
#### 重试前的直接证据
第三次失败前的日志(摘录非敏感字段):
```json
{"time":"2026/09/07 14:39:36.610999 +0000","caller":"disttae/txn_table.go:2836","msg":"txn pk persisted check changed","reason":"tombstone_rows_bailout","table-name":"mo_tables","txn":"d55a2fdfd745180818d2fadb8b696704/Active/S:1788790337404656009-1","from":"1788790337404656009-1","to":"1788791976451248851-1","key-count":1,"changed-objects":5,"deleted-objects":66,"candidate-blocks":0,"check-tombstone":true,"duration":"220.836µs"}
```
紧接着:
```text
2026/09/07 14:39:36.642518 +0000
caller=compile/sql_executor.go:199
msg=internal sql executor error
error=txn need retry in rc mode, def changed
sql=REPLACE INTO mo_catalog.mo_index_update VALUES
(2, 508246, 'tke_family_weekly_ivf_10m',
'historical_file_blocks_wiki_ivfflat10m', 'idx_l2',
'ivfflat_reindex', ...)
txn=d55a2fdfd745180818d2fadb8b696704
```
前两次亦有相同链路:
| 时间 | 表 | reason | key-count | changed/deleted objects | candidate-blocks |
|---|---|---|---:|---|---:|
| 13:44:00.195184 | mo_tables | tombstone_rows_bailout | 1 | 7 / 1 | 0 |
| 14:12:17.532592 | mo_tables | tombstone_rows_bailout | 1 | 13 / 1 | 0 |
| 14:39:36.610999 | mo_tables | tombstone_rows_bailout | 1 | 5 / 66 | 0 |
这些计数是对象数,**不是 tombstone 行数**。超过5万行的判断来自该 reason 对应的明确代码分支,不是把上述对象计数当作行数。
### Expected Behavior
在其他租户/表正常发生目录变更时,昂贵的 IVF 构建不应因粗粒度目录变更判断,在收尾阶段反复丢弃已完成的全量计算。
- 需要保留真正 schema/PK 冲突所要求的正确性保护;不能简单忽略 `def changed`,也不能无条件把 `changed` 改为 false。
- 尽可能精确判定目标目录键是否受到影响,或在昂贵工作前完成必要的元数据校验/锁定,避免晚期保守重试放大。
- 如果整条 DDL 必须重试,应提供可观测的次数、原因和阶段,避免客户端仅看到一次超长 CREATE INDEX。
### Steps to Reproduce
**现场触发条件已观察到;以下是复现建议,不宣称已完成独立、确定性的最小复现。**
1. 使用上述 main commit,在测试集群中创建普通租户和含 `embedding VECF32(768)` 的测试表,导入1,000万行向量。
2. 执行:
```sql
CREATE INDEX idx_l2 USING ivfflat
ON historical_file_blocks_wiki_ivfflat10m(embedding)
lists=3162 op_type "vector_l2_ops" quantization "float32"
kmeans_train_percent 2 kmeans_max_iteration 20;
```
3. 同时在独立测试租户/对象上运行高频建删表、建删账号等目录 DDL,覆盖长事务运行期间的目录 tombstone 持久化/合并。**不要直接修改系统表,不要在共享生产环境复现。**
4. 检查同一建索引事务是否出现 `mo_tables / tombstone_rows_bailout` → `REPLACE mo_catalog.mo_index_update` 报 def changed → 隐藏索引表重新生成及千万行映射再次执行。
5. 对照组应固定同一镜像、资源、数据和工具,只移除并发目录 DDL。
更小的回归测试可在存储层构造:目标目录主键未变化、其他键产生超过阈值的 tombstone,验证精确冲突语义;再通过索引构建入口验证不会在尾部反复全量重做。修复不能破坏真实元数据变更时的重试。
### Additional information
#### 源码链路与回归性质
1. [tombstonePKExistsInRange 的 50000 行提前返回](https://github.com/matrixorigin/matrixone/blob/6eee64625e7e2cefd0f3dfeb61606f637111e057/pkg/vm/engine/disttae/txn_table.go#L3096):在目标键精确匹配之前,累计 tombstone rows 超过阈值即返回 `true, "tombstone_rows_bailout", nil`。
2. [LockMeta.lockMetaRows](https://github.com/matrixorigin/matrixone/blob/6eee64625e7e2cefd0f3dfeb61606f637111e057/pkg/sql/compile/lock_meta.go#L189):目录键锁/校验的重试被转为 `ErrTxnNeedRetryWithDefChanged`。
3. [idxcron.RegisterUpdate](https://github.com/matrixorigin/matrixone/blob/6eee64625e7e2cefd0f3dfeb61606f637111e057/pkg/vectorindex/idxcron/cmd.go#L31):沿用建索引事务,切到 system account,通过内部 SQL executor 写 `mo_catalog.mo_index_update`。
4. 现场日志证明该错误向外传播后,昂贵建索引部分整体重做三次。
`def changed` 在此不能被解读为已经证明目标 IVF 表发生真实 schema 变化:这三次触发的是不进行目标 PK 精确匹配的保守分支。尚未逐条归属所有目录 tombstone 的生产者。
旧 `9ae27d218` 与新 `6eee64625` 的 `txn_table.go`、`lock_meta.go`、`idxcron/cmd.go`、`iscp_util.go` 在该对比范围内没有差异。**目前属于已确认的重试放大/性能可用性问题,未证明是两次运行之间新引入的代码回归。**
阈值分支可追溯到 #23868(2026-03-17);该 PR 原本将更粗粒度判断改为 PK-aware 检查,保留了成本阈值。因此这里只记录机制来源,不直接把它认定为整个缺陷首次引入的 PR。
#### 为什么旧 Workflow 更难触发
- 旧 IVF:2026-09-06 21:46–23:41 UTC;旧 Concurrent:2026-09-07 03:43–05:05 UTC,无重叠。
- 新 IVF CREATE INDEX:2026-09-07 13:18–15:07 UTC;新 Full Concurrent:13:00–14:14 UTC,有重叠。
- TP/AP CN 分离不意味着系统目录和事务元数据隔离。新并行安排是重要触发背景,不是 Runner 重复发送 CREATE INDEX 的证据;停止重叠只是规避方案,不能作为 MO 根因修复。
#### 其他已排查项及范围限制
- 本次 LOAD 和 CREATE INDEX 最终均成功;取消发生在第一个 recall mode,约完成2076/10000 queries。六模式召回未完成,不能标为通过。
- 建索引期间 TP CN 平均约12.9核(limit14);采样 CPU profile 主要在 `productl2 → GoBruteForceIndex.SearchFloat32 → L2Distance`。这是实际重复计算,不是 Runner 空等。
- 准备窗口内 Pod restart count 全部为0;未见 OOM 重启来解释四遍构建。
- 两次镜像构建参数未见 debug flags 差异。
- LOAD 的27%差距和单遍映射的剩余差距未通过同版本控制实验归因;本 Issue 的确认范围是三次晚期保守重试导致的主要耗时放大,不把所有性能差异都纳入同一根因。
历史日志检索:Loki datasource `loki`,namespace/pod 如上;以三个失败时刻分别取±约2分钟窗口,过滤事务 ID `d55a2fdfd745180818d2fadb8b696704`。请在日志保留期内保存必要证据。
Contributor guide
Assessment
This issue has not been assessed yet.