改 mapping 或分词器时,通常会新建一个版本化索引再全量导入。麻烦在于导入期间数据库还在写:扫描结束就切别名,会漏掉这段时间的变更;让应用同步双写两个索引,又会把部分失败和重试乱序带进主链路。
一个比较稳的小做法,是让数据库事务只写业务表和 outbox,索引消费者按索引分别维护 checkpoint。新索引创建后先记下起始序号,用一致性快照做全量扫描;每条文档都带业务版本,并用 external version 写入。全量完成后,从 outbox 重放起始序号之后的变更,旧事件即使晚到,也不能覆盖新版本。outbox 的保留时间要覆盖最慢的一次全量导入,不能只按平时消费延迟设置。
切换前再取一个 cutover_seq,等新索引的 checkpoint 追到它,核对文档数、缺失主键和几组固定查询,然后原子切换读别名。切换后的事件仍由同一条可重放日志投递,不需要在应用请求里临时改双写逻辑。旧索引可以在观察窗口内继续消费,用于快速切回;删除前先确认两边 checkpoint 都越过观察窗口。
验收时可以故意打乱同一主键的两条变更、在全量中途重启消费者,并在别名切换前后持续写入。最终应满足:新索引没有漏掉 cutover_seq 之前的记录,旧版本事件不能覆盖新文档,切回旧索引时也不会回到观察窗口之前的数据。