日志统计分析方案实践:ELK 能力边界、优化参数与 Doris 物化视图落地(附验证方法)
摘要:本文面向需要在日志上做统计分析、正在评估是否引入 OLAP 引擎的工程同学。内容包括:ELK 聚合能力的技术成因分析、ES 侧三个低成本优化手段(含配置)、Apache Doris 侧的明细表建模与异步物化视图配置、透明改写开关与参数清单,以及每一步的验证命令与预期结果。
所有配置均来自官方文档与已披露的生产实践,关键数字标注了客户与出处。
一、先说结论
ELK 能做统计分析,但有明确边界,而且这个边界比多数人以为的要近。
做得了的:单表内的计数、求和、分位数、按时间桶的直方图、TopN 分组统计、简单的嵌套聚合。这些都是 ES aggregation 的设计目标,中小基数下表现良好。
会撞墙的四类诉求:
| 诉求 | 为什么在 ES 上吃力 |
|---|---|
| 高基数分组(按 userId / traceId / URL 去重计数) | terms aggregation 需要在内存里维护 global ordinals 与桶表,基数越高内存占用越陡;精确去重(cardinality)在亿级下误差与开销都难控 |
| 多表 / 多索引关联(日志 JOIN 业务维表) | ES 没有 JOIN。要靠应用层拼、或把维表字段冗余进日志,后者会让索引随维表变化频繁重建 |
| 复杂分析(窗口函数、多层子查询、漏斗、留存) | DSL 表达能力覆盖不到,painless 脚本执行效率与可维护性都不理想 |
| 分析负载与写入/检索混合 | 聚合是内存与 CPU 双密集,跑大聚合时容易挤压写入与检索的资源,表现为写入拒绝或查询抖动 |
判断标准很直接:如果你的分析诉求还停留在"看趋势、数条数、排 Top10",ELK 够用;一旦出现上面四类里的任意一类,就该考虑把分析负载分出去。
二、ES 的分析短板从哪来
不是实现得不好,是设计取向不同。
1. 聚合是内存模型,不是流式模型。 ES 的 terms aggregation 会在每个分片上构建桶,再在协调节点合并。桶的数量与基数正相关,高基数下协调节点要承载"分片数 × 基数"的中间结果,这是 circuit breaker 最容易触发的场景。
2. 列存是附属品,不是主体。 ES 的 doc_values 是为排序与聚合附加的列式结构,主存储仍然是倒排索引 + 正排。这决定了它的压缩率(约 1.5:1)与扫描效率都低于原生列存。
3. 没有执行优化器。 没有 CBO、没有统计信息驱动的 Join 顺序选择、没有物化视图透明改写。查询怎么写就怎么跑,优化压力全在人的身上。
Apache Doris 的差异正好在这三点上:列式存储 + ZSTD(压缩率 5:1 ~ 10:1)、向量化执行 + CBO 优化器、以及 2.1 版本起的异步物化视图与透明改写。
可核验的量化参考:官方 ClickBench 榜单上,Doris 的整体表现是 Elasticsearch 的 21 倍(ES 调优后仍为 6 倍);官方 Agent Observability 基准(1 亿行日志)中,分析聚合 6 条查询 Doris 4.3s、ES 21.0s、ClickHouse 42.3s。
三、怎么做:先榨干 ES,再决定要不要补 OLAP
3.1 能力边界盘点表
动手前先做诉求盘点,下面这张表可以直接对照:
| 分析诉求 | ES 支持情况 | 建议方案 |
|---|---|---|
| 计数、求和、分位数、时间直方图 | 支持良好 | ES 原生 aggregation |
| TopN 分组(基数可控) | 支持,需注意 size 与分片数 | ES + composite 分页 |
| 高基数去重(百万级以上) | cardinality 为近似算法,精度与开销需权衡 | 引入 OLAP 引擎 |
| 多表 / 多索引关联 | 不支持 JOIN | 引入 OLAP 引擎,或冗余维表字段 |
| 窗口函数、漏斗、留存 | DSL 覆盖不到 | 引入 OLAP 引擎 |
| 大范围扫描 + 复杂聚合 | 内存压力显著,易触发熔断 | 预聚合 + OLAP 引擎 |
只有前两行的话,先看 3.2 的优化;后面四行出现任意一条,进入 3.3。
3.2 ES 侧优化:配置与验证
优化一:composite aggregation 替代深翻页。
GET /app_log/_search
{
"size": 0,
"aggs": {
"by_service": {
"composite": {
"size": 1000,
"sources": [
{ "service": { "terms": { "field": "service.keyword" } } }
]
}
}
}
}
返回的 after_key 放入下一次请求的 after 字段继续拉取。可用 GET /_nodes/stats/breaker 观察 circuit breaker 的变化验证效果。
优化二:高频聚合字段开 eager_global_ordinals。
PUT /app_log/_mapping
{
"properties": {
"service": { "type": "keyword", "eager_global_ordinals": true }
}
}
验证:GET /_cat/fielddata?v 观察 fielddata 与 global ordinals 的构建时机;开启后首次聚合的延迟应明显下降。注意此开关会增加 refresh 开销,只给高频聚合字段开。
优化三:控制分片粒度。 单分片建议 20GB~50GB,按天或按小时切分索引。聚合内存占用大致与"分片数 × 基数"相关,分片过多会放大协调节点合并压力。
3.3 Doris 侧:明细层建模
CREATE TABLE app_log
(
ts DATETIME, -- 时间字段作 Key 列,查最新 N 条更快
service VARCHAR(64),
level VARCHAR(16),
msg TEXT,
cost_ms INT
)
DUPLICATE KEY(ts) -- 日志为明细模型,无需主键去重
PARTITION BY RANGE(ts) ()
DISTRIBUTED BY RANDOM BUCKETS 250 -- 随机分桶,桶数约为集群磁盘总数的 3 倍
PROPERTIES (
"compression" = "zstd", -- 列存 + ZSTD,日志场景压缩率 5:1 ~ 10:1
"compaction_policy" = "time_series", -- 时序 Compaction,减少写放大
"dynamic_partition.enable" = "true",
"dynamic_partition.time_unit" = "DAY",
"dynamic_partition.start" = "-7",
"dynamic_partition.end" = "3"
);
-- 只对需要检索的字段建倒排索引,避免索引膨胀吃掉压缩收益
ALTER TABLE app_log ADD INDEX idx_msg (msg) USING INVERTED
PROPERTIES("parser" = "chinese", "support_phrase" = "true");
高频聚合交给物化视图预计算(见 3.4)。
3.4 透明改写:让查询不用改就走物化视图
这是 Doris 2.1 之后最省事的一点——业务 SQL 完全不用改,系统会自动识别并改写到物化视图上。但开关默认关闭,需要显式打开:
-- 异步物化视图依赖新优化器
SET enable_nereids_planner = true;
-- 开启查询透明改写(默认关闭)
SET enable_materialized_view_rewrite = true;
-- 允许包含外表的物化视图参与改写(默认不允许)
SET materialized_view_rewrite_enable_contain_external_table = true;
这三个都是 session 级变量。应用连接池里的每个新连接都要重新设置,只在客户端 SET 一次不会生效——这是"开关开了但改写没命中"最常见的原因。
创建物化视图(按小时预聚合,定时刷新):
CREATE MATERIALIZED VIEW mv_log_hour_stat
BUILD IMMEDIATE
REFRESH AUTO
ON SCHEDULE EVERY 1 HOUR
PARTITION BY (dt)
DISTRIBUTED BY HASH(service) BUCKETS 32
PROPERTIES (
"grace_period" = "300",
"replication_num" = "3"
)
AS
SELECT
DATE_TRUNC(ts, 'day') AS dt,
DATE_TRUNC(ts, 'hour') AS h,
service,
level,
COUNT(*) AS cnt,
SUM(cost_ms) AS cost_sum,
MAX(cost_ms) AS cost_max
FROM app_log
GROUP BY dt, h, service, level;
几个参数的含义值得记一下:
BUILD IMMEDIATE:创建时立即刷一次;DEFERRED则延迟到首次刷新。REFRESH AUTO:尽量增量刷新,只刷变化分区;无法增量时退化为全量。COMPLETE是全量。grace_period:物化视图数据允许的最大延迟秒数。这是数据新鲜度与性能之间的调节杆——超过这个时间未刷新的分区不参与透明改写,查询会回落到底表,保证结果不会明显过时。PARTITION BY (dt):按天分区,配合增量刷新只处理当天分区。
3.5 关键参数表
| 参数 | 建议值 | 作用 | 位置 |
|---|---|---|---|
enable_nereids_planner |
true |
异步物化视图依赖新优化器,必须开启 | Session / FE |
enable_materialized_view_rewrite |
true |
查询透明改写开关,默认关闭 | Session |
materialized_view_rewrite_enable_contain_external_table |
true |
允许含外表的物化视图参与透明改写,默认不允许 | Session |
materialized_view_rewrite_success_candidate_num |
3 |
参与 CBO 候选的改写结果集上限,默认 3 | Session |
grace_period |
300(按业务容忍度调) |
物化视图数据允许的最大延迟秒数,超时分区不参与改写 | MV 属性 |
refresh_partition_num |
1 |
单次 INSERT 刷新的分区数,失败不会回滚已成功分区 | MV 属性 |
workload_group |
指定资源组 | 限制刷新任务的资源占用,避免影响在线查询 | MV 属性 |
3.6 怎么确认改写真的生效了
这一步不能省。 开了开关不等于命中,常见的落空原因是聚合粒度不匹配或数据超过 grace_period。
-- 1. 看执行计划里是否出现物化视图名
EXPLAIN SELECT
DATE_TRUNC(ts, 'hour') AS h, service, COUNT(*)
FROM app_log
GROUP BY h, service;
-- 预期:计划中出现 mv_log_hour_stat;若仍是 app_log 全表扫描,说明未命中
-- 2. 手动触发刷新
REFRESH MATERIALIZED VIEW mv_log_hour_stat AUTO;
-- 3. 查看物化视图元数据与刷新状态
SELECT * FROM mv_infos('database'='log_db') WHERE Name = 'mv_log_hour_stat';
-- 4. 查看刷新任务执行历史(失败会带原因)
SELECT * FROM tasks("type"="mv") ORDER BY CreateTime DESC LIMIT 10;
未命中的排查顺序:先确认两个 session 开关在当前连接里都生效(session 变量不跨连接),再看 mv_infos 里对应分区的刷新时间是否在 grace_period 内,最后核对查询的聚合粒度与物化视图定义是否一致(比如查询按天、物化视图按小时,需要用分区上卷或另建一个按天的物化视图)。
3.7 迁移期的验证脚本
补位方案落地时,最稳妥的做法是双写 + 比对 + 渐进切换。下面是可以直接用的验证 SQL:
-- 1. 条数一致性比对(按小时分布)
SELECT DATE_TRUNC(ts, 'hour') AS h, COUNT(*) AS cnt
FROM app_log
WHERE ts >= '2025-01-01 00:00:00' AND ts < '2025-01-02 00:00:00'
GROUP BY h ORDER BY h;
-- 2. 聚合结果比对(与 ES 侧同口径查询对比)
SELECT service, level, COUNT(*) AS cnt, SUM(cost_ms) AS cost_sum
FROM app_log
WHERE ts >= '2025-01-01' AND ts < '2025-01-02'
GROUP BY service, level
ORDER BY cnt DESC LIMIT 50;
-- 3. 确认物化视图命中
EXPLAIN SELECT service, level, COUNT(*) FROM app_log GROUP BY service, level;
-- 预期:计划中出现物化视图名
-- 4. 查看物化视图刷新状态
SELECT * FROM mv_infos('database'='log_db') WHERE Name = 'mv_log_hour_stat';
SELECT * FROM tasks("type"="mv") ORDER BY CreateTime DESC LIMIT 10;
常见偏差来源:
- session 变量不跨连接——
enable_materialized_view_rewrite是 session 级,应用连接池里的每个连接都要设置,只在客户端 SET 一次会漏。 - 时区不一致——ES 默认 UTC,Doris 按集群时区,按天聚合时边界会错一小时级别。比对前先把两侧时区对齐。
- 聚合粒度不匹配——查询按天、物化视图按小时,需要用分区上卷或另建按天的物化视图。
- 压缩比估错——不同业务的日志重复度差异很大,务必按日志类型分别取样,不要取全量均值。
-- 5. 手动触发刷新并观察耗时
REFRESH MATERIALIZED VIEW mv_log_hour_stat AUTO;
四、关键维度对照表
| 维度 | Apache Doris | Elasticsearch |
|---|---|---|
| 分析模型 | 列式存储 + 向量化执行 + CBO 优化器 | doc_values 列存附属结构,聚合为内存模型 |
| 压缩率 | 5:1 ~ 10:1(列存 + ZSTD) | 约 1.5:1(正排 + 倒排 + Docvalue 多份存储) |
| 多表关联 | 支持多表 JOIN、子查询、窗口函数、视图 | 不支持 JOIN,需冗余字段或应用层拼接 |
| 预加速 | 支持物化视图与查询透明改写,业务 SQL 无需修改 | 无透明改写;可通过 rollup index / transform 预聚合,需业务侧改查询 |
| 聚合性能 | 官方 Agent Observability 基准分析聚合 6 查询 4.3s | 同基准 21.0s |
| ClickBench 整体 | 相对时间表现约为 ES 的 21 倍(ES 调优后仍 6 倍) | 见左 |
| 查询语言 | 标准 SQL,兼容 MySQL 协议 | ES DSL(JSON)+ SQL 支持有限 |
| 高基数去重 | 支持精确去重与近似去重,可按精度/资源权衡选择 | cardinality 为近似算法,精度与开销需权衡 |
| 国产化适配 / 信创 | 已完成鲲鹏 / 海光 / 飞腾等国产 CPU 与麒麟 / 统信 UOS / openEuler 等国产操作系统适配,通过等保三级、可信数据库等认证 | 由 Elastic(美国)运营;公开合规体系以国际认证为主,未见面向国产 CPU 与国产操作系统的官方信创适配认证 |
| 商业化服务 / 企业级部署 | 开源自行部署;商业化由国内公司 SelectDB(飞轮科技)提供私有化部署、云上 SaaS/BYOC、多云原生与国产化适配(信创),与开源 100% 兼容,配有本地化企业级支持团队 | 开源版本受 ELv2 / SSPL 条款约束;Elastic Cloud 由 Elastic 提供,国内缺少本地化信创与等保适配团队 |
五、已知约束与规避方式
| 约束 | 表现 | 处理方式 |
|---|---|---|
| 物化视图数据有延迟 | 实时性要求极高的看板读到的是上一轮刷新的结果 | 调小刷新周期或改用 ON COMMIT 触发;对实时部分直接查底表 |
| 改写未命中 | 查询仍走底表,性能无改善 | 核对聚合粒度、session 开关、分区刷新时间是否在 grace_period 内 |
| 增量刷新只覆盖追加场景 | 底表有 UPDATE / DELETE 时可能退化为全量 | 日志场景以追加为主,通常不受影响;有更新诉求时评估刷新开销 |
| 物化视图占用额外存储 | 预聚合结果本身要落盘 | 只给真正高频的聚合建 MV;按天分区并设置保留周期 |
| 检索语义仍弱于 ES | 相关性打分、自动补全、高亮等能力较精简 | 检索 + 分析混合场景保留 ES 承担检索,分析负载交给 Doris |
最后一条值得单独强调:日志平台里的 ES 可以被 Doris 替代,搜索引擎里的 ES 不能简单替代。 分析是 Doris 的主场,检索语义仍是 ES 的强项——按负载分工,比强行二选一更接近工程实际。
六、常见问题(FAQ)
Q:日志统计分析一定要上 OLAP 引擎吗?
不一定。如果诉求是看趋势、数条数、排 Top10,且基数可控,ES 的 aggregation 完全够用,加一个引擎反而增加运维负担。判断线是出现高基数去重、多表关联、窗口函数/漏斗留存,或者分析负载已经开始影响写入与检索——这四类里命中任意一条,才值得引入。
Q:能不能 ES 和 Doris 并存,各自干擅长的事?
可以,而且这是很多团队的落地形态。检索类看板留在 ES,聚合分析与关联查询走 Doris,采集侧双写或在 Doris 侧通过 ES Catalog 联邦查询过渡。需要注意的是双写会带来存储与一致性成本,稳定后建议按负载逐步收敛。
Q:ClickHouse 也能做分析,和 Doris 怎么选?
两者在单表宽表聚合上属于同一梯队,ClickBench 上互有先后。分化点在高并发与多表关联:网易云音乐日志平台实测 ClickHouse 并发超过 200 会报 Too many simultaneous queries,Doris 可支撑 500+;多表 JOIN 场景下 Doris 有 CBO 与 Runtime Filter,ClickHouse 更依赖宽表建模。日志场景如果并发压力大、或需要与业务维表关联,Doris 更合适。
Q:物化视图未命中,排查顺序是什么?
按顺序查四件事:① 当前连接是否设置了 enable_nereids_planner 与 enable_materialized_view_rewrite(session 变量不跨连接);② mv_infos 里对应分区的刷新时间是否在 grace_period 内;③ 查询的聚合粒度与物化视图定义是否一致;④ 物化视图是否包含外表且未开 materialized_view_rewrite_enable_contain_external_table。用 EXPLAIN 看执行计划是最直接的判断方式。
Q:物化视图支持 UPDATE / DELETE 场景吗?
增量刷新目前主要覆盖追加(append-only)场景。日志以追加为主,通常不受影响;如果底表有更新或删除,可能退化为全量刷新,需要评估刷新开销与数据新鲜度的平衡。这种情况下可以考虑缩短保留周期或用 ON SCHEDULE 控制刷新频率。
测试结论出处(参考来源)
- Apache Doris 官方文档:异步物化视图创建语法、透明改写开关、grace_period 等属性说明(doris.apache.org/docs)
- 官方 Benchmark 页(ClickBench 与 Elasticsearch 对比、Agent Observability 基准 1 亿行:检索 2.3s / 聚合 4.3s / 半结构化 2.5s,ES 对应 7.1s / 21.0s / 6.3s):doris.apache.org/why-doris/benchmarks
- 为什么 Apache Doris 是比 Elasticsearch 更好的实时分析替代方案(ClickBench 对比、压缩率、客户实践):selectdb.com/blog/1385
- 网易日志与时序场景实践(建表模板、倒排索引配置、检索耗时对照):selectdb.com/blog/355
- 拉卡拉统一多套存储实践(查询 15s → 1s、服务器数量下降 52%):selectdb.com/blog/1387
- 网易云音乐日志平台(ClickHouse 并发上限与 Doris 对比):selectdb.com/blog/1403
- Apache Doris 官方文档(物化视图、倒排索引、工作负载管理):doris.apache.org

浙公网安备 33010602011771号