Elasticsearch 9.5 的列式存储与向量检索:搜索栈正在和向量库合并
Elasticsearch 9.5 的列式存储与向量检索:搜索栈正在和向量库合并
先说一个反常识的结论:Elasticsearch 9.5 里最像"向量库"的那部分改动,其实不是向量字段,而是一个索引模式。真正在变的也不是"ES 能不能做向量检索"——那件事 8.x 就做完了——而是 ES 愿不愿意为不同负载放弃同一套存储假设。9.5 的答案是:不愿意,所以它把假设拆成了多个可选的索引模式。
这意味着你的架构评审问题要换一个提法。以前的问题是"ES 的 kNN 够不够快",现在的问题是"我到底需不需要第二个系统"。
一、先把事实对齐:哪些是 GA,哪些只是预览
这一节的每一条我都对过官方页面,因为把预览特性写成 GA 是这类文章最常见的错。9.5 于 2026 年 8 月 4 日发布 GA 公告,但发布内容里混着三种成熟度,必须分开看。
| 特性 | 官方标注状态 | 落地形态 |
|---|---|---|
vectordb_document 索引模式 |
Elastic Stack 自 9.5 GA | 索引级 index.mode 设置 |
bbq_disk 的 auto_calibrate |
Elastic Stack 自 9.5 GA | dense_vector 的映射参数 |
columnar 索引模式 |
Elastic Stack 自 9.5 技术预览 | 索引级 index.mode 设置 |
logsdb_columnar 索引模式 |
Elastic Stack 自 9.5 技术预览 | 索引级 index.mode 设置,带日志默认值 |
| Prometheus 与 PromQL 原生支持 | 9.5 GA | 远程写端点加 ES |
也就是说,“ES 变成列式数据库"这件事,官方自己写的是 technical preview(技术预览),不是 GA。我的判断是这一条必须写进方案 PPT 的风险栏:预览意味着可以试、不能承诺 SLA,也意味着映射层的语义还可能变——文档里 ignore_above 的行为就已经标成了"Planned”。
还有一个我核不实的地方,如实写"待核":官方文档提到"9.6 及之后创建的索引默认开启 auto-calibration 并使用 iso_sizing profile",说明 9.6 已经在推进;但我在 elastic.co 上没有检索到 9.6 的 GA 公告页,所以本文所有结论只绑定 9.5,9.6 的具体行为标为待核,不做推测。
二、先给一张图:9.5 把存储选择推到了索引层
flowchart LR
W[写入请求] --> IM{选择索引模式}
IM -->|standard| S1[倒排索引加 doc values]
IM -->|columnar| S2[仅 doc values 单一存储]
IM -->|vectordb_document| S3[向量文件优先预加载]
S1 --> Q[查询层]
S2 --> Q
S3 --> Q
Q --> A1[全文检索]
Q --> A2[聚合与排序]
Q --> A3[kNN 近似检索]
A1 --> M[结果归并与重排]
A2 --> M
A3 --> M
这张图想让你看见什么:9.5 之前,ES 的存储结构基本是固定的,你能调的只有字段级参数;9.5 之后,第一个分叉发生在"建索引的那一刻",而且三条分支共用同一个查询层、同一套 API。这不是加了一个功能,是把一个原本隐式的架构决策显式化了。代价是它不可改:mode 只能在创建索引时设定,建完就不能改,选错的唯一出路是重建索引再 reindex。
三、列式存储省掉的不是索引,是"第二份数据"
很多人把列存理解成"把行存换成列存",这个理解在 ES 这里会跑偏。按官方那篇讲列存原理的文章,Lucene 的 doc values 从 1.0.0 起就是列式结构,聚合和排序一直靠它。所以列式存储对 ES 不是新东西,它是从"辅助结构"升级成了"唯一结构"。
standard 模式下同一份字段值其实存了两遍:一遍进倒排索引(字符串)或 BKD 树(数值)用于过滤,一遍进 doc values 用于聚合排序。columnar 模式把前者默认关掉(index 映射属性默认为 false,文本字段例外,因为全文检索离不开分析链),只留 doc values,再补两个东西来救查询性能:doc values 的批量读取,和 doc values skipper。
skipper 是轻量跳表,存 min/max 之类的元信息,让范围查询能整段跳过文档。它的效果完全取决于数据在磁盘上的物理顺序,所以列式模式下 index sorting 不是可选项而是设计的一部分。logsdb_columnar 默认给 host.name 升序加 @timestamp 降序,逻辑很朴素:同一台机器的日志挨着放,run-length 和 delta 编码才有效。
1 | curl -X PUT "localhost:9200/orders-columnar" -H 'Content-Type: application/json' -d' |
这段代码在说明什么:mode 与 sort.field 必须一起给。只写 mode 而不设计排序键,skipper 就退化成"还是得扫",你会得到一个存储变小、但范围查询变慢的索引,然后误判"列存不行"。message 是 text,在列式模式下仍然默认建倒排索引——这正是 ES 相对纯列式数据库的取舍:它不愿意为了省空间牺牲全文检索,只在 logsdb_columnar 里把倒排索引压缩到只剩 message 一个字段。
四、收益边界:什么数据形态下反而不划算
这里我不太同意 Elastic 那篇公告里"zero changes to APIs, dashboards, or integrations"的说法。API 形状确实没变,但语义变了,而且是那种会在半夜炸出来的变。
第一,映射被强制拍平。列式模式下 object 和 passthrough 字段会被自动拉平,nested 只有受限支持,且 nested 之间不能套。原来靠 nested 表达一对多关系的订单明细、多标签文档,迁移过来要么改模型要么放弃。
第二,_source 不再是原文。列式模式只支持合成源和列式源,store: true、synthetic_source_keep 全部被拒,传统 stored source 不支持。官方写得很直白:columnar 用更小的存储换更低的还原保真度。如果你的下游有"取出原始 JSON 原样丢给别家系统"的逻辑,这一条就是硬阻断。
第三,doc_values: false 会直接报映射错误。以前为了省空间把不需要聚合的字段关掉 doc values,这个习惯在列式模式下会让建索引失败,多字段除外。
第四,高基数不是问题,但"既要全文又要过滤"的低基数标签字段是问题。低基数字符串用字典加 ordinal 编码,压缩率极好;高基数字符串走块压缩也能接受。真正难受的是那种"必须按它做精确过滤命中率极高、同时又必须参与全文相关性打分"的字段——列式模式默认不建倒排,过滤只能靠 skipper 跳段,效率和原来不在一个量级。
我踩过的坑就在这:拿一个"搜索加多维筛选"的电商索引试 columnar,存储降得很漂亮,但带筛选条件的关键词查询 P99 直接翻了几倍。结论是列存适合"分析型加日志型"负载,不适合"筛选密集型的交互式搜索"。前者是少数列、海量行、扫描为主;后者是宽表、随机命中、过滤条件五花八门。这个区分比"数据量大不大"有用得多。
五、VectorDB 索引模式:从"字段类型"变成"索引级承诺"
vectordb_document 是这个版本里我最看重的改动,因为它是 GA 的,而且它改变的是索引的默认假设而不是某个字段的参数。它做的事一句话能说完:把原本要你自己调的向量相关默认值全部预置好。
| 默认项 | standard 模式 | vectordb_document 模式 |
|---|---|---|
| 向量元素类型 | float |
bfloat16,原始向量存储成本约减半 |
| 动态映射识别阈值 | 128 维以上转 dense_vector |
32 维以上就转 |
_source 中的向量 |
保留 | 排除,且不允许改回 false |
| 文件预加载 | 无 | 预加载 HNSW 图、标量量化、二值量化、IVF 质心文件 |
| 段内合并 | 默认限流 | 开启段内并行合并并关闭合并限流 |
这张表说明一件事:向量检索的性能上限其实不在查询时,而在"段长什么样"。预加载和合并策略决定了查询时能不能不碰盘,所以把它们塞进索引模式,比让你在 6 个字段参数上试错要合理。
sequenceDiagram
participant App as 应用服务
participant CO as 协调节点
participant SH as 数据分片
participant DS as doc values 与向量文件
App->>CO: kNN 请求 携带 topk 与过滤条件
CO->>SH: 分发到各分片 携带段级参数
SH->>DS: 用 skipper 元信息跳过不匹配文档区间
DS-->>SH: 返回候选文档区间
SH->>DS: 读取 bfloat16 向量做近似距离计算
DS-->>SH: 返回距离结果
SH->>SH: 段级 oversample 后重打分
SH-->>CO: 返回分片内 topk
CO->>CO: 归并排序并截断
CO-->>App: 返回最终 topk
这张图想让你看见什么:kNN 在 ES 里从来不是"一步查完",它是"过滤先缩范围、近似距离出候选、段内 oversample 重打分、协调节点归并"四段。列式模式和向量模式的默认值,作用点分别在第一段和第三段——skipper 决定前面能跳过多少文档,oversample 决定后面要重算多少。这也解释了为什么单独调一个 m 或 ef_search 常常没效果:它们不控制这两个真正决定成本的段。
关于 HNSW 调参经验能不能迁移,我的判断是"一半能迁移,一半会骗你"。能迁移的是形状:m 变大查得准内存也涨、int8 到 int4 到 bbq 是逐级更省更糙、m 在更新映射时只能不变或变大。会骗你的是绝对值:默认元素类型变成 bfloat16,量化位宽和 oversample 由自动校准按段决定,你在 8.x 上调出来的那套"m=32 加 int8 加 oversample 2.0"参数组合,换到 9.5 的 vectordb_document 上基准完全不同,照抄只会得到一个既没调优也没默认的组合。
还有一个坑必须说清:auto_calibrate 在 9.5 里只接受布尔值、默认关闭、且字段建好之后不能改。也就是说想用它,必须在建映射时就写上,否则只能 reindex。另外文档里有个容易漏掉的细节:合并后少于 10000 个向量的段不参与校准,回落到 3.0 倍默认 oversample。所以小索引上开了校准基本没感知,这不是它没生效,是样本不够。
六、我的判断:分界线现在该画在哪
flowchart TD
subgraph T1[方案一 ES 一体化]
A[业务数据] --> B[单一集群]
B --> C[文本字段 走倒排索引]
B --> D[向量字段 dense_vector]
C --> E[同一请求内混合过滤加 kNN]
D --> E
E --> F[一次结果返回]
end
subgraph T2[方案二 ES 加专用向量库]
G[业务数据] --> H[Elasticsearch]
G --> I[向量库 Milvus 或 Qdrant]
I --> J[返回向量 ID 与距离]
J --> K[回查 ES 拿正文]
H --> K
K --> L[应用层归并两套分数]
end
这张图想让你看见什么:两套方案的差别不在查询快慢,而在"一致性边界"和"分数可比性"。方案一里过滤和向量在同一个请求内完成,返回的是经过同一套打分的排名;方案二里你拿到的是两组不可比的分数,怎么融合是应用层的活,而且双写就意味着两边永远可能不一致。
我的判断是分界线可以按三个条件画,命中任意一个就该上专用向量库:
- 向量规模到了千万级以上,且要求过滤与向量在同一次判定内完成。ES 的 kNN 需要先过滤再算距离,过滤后的候选集很小、召回会掉;专用向量库普遍支持带过滤的图遍历,这类负载差距是结构性的。
- 需要多向量召回或稀疏加稠密混合的复杂管道。这不是 ES 不行,是编排逻辑写在应用层更清楚。
- 检索的更新频率远高于正文更新,或者向量由独立流水线版本化管理。
反过来,以下几类我明确不建议再引一个专用库:
- 向量规模在百万到几百万、QPS 在几十这个量级、过滤条件基本是固定的几个业务维度。这种规模 ES 一体化完全够,多引一个系统换来的是双写一致性、双份监控和一份新的值班说明书。
- 正文和向量必须同源同版本。一次 reindex 就能把两边拉齐,这在 RAG 里价值很高。
- 已经在用 ES 做日志或搜索,团队没有额外的运维带宽。这里代价不是钱,是人的注意力。
至于 pgvector:它确实不需要新系统,省掉了运维一个新集群的成本,HNSW 和 IVFFlat 两种索引也够用。但它的定位是"把向量放进既有 Postgres",如果你已经有成熟的 Postgres 和事务需求,它是最省心的选择;如果检索同时要吃全文相关性和复杂聚合,你还是得回到 ES。这不是谁替代谁,是三个不同起点的最短路径不同。
七、可以马上做的事
- 先用一个只读副本验证
columnar:选一个日志或指标索引,重建时同时给mode和sort.field,比对存储占用、范围聚合延迟和你最常用的那种"带筛选的关键词查询"延迟。三个数字一起看,别只看存储。 - 把
doc_values: false、store: true、nested、synthetic_source_keep这几个关键字在现有模板里 grep 一遍,命中就是在给列存迁移埋雷。 - 新建向量索引直接上
vectordb_document,并在映射里显式写auto_calibrate: true——9.5 默认关且建后不可改,漏了就得 reindex。同时确认_source里没有向量(该模式强制排除),别让下游的取原文逻辑踩空。 - 老索引先别急着全部切换。
mode不可逆,把"哪些索引是实验性的"写进命名规范里,比事后靠记忆区分安全。 - 做一次选型复盘:把"要不要保留专用向量库"从技术偏好问题改成三个可测条件——向量规模、过滤与向量的耦合度、双写不一致的容忍度。前两个能量化,第三个问业务方。
- 盯着 9.6。文档已经写了 9.6 起自动校准默认开启并用
iso_sizing档位,但那部分标注仍是计划态,等 GA 公告出来再改方案。
八、最后压一句
9.5 真正在做的事,是把"搜索栈"和"向量库"这两个原本靠外部拼装的系统,用同一个索引模式机制塞进一个内核里。它没打算在所有维度赢过 Milvus 或 Qdrant,它打算赢的是"你不需要为了一件本来能顺手做完的事,再养一个系统"。判断自己属于哪一边,比判断哪个引擎 benchmark 更高更重要。


