Elasticsearch 9.5 跑在 5 年前的老 PC 上,日志硬扫每秒 343 万行

Elasticsearch 9.5 的 logsdb_columnar 模式,把日志文本纯列式存放,且只保存一份。上一篇文章我们用它做了极致压缩,这次换个角度:不建索引,靠硬扫描实时搜日志,到底能有多快?

我在家里一台 5 年前的老 PC(CPU:2021年 AMD 5700G 家用 CPU,Zen3 架构,8核心16线程,内存 32GB DDR4, NVME SSD)上横向对比了 7.17、8.19、9.5 三个版本的实时扫描性能。结论先放这里:

ES 9.5 + ES|QL:每秒扫描 343 万行日志,比 7.17 时代的 DSL 硬扫快了 8 倍以上,同样比传统 DSL 查询语言成绩最好的 9.5 DSL 快 2.5倍

什么是日志硬扫描?

grep 是最经典的日志硬扫:不建任何索引,逐行暴力匹配。Splunk、Loki 等系统也走类似路线------存储便宜、写入极快,代价是查询时全靠扫描算力硬顶。

大量低优先级的日志场景适合这个思路:数据量巨大、成本敏感、但真正需要搜索的次数并不多。为一堆几乎不会被查的日志建全文索引,是典型的"为小概率事件付大额保费"。

Elasticsearch 如何实现不建索引也能实时扫描?

答案是 Runtime Field :这个特性在 ES 7.11 版本就开始支持,查询时用脚本动态获取字段值,再对它做匹配。message 日志文本字段设置 "index": false,不建倒排索引,实时搜索脚本获取原始文本有两个来源:

_source(行存) :一个文档的所有字段打包存在一起。读取一条日志文档的完整内容很快,但只想读 message 一个字段时,也得把整行解压出来。脚本写法:params._source.message

doc value(列存) :同一字段的所有值连续存放,不同字段分开。只扫 message 一列时,磁盘 IO 和 CPU 缓存都极其友好。脚本写法:doc["message"].value

另外还有 synthetic source :不真正存 _source,查询时从 doc value 反向 "合成" 出来 ------ 省一份存储,但读取时多一步重组开销。logsdb 模式默认就用它。

DSL 与 ES|QL:两代查询引擎的分野

DSL 面向高并发搜索场景设计:每个查询被限制在有限资源内(单个 shard 的查询基本跑在一个核心上),保证成百上千个并发请求互不拖垮。

ES|QL 面向分析型查询和计算:单个查询就可以吃满全部 CPU 核心,引擎按列式数据组织计算,天生匹配列存。对 "一次扫几百万行" 的分析任务,这才是正确的引擎形态。

当然,硬扫描不等于无脑全扫。时间范围和标签过滤永远是第一道闸门,先把扫描范围切小,再谈扫描速度。

测试设计

测试数据集和之前一篇文章一样,采用 2022 年 CCF 国际 AIOps 挑战赛 数据集中的一个日志目录,2022-03-20-cloudbed2。这是一套基于微服务电商 Demo(hipstershop)的标题一可观测性数据,日志形态非常接近真实生产的应用日志。

原始 CSV 4,856,187,767 字节 = 4631 MB
zip 压缩后 431,613,887 字节 = 411.6 MB(压掉 91.1%)
日志条数 16,410,236
平均单条 295.9 字节

原始数据样例:

sql 复制代码
`

1.  log_id,timestamp,cmdb_id,log_name,value

3.  KN43pn8BmS57GQLkQUdP,1647761110,cartservice-1,log_cartservice-service_application,etCartAsync called with userId=3af80013-c2c1-4ae6-86d0-1d9d308e6f5b

5.  M943pn8BmS57GQLkQUdP,1647761111,cartservice-1,log_cartservice-service_application,     Executed endpoint 'gRPC - /hipstershop.CartService/GetCart'

7.  Ot43pn8BmS57GQLkQUdP,1647761111,cartservice-1,log_cartservice-service_application,mptyCartAsync called with userId=e25fe877-95f8-4a44-ae29-ae6c3eca824a

`AI写代码

字段映射

CSV 列 ES 字段 处理 低基数字段不同值数量
log_id _id 20 个字符,与 ES 自动生成的 ID 形态一致,不重复写入,交给 ES 自动生成,自动生成_id 比业务指定id具有更好的性能
timestamp @timestamp 秒级 epoch,×1000 转毫秒
cmdb_id host.name keyword 31个不同值
log_name logname keyword 22个不同值
value message match_only_text

查询模式:时间范围过滤 + message 中匹配一段卡号样式的字符串(*4432-8015-6152-0454*),分别测 10 分钟、1 小时、1 天三个时间窗口。

时间窗 区间内文档数 命中
10 分钟 180,563 137
1 小时 1,104,256 825
1 天 6,972,239 5,195

DSL 查询示例(doc value 版):

