Phorge 现代化改造实战(十二):兼容 Elasticsearch 5、6、7,版本配置决定整个索引结构
内容提要
本文讲述Phorge搜索服务在接入真实Elasticsearch和Meilisearch后暴露的兼容性问题。因Elasticsearch版本升级移除mapping type,Gorge需按版本调整索引结构、写入路径和查询过滤,并修复include_in_all、not query等过时语法。Meilisearch则需声明id为可过滤属性。通过增加结构关系测试和真实后端联调,确保协议兼容。
延伸解读
版本配置决定索引结构,填错方向故障不同
ES_VERSION 不只是日志展示,它决定 Gorge 生成哪种 mapping 结构。填低(如 5)时,Gorge 会生成多 type mapping,Elasticsearch 7 会直接拒绝创建索引,报 mapper_parsing_exception,错误明显。填高(如 7)时,旧版 Elasticsearch 5 可能接受 _doc 作为普通 type,索引能建、文档能写,但 Phorge 自带引擎不识别新结构,导致 sanity check 和类型过滤分歧,错误隐蔽。因此必须与真实集群主版本一致。
文档类型从索引结构迁移为普通字段
Elasticsearch 5 及更早版本中,文档类型(TASK、DREV 等)作为 mapping type 存在于索引结构中,URL 路径也包含类型。6.x 起只允许一个 type,7.x 起 typeless,类型参数被移除。Gorge 的修复是:版本 <6 保留多 type 结构;6 使用 _doc;>=7 将 properties 直接放在 mapping 根下。同时,写入路径从 /{index}/{TYPE}/{phid} 改为 /{index}/_doc/{phid},并在文档体中增加 docType
过时语法与主键过滤陷阱
Elasticsearch 5 已移除 not query,旧实现生成 { "not": { "ids": ... } } 会直接报错,需改用 bool.must_not。include_in_all 参数在 6.0 后不允许出现在 mapping 中,只在 <6 时生成。Meilisearch 要求过滤表达式中的属性必须提前声明为 filterableAttributes,即使 id 是主键,也不能直接用于 id != 过滤,否则返回 400。这些错误在单元测试中可能被掩盖,因为测试只验证 JSON 生成,不验证
测试应验证结构关系而非快照
为避免测试与实现共享同一误解,Gorge 新增了结构关系测试,如 TestEveryFilterableAttributeIsDeclared,它从实际生成的过滤表达式中提取属性名,逐一确认已声明,并守卫测试本身不空转。Elasticsearch 侧则断言不同版本下 mapping 的结构关系,而非保存完整 JSON 快照,因为快照会锁死无关细节,导致维护困难。真实后端联调(demo 编排)让 Elasticsearch 和 Meilisearch 亲自解析生成的协议,补上单元测试无法覆盖的验证。
Q&A
Phorge搜索服务在接入真实Elasticsearch和Meilisearch后遇到了哪些兼容性问题?
主要问题包括:Elasticsearch 7.17创建索引时返回mapper_parsing_exception,因为旧代码为每种文档类型生成mapping type,而ES 7已移除mapping type;Meilisearch在带exclude的查询上返回400,因为id属性未声明为可过滤属性。此外还有include_in_all过时参数和not query语法在ES 5.0已被移除等问题。
Elasticsearch 5、6、7在mapping type上有什么区别?Gorge如何根据版本调整索引结构?
ES 5及更早版本允许一个索引有多个mapping type,如TASK、DREV等;ES 6只允许一个type,官方建议使用_doc;ES 7及以后使用typeless mapping,properties直接放在mapping根下。Gorge通过ES_VERSION判断:版本<6时保留多type结构,版本6使用_doc作为type名,版本>=7则直接使用properties。
为什么Gorge在Elasticsearch 7中写入文档时使用_doc路径,并在文档中增加docType字段?
因为ES 7移除了mapping type,不能再使用/phabricator/TASK/PHID-TASK-...这样的typed endpoint,所以统一使用/_doc/路径。为了在查询时区分文档类型,Gorge在文档体中增加docType字段(如"docType": "TASK"),并声明为keyword类型,用于精确过滤。
在Elasticsearch typeless mapping下,Gorge如何实现只查询特定类型(如任务和评审)?
在typeless mapping下,搜索路径只能指向整个索引(如/phabricator/_search),类型限制通过bool query的filter条件实现,例如使用terms查询docType字段:{"terms": {"docType": ["TASK", "DREV"]}}。使用filter是因为文档类型不影响相关性评分。
include_in_all参数为什么在Elasticsearch 6及以上版本中不能使用?Gorge如何处理?
include_in_all参数与_all字段绑定,而_all字段在ES 6.0中被移除,因此include_in_all在6.0及以后创建的索引中不允许出现,否则会导致mapping解析失败。Gorge只在版本<6时生成include_in_all: false,因为ES 5的兼容检查需要它。
Gorge如何修复not query语法在Elasticsearch 5.0被移除的问题?
旧实现使用not query,但not query在ES 2.0弃用、5.0移除。修复时直接使用bool.must_not,因为目标版本(5、6、7)都支持。例如:{"bool": {"must_not": [{"ids": {"values": ["PHID-TASK-..."]}}]}}。
Meilisearch为什么要求id属性必须声明为可过滤属性?Gorge如何修复?
Meilisearch要求所有出现在过滤表达式中的属性都必须提前在filterableAttributes中声明,即使id是主键也不例外。Gorge在filterableAttributes()中增加了"id",确保id != ...这样的过滤表达式能正常工作。
Gorge增加了哪些测试来确保Elasticsearch和Meilisearch的兼容性?
Elasticsearch侧增加了结构关系测试,如TestVersion5KeepsAMappingPerDocumentType、TestVersion6NestsASingleMappingType、TestVersion7HasNoMappingTypes等,使用递归辅助函数findKey和findValue断言key的存在性。Meilisearch侧增加了TestEveryFilterableAttributeIsDeclared,它运行真实SearchQuery,提取buildFilters生成的表达式属性,并验证它们都被filterableAttributes声明。此外还增加了真实后端联调测试,通过demo编排运行18个场景。
升级到Gorge 2026.09.08-r1后,用户需要执行哪些操作来应用修复?
用户需要:1) 将ES_VERSION设置为真实集群主版本(如ES_VERSION=7);2) 重新创建并填充索引,执行./bin/search init和./bin/search index --all --force。注意bin/search init会删除并重建索引,需确认可重建并做好备份。此外,升级Gorge服务后还需确认Phorge的cluster.search配置正确,并完成流量切换。