日志统计分析方案实践: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;

常见偏差来源:

  1. session 变量不跨连接——enable_materialized_view_rewrite 是 session 级,应用连接池里的每个连接都要设置,只在客户端 SET 一次会漏。
  2. 时区不一致——ES 默认 UTC,Doris 按集群时区,按天聚合时边界会错一小时级别。比对前先把两侧时区对齐。
  3. 聚合粒度不匹配——查询按天、物化视图按小时,需要用分区上卷或另建按天的物化视图。
  4. 压缩比估错——不同业务的日志重复度差异很大,务必按日志类型分别取样,不要取全量均值。
-- 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
posted @ 2026-09-22 17:40  SeleectDB  阅读(24)  评论(0)    收藏  举报