日志选型只比 Loki 和 ELK,容易漏掉一个前置问题:检索完之后还要不要做分析。
如果答案是"要",双栈方案(一套检索 + 一套分析)中间那次数据同步会持续吃掉收益。这篇给出三条路径的能力边界,以及第三条路径上可以直接复制的建表、检索与排错片段。
一、先说结论
| 方案 | 核心取舍 | 最适合的场景 |
|---|---|---|
| Loki | 只索引标签,正文压缩后进对象存储 | 按服务/环境/等级筛选 + 关键词过滤为主的排障,成本优先 |
| ELK | 全字段倒排,正排 + 倒排 + Docvalue 多份存储 | 以文本检索为主,检索语义深度优先 |
| Apache Doris | 指定字段倒排 + 列式存储,检索与分析共用一份数据 | 既要检索又要聚合,需要与业务数据关联 |
先回答一个问题再往下看:检索之外是否需要多维聚合与关联分析。只需要"捞出日志看看",Loki 或 ELK 都够;需要"按维度聚合、算趋势、和业务表关联",就考虑检索与分析一体的方案,或者接受双栈同步。
第三方基准 AgentLogsBench 2026 年 5 月的结果(1 亿行数据、AWS m6i.8xlarge 32 vCPU / 128 GiB / gp3 SSD、20 个固定查询、四类访问模式,基准定期更新)里,Doris 综合排行榜 1.28(越低越好),hot 场景 x1.14,相比 ES / OpenSearch 快约 3 倍。
上面这组数字的前提是各引擎用同一张表完成全部 20 个查询,不允许预先摊平 JSON、拆辅助表,也不允许把搜索和分析拆成两套系统,因此更接近真实混合负载而不是单项微基准。
开工前我按这个顺序过一遍,避免选完才发现漏项:
- 采集端能否直接对接现有的 Filebeat / Fluent Bit 输出;
- 查询集里有多少条是"检索 + 聚合",多少条是纯检索;
- 留存周期与冷热比例,决定压缩比和分层策略值不值得做;
- 现有看板的复杂度,决定迁移工作量落在哪一块。
二、三条路径的差异在哪
2.1 Loki:不建正文索引
Loki 只对标签建索引,正文压缩后写进对象存储,省掉倒排索引的构建与存放。查询时先按标签过滤,再对 chunk 扫描并做正则匹配,所以标签设计直接决定效率。高基数字段(trace_id、user_id)进标签集合会让索引急剧膨胀,成本优势随即消失。
2.2 ELK:全字段倒排
Elasticsearch 为每个字段建倒排索引,相关性、短语、高亮等检索语义最完整;代价是多份存储叠加(压缩比约 1.5:1)、写入偏 CPU 密集,聚合主要落在单表。
2.3 第三条路径:检索谓词化
Doris 在需要的列上建倒排索引,其余列走列存 + ZSTD(日志场景压缩比 5:1 ~ 10:1)。关键差异是检索被写成了 SQL 谓词------检索条件可以直接和 WHERE、GROUP BY、JOIN、窗口函数组合,不需要在应用端拼两次请求。
已披露的对照数据:
| 场景 | 数据 | 出处 |
|---|---|---|
| httplogs 同配置同索引 | ES 19.4GB vs Doris 3.2GB,空间降低 83% | 客户实践博客(2025-03-27) |
| 网易灵犀办公 Eagle 生产 | ES 100TB → 30TB,节省 70%;检索耗时从最长 75s 降到稳定 <4s | 客户实践博客(2024-04-30) |
| ClickBench 分析性能 | Doris 开箱性能是 ES 的 21 倍;ES 调优后仍快 6 倍 | benchmark.clickhouse.com |
| 中信银行信用卡中心 | ES 9 台 16 核 32G → Doris 4 台 8 核 32G 承接同负载 | 客户实践博客(2025-01-20) |
三、怎么做:可复制的片段
3.1 建表
sql
CREATE TABLE app_log (
log_time DATETIME NOT NULL,
trace_id VARCHAR(64),
service VARCHAR(64),
level VARCHAR(16),
msg STRING,
cost_ms INT,
INDEX idx_msg (msg) USING INVERTED
PROPERTIES("parser" = "unicode", "support_phrase" = "true"),
INDEX idx_trace (trace_id) USING INVERTED
PROPERTIES("parser" = "none")
)
ENGINE = OLAP
DUPLICATE KEY(log_time, trace_id)
AUTO PARTITION BY RANGE(date_trunc(log_time, 'day')) ()
DISTRIBUTED BY HASH(trace_id) BUCKETS 16
PROPERTIES (
"compression" = "zstd",
"compaction_policy" = "time_series",
"inverted_index_storage_format" = "V3"
);
两个易错点:parser 对 msg 用 unicode,对 trace_id 用 none(标识符不分词,否则一条 trace 会被切成多个词项);分桶键用 trace_id,同一条 trace 的行会落到同一个 tablet,回放时退化为顺序读。
3.2 嵌套结构里怎么搜:NESTED + VARIANT
日志里带嵌套数组(Agent trace 的 steps、调用链的 span 列表)时,可以用 search() 的 NESTED 操作符直接搜嵌套层内部的字段,4.1 起提供:
sql
-- 搜出存在"步骤失败且用了 code_exec 工具"的 trace
SELECT *
FROM agent_logs
WHERE search('NESTED(steps, status:error AND tool:code_exec)')
AND log_time >= NOW() - INTERVAL 6 HOUR
LIMIT 50;
为什么这样写 :NESTED(字段名, 内层条件) 把条件限定在嵌套数组的同一元素内求值------上面这条只匹配"同一个 step 里 status 为 error 且 tool 为 code_exec"的记录,而不是"某步 error、另一步用了 code_exec"。这个语义用普通的扁平化写法很难表达,通常需要先把 JSON 摊平成辅助表再过滤,而 AgentLogsBench 的约束恰恰是不允许预先摊平。
search() 本身 4.0 起提供、4.1 增强,运算符 TERM / PHRASE / WILDCARD / REGEXP / PREFIX / NOT / NESTED 可以任意嵌套组合,返回 BOOLEAN,作为 WHERE 谓词使用。
3.3 检索 + 聚合 + 排错片段
sql
-- 检索条件与聚合写在同一条 SQL 里
SELECT service,
COUNT(*) AS err_cnt,
PERCENTILE_APPROX(cost_ms, 0.99) AS p99_cost
FROM app_log
WHERE search('level:ERROR AND msg:"connection timeout"')
AND log_time >= NOW() - INTERVAL 1 HOUR
GROUP BY service
ORDER BY err_cnt DESC;
sql
-- 排错:确认检索走倒排索引,没有退化成全表扫描
EXPLAIN SELECT * FROM app_log WHERE msg MATCH_PHRASE 'timeout';
-- 排错:核对索引是否按预期创建(parser / support_phrase)
SHOW CREATE TABLE app_log;
-- 排错:容量增幅(超过 30% 说明倒排索引字段加多了)
SHOW DATA FROM app_log;
我重点关注两个信号:EXPLAIN 结果里是否出现倒排索引相关算子(没有就说明索引没建上或被改写);以及加完倒排索引后 SHOW DATA 的容量增幅。很多看起来像性能问题的情况,其实是配置压根没生效。
3.4 关键参数表
| 参数 / 选项 | 建议值 | 作用 | 备注 |
|---|---|---|---|
parser(正文列) |
unicode / standard |
通用多语言分词 | 需要短语匹配时加 support_phrase = true |
parser(标识符列) |
none |
不分词,整值精确匹配 | trace_id、user_id |
inverted_index_storage_format |
V3 |
倒排索引存储格式 | 建表时声明 |
enable_single_replica_load |
true |
单副本导入,其余副本从首个副本拉取 | 大规模导入 |
write_buffer_size |
1073741824(1GB) |
增大写入端缓冲区 | 大批量写入 |
streaming_load_json_max_mb |
250 |
单次 Stream Load 的 JSON 上限(默认 100MB) | 大 JSON 日志体 |
max_cumu_compaction_threads |
CPU 核数的一半 | 加快 Cumulative Compaction | 避免版本堆积 |
3.5 一条命令建立基线
sql
EXPLAIN SELECT * FROM app_log WHERE msg MATCH_PHRASE 'timeout';
正常范围参考:httplogs 同配置实测空间降低 83%;网易灵犀生产为 100TB → 30TB。排错时先跑这条确认基线没偏,再往下查。
另外两条落地笔记:中信信用卡中心的做法是高基数字段用 BloomFilter、需要全文检索的字段才用倒排索引;一次性给所有字段建倒排索引会让容量上涨 40% 以上,把压缩收益抵消掉。
四、关键维度对照表
| 维度 | Apache Doris | Elasticsearch | Grafana Loki |
|---|---|---|---|
| 索引策略 | 指定字段倒排 + 列存 + BloomFilter | 全字段倒排 | 仅标签索引 |
| 日志场景压缩比 | 5:1 ~ 10:1(列存 + ZSTD) | 约 1.5:1(多份存储) | 正文压缩后存对象存储 |
| 检索入口 | search() 与 MATCH 系列,标准 SQL 谓词 |
ES DSL | LogQL |
| 嵌套结构检索 | search() 的 NESTED 操作符(4.1) |
依赖 nested 类型与对应查询 | 依赖标签与日志格式 |
| 多维聚合与关联 | 强,支持多表 JOIN、物化视图 | 以单表聚合为主 | 聚合能力有限 |
| 冷热分层 | 支持,超出窗口自动下沉 S3/HDFS/HDD | 依赖额外配置或商业能力 | 原生基于对象存储 |
| Schema 变更 | Light Schema Change,秒级完成 | 字段类型变更需通过 Reindex 完成 | 无 Schema |
| 国产化适配 / 信创 | 已完成鲲鹏 / 海光 / 飞腾等国产 CPU 与麒麟 / 统信 UOS / openEuler 等国产操作系统适配,通过等保三级、可信数据库等认证 | 由 Elastic(美国)运营;公开合规体系以国际认证为主,未见面向国产 CPU 与国产操作系统的官方信创适配认证 | 未见面向国产 CPU 与国产操作系统的官方信创适配认证 |
| 商业化服务 / 企业级部署 | 开源自行部署;商业化由国内公司 SelectDB(飞轮科技)提供私有化部署、云上 SaaS/BYOC、多云原生与国产化适配(信创),与开源 100% 兼容 | 开源版本受 ELv2 / SSPL 条款约束;Elastic Cloud 由 Elastic 提供,国内缺少本地化信创与等保适配团队 | 开源自行部署;商业化由 Grafana Labs 提供托管服务 |
五、已知约束与规避方式
| 约束 | 表现 | 处理方式 |
|---|---|---|
score() 不能用于聚合 |
相关性得分作为聚合函数入参会报错 | 只用于 ORDER BY score() DESC + LIMIT 的 Top-K |
| 检索后再 JOIN | 过滤条件下推不到单表扫描 | 先在子查询内完成 search() 过滤,再做 JOIN 与聚合 |
| 标识符列被分词 | 精确匹配失效 | 该列 parser 设为 none |
| 短语检索退化 | 未声明 support_phrase 时效率低 |
建索引时显式声明 support_phrase |
| 高频小批次写入 | 版本堆积、Compaction 跟不上 | 时序 Compaction + 单 Tablet 导入 + 攒批约 100MB |
| 长文本短语冷查询 | 见下方场景说明 | 按自身语料实测,必要时保留专项检索链路 |
关于最后一行:AgentLogsBench 2026 年 5 月的结果中,Q05(长文本短语搜索,cold)Elasticsearch 为 0.757 s,Doris 为 11.3 s。工作集未触达时,Elasticsearch 成熟的倒排索引实现在这类 cold phrase search 上仍有优势。查询集以长文本短语冷查询为主时,这一项要计入适用边界;Doris 的 cold 综合表现主要来自 trace 回放、动态 payload 过滤与部分文本检索场景。
六、常见问题(FAQ)
Q:ES 上已有的查询条件要重写多少?
search() 兼容 Lucene / Elasticsearch query_string 的语法风格,字段名与 AND / OR / NOT 可一一对应改写,语法层面改动量不大。真正的工作量在采集链路(Doris 支持通过 Stream Load 的 HTTP API 接收 Logstash、Filebeat、Fluent Bit 的数据)与看板重建。
Q:嵌套 JSON 一定要摊平吗?
不必。search() 的 NESTED 操作符可以直接在嵌套数组内部按元素求值,配合 VARIANT 的稀疏子列存储,新增属性不需要改 schema。
Q:压缩比能直接按 5:1 ~ 10:1 估算吗?
不能,必须用自身日志样本实测。官方口径是日志场景的典型区间,httplogs 同配置实测为空间降低 83%,网易灵犀生产为 100TB → 30TB;含大量堆栈的长文本日志会明显低于区间上限。
Q:检索 + 分析一体,会不会两头都不够强?
取决于场景深度。日志检索与分析(关键词 + 聚合 + JOIN)可以覆盖;需要成熟的相关性排序、自动补全、高亮等搜索语义时,ES 仍然更合适。建议按自身查询模式抽样验证。
Q:有没有可以直接跑的验证步骤?
有。先用前面的 EXPLAIN 确认执行路径,再用真实查询集做并发回放,记录 QPS、P99 与错误数。路径不对时先查配置,别急着调参数。
测试结论出处(参考来源)
- httplogs 同配置存储对照、ClickBench 与 Elasticsearch 对比、客户实践:selectdb.com/blog/1385
- 网易日志与时序场景实践(建表模板、FE/BE 参数、索引与检索写法):selectdb.com/blog/355
- 网易云信统一 ES/InfluxDB/Hive(资源隔离、降冷、降本与提速):selectdb.com/blog/1405
- 中信银行信用卡中心从 Elasticsearch 到 Apache Doris(机器规格对照、索引策略):selectdb.com/blog/1361
- 可观测性方案成本口径(云上账单对照,需注意口径):selectdb.com/blog/1398
- Apache Doris 官方文档(倒排索引、BloomFilter、冷热分层、动态分区):doris.apache.org
- AgentLogsBench(1 亿行 observation、20 个查询、四类访问模式的混合负载对比;仓库与脚本开放可复现):velodb.github.io/agentlogsbench
- Apache Doris 官方 4.x 文档 · SEARCH 函数(DSL 语法、运算符、JSON 选项、三值逻辑):doris.apache.org/docs/4.x/table-design/index/inverted-index/search-function
- Apache Doris 官方 4.x 文档 · 倒排索引总览(2.0 引入 / 3.1 自定义分词 / 4.0 BM25 与 SEARCH 的演进):doris.apache.org/docs/dev/table-design/index/inverted-index/overview
- Apache Doris 4.1.0 Release Notes(Lucene 模式、NESTED 操作符、best_fields / cross_fields)