bash 复制代码
`

1.  GET logs-aiops-22/_search?request_cache=false
2.  {
3.    "runtime_mappings": {
4.      "message_rt": {
5.        "type": "keyword",
6.        "script": {
7.          "source": """
8.            if (!doc["message"].empty) {
9.              emit(doc["message"].value);
10.            }
11.          """
12.        }
13.      }
14.    },
15.    "query": {
16.      "bool": {
17.        "filter": [
18.          { "range": { "@timestamp": {
19.              "gte": "2022-03-20T08:18:27.000Z",
20.              "lte": "2022-03-21T08:18:27.000Z" } } },
21.          { "wildcard": { "message_rt": { "value": "*4432-8015-6152-0454*" } } }
22.        ]
23.      }
24.    },
25.    "fields": ["@timestamp", "message_rt"],
26.    "_source": false
27.  }

`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

ES|QL 查询则简洁得多:

sql 复制代码
`

1.  FROM logs-aiops-22
2.  | WHERE @timestamp >= "2022-03-20T08:18:27.000Z"
3.    AND @timestamp <= "2022-03-20T08:28:27.000Z"
4.    AND CONTAINS(message, "4432-8015-6152-0454")
5.  | LIMIT 10000

`AI写代码

测试结果

7.17:行存快,列存慢

数据来源 10 分钟 1 小时 1 天 每秒速率
DSL + _source(行存) 477ms 2703ms 16778ms ~41 万条
DSL + doc value 1143ms 7024ms 超时 ~16 万条

7.17 时代 doc value 的列存实现对大文本扫描很不友好,1 天窗口直接超时。

8.19:logsdb 登场,但 doc value 依然不快

数据来源 10 分钟 1 小时 1 天 每秒速率
DSL + _source(行存) 312ms 1878ms 11824ms ~48 万条
DSL + synthetic source 582ms 3488ms 22077ms ~32 万条
DSL + doc value 839ms 5249ms 超时 ~21 万条

行存直读仍然最快;synthetic source 因为多了一步"从列存合成行"的开销,比直读 _source 慢约 35%。

9.5:logsdb_columnar 模式,doc value 彻底翻身

查询方式 10 分钟 1 小时 1 天 每秒速率
DSL + synthetic source 488ms 2849ms 16712ms ~42 万条
DSL + doc value 152ms 889ms 5121ms ~136 万条
ESQL + doc value 60ms 322ms 746ms ~343 万条

数据背后的三个结论

1. 为什么直读 _source 一直比 synthetic source 快? 行存把整条日志连续存放,顺序读取零重组;synthetic source 每读一条都要从多个列存字段里拼装还原,天然多一层开销。

2. 为什么 9.5 的 doc value 突然快了 6 倍? logsdb columnar 模式重写了列式存储布局:message 只存一份、连续紧凑排列,扫描一列就是顺序读一段磁盘,CPU 预取和缓存命中率大幅提升。列存终于发挥出了列存该有的样子。

3. 为什么 ES|QL 又比最快的 DSL 快 2.5 倍?

ES|QL 不是 DSL 的语法糖,而是一台天生为列式数据设计的现代分析引擎

  • 列式向量化执行:数据按列成块(block)流经算子,而不是一条文档一条文档地处理,可以充分利用 SIMD 指令,一条 CPU 指令同时处理多个数据;
  • 全核心并行:DSL 单查询基本困在一个核心里,ES|QL 单个查询就能跑满整机所有 CPU 核心,硬件利用率被最大化;
  • 存储与引擎同构:columnar 存储 + 列式引擎,数据从磁盘到 CPU 全程不需要行列转换。

应用建议

  • 成本优先的日志场景:直接上 9.5 logsdb columnar 纯列式模式,并且不建倒排索引,存储省到极致;
  • 实时扫描优先使用 ES|QL,DSL runtime field 只作兼容手段;
  • 永远做好时间和标签过滤,控制扫描范围,别让算力浪费在无关数据上;
  • 适用边界要清楚:这套打法适合"存得多、查得少"的成本敏感型日志。对安全分析、业务搜索这类需要毫秒级高并发响应的场景,老老实实建索引------用存储换计算,依然是最划算的买卖。
相关推荐
程序员z75 小时前
Elasticsearch 倒排索引详解
大数据·elasticsearch·搜索引擎
ryan_9966 小时前
生产环境向量检索数据库选型:Elasticsearch、Qdrant、Milvus 与 Chroma
数据库·elasticsearch·milvus·向量数据库·chroma·qdrant
智搜广告6 小时前
AI回答优化公司智搜广告让品牌成为推荐首选
大数据·人工智能·python·elasticsearch·geo
Elasticsearch7 小时前
OpenTelemetry Java 扩展:无需分叉 agent 即可自定义追踪
elasticsearch
Elasticsearch8 小时前
搜索倍增器:推动收入、生产力和 AI 实现规模化
elasticsearch
Elastic 中国社区官方博客18 小时前
让大模型思考,让小模型执行:在 Elastic Workflows 中拆分 LLM 成本
大数据·运维·数据库·人工智能·elasticsearch·ai
IZero0720 小时前
Elasticsearch 学习笔记筑基-文档操作篇
笔记·学习·elasticsearch
醉颜凉20 小时前
Elasticsearch 核心基石:倒排索引全解析(原理+结构+流程图+实战)
elasticsearch·jenkins·流程图
醉颜凉20 小时前
Elasticsearch底层原理:文档更新与删除完整执行流程深度剖析
大数据·elasticsearch·jenkins