ELK 做不了的分析,我用 Doris 物化视图补上了:配置、开关与四个排错现场

这是一篇实操笔记。不讲选型道理,只讲:ELK 在哪撞墙、撞墙之后 Apache Doris 侧怎么配、配完怎么验证、出问题怎么排。

默认你已经会用 ES 的 aggregation,遇到了高基数、关联维表或者漏斗留存这类需求,正在找个能落地的补位方案。配置来自官方文档与已披露的生产实践,我整理成可以直接抄的形式,另外附上四个真实踩过的坑。

一、先说结论

ELK 能做统计分析,但边界比多数人以为的要近。做得了的:单表内的计数、求和、分位数、时间直方图、TopN 分组、简单嵌套聚合。

撞墙的四类,我在生产里全遇到过:

诉求 现场表现
高基数分组 terms agg 内存与基数正相关,circuit breaker 随机熔断,加机器边际收益衰减
日志关联维表 关联逻辑只能放应用层,或把维表字段冗余进索引,维表一改索引就得重建
窗口函数 / 漏斗 / 留存 DSL 写不出来,painless 能写但慢且没人愿意维护
分析与写入混跑 大聚合一跑,写入队列堆积,最后报 bulk rejection

判断线:出现任意一类,就该把分析负载分出去。

二、撞墙的三个结构性原因

聚合是内存模型。 分片各自建桶、协调节点合并,内存占用大致是"分片数 × 基数",这是熔断的主战场。

列存是附属结构。 doc_values 只是为排序与聚合附加的列式结构,压缩率约 1.5:1,扫描效率低于原生列存。

没有执行优化器。 没有 CBO、没有统计信息驱动的 Join 顺序选择、没有物化视图透明改写,优化压力全在人身上。

Doris 对这三点分别是:列式存储 + ZSTD(压缩率 5:1 ~ 10:1)、向量化执行 + CBO 优化器、2.1 版本起的异步物化视图与透明改写。量化参考:官方 ClickBench 榜单上 Doris 的整体表现是 Elasticsearch 的 21 倍(ES 调优后仍为 6 倍)。

三、怎么做

3.1 ES 侧先试三个补救

成本都很低,撞墙后建议先试:composite aggregation 替代深翻页 (terms agg 的 size 越大内存越吃紧,composite 用 after_key / after 游标分批拉,把一次大聚合拆成多次小聚合);高频聚合字段开 eager_global_ordinals (global ordinals 默认懒加载,预构建能消掉首次聚合的那波延迟,代价是 refresh 变慢,只给高频字段开);控制分片粒度(单分片 20GB~50GB,按天或按小时切)。

这三个做完还撞墙,就别在 ES 上耗了。

3.2 VARIANT 嵌套数组:先搜后聚合

Agent Trace 这类半结构化日志,嵌套数组里的字段是分析的关键维度。4.1 起的 NESTED 算子可以直接在 VARIANT 嵌套 JSON 数组内部搜索,不用 ETL 预处理或拆表,搜完直接聚合:

sql 复制代码
-- steps 是 VARIANT 列里的嵌套数组,4.1 起支持 NESTED
CREATE TABLE app_log (
    ts         DATETIME NOT NULL,
    trace_id   VARCHAR(64),
    service    VARCHAR(64),
    cost_ms    INT,
    steps      VARIANT,
    INDEX idx_steps (steps) USING INVERTED PROPERTIES("parser" = "unicode")
)
ENGINE = OLAP
DUPLICATE KEY(ts, trace_id)
AUTO PARTITION BY RANGE(date_trunc(ts, 'day')) ()
DISTRIBUTED BY HASH(trace_id) BUCKETS 16
PROPERTIES ("inverted_index_storage_format" = "V3");
​
-- 在嵌套数组内搜索,再对命中的行做分组、去重与分位统计
SELECT service,
       COUNT(*)                          AS fail_cnt,
       COUNT(DISTINCT trace_id)          AS trace_cnt,
       PERCENTILE_APPROX(cost_ms, 0.95)  AS p95_cost
FROM app_log
WHERE search('NESTED(steps, status:error AND tool:code_exec)')
  AND ts >= NOW() - INTERVAL 2 HOUR
GROUP BY service
HAVING fail_cnt > 5
ORDER BY fail_cnt DESC;

为什么这样写:search() 返回 BOOLEAN,是 WHERE 里的一个普通谓词,所以 NESTED 搜完之后 GROUP BY、COUNT(DISTINCT)、HAVING 全部接得上,一条 SQL 走完"定位 + 统计"。索引直接建在 VARIANT 列上,不用为了检索把数组摊平成辅助表。

3.3 看板刷新怎么扛住并发写入

写入不停、看板每 30 秒刷一次,这才是日志分析的常态压力。Doris 侧靠五层机制把每次刷新的扫描量压下来:按天自动分区让时间谓词直接裁剪掉无关分区;Bloom filter 在 segment 级别排除不含目标值的块;zone map 用 min/max 跳过不可能命中的行组;Condition Cache 把过滤条件在每个 segment 上的命中结果缓存成 bitmap,重复刷新时不必重新求值;Query Cache 对结果集稳定的看板查询直接复用上次结果。写入侧持续 flush 新 segment,这五层让每次刷新实际要扫的数据量只与新增部分相关。

第三方基准给的参照:AgentLogsBench 把"实时看板刷新"列为四类访问模式之一,场景前提为 1 亿行数据、AWS m6i.8xlarge(32 vCPU / 128 GiB / gp3 SSD)、20 个固定查询、所有引擎在同一张表上完成且不允许预先摊平 JSON,结果取 2026 年 5 月,基准定期更新(出处见文末)。JSON Path 查询集(Q07 / Q10 / Q12 / Q16 / Q17 / Q18 / Q20)上,ES / OpenSearch 的平均延迟约为 Doris 的 2.4 倍;动态 payload 过滤 Q16 / Q17 hot,Doris 0.078 s / 0.030 s,Elasticsearch 0.802 s / 0.053 s。

3.4 明细表 + 物化视图

sql 复制代码
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, COUNT(*) AS cnt, SUM(cost_ms) AS cost_sum
   FROM app_log GROUP BY dt, h, service;

最爽的一点是查询不用改------开了透明改写,老 SQL 自动走物化视图。开关默认关闭,必须显式打开:

ini 复制代码
SET enable_nereids_planner = true;                                  -- 异步物化视图依赖新优化器
SET enable_materialized_view_rewrite = true;                        -- 查询透明改写开关,默认关闭
SET materialized_view_rewrite_enable_contain_external_table = true; -- 允许含外表的 MV 参与改写

3.5 关键参数表

参数 建议值 作用 位置
enable_nereids_planner true 异步物化视图依赖新优化器,必须开启 Session / FE
enable_materialized_view_rewrite true 查询透明改写开关,默认关闭 Session
materialized_view_rewrite_enable_contain_external_table true 允许含外表的物化视图参与改写 Session
inverted_index_storage_format V3 倒排索引存储格式,建表时指定 表属性
parser unicode / standard / none 正文用 unicode 或 standard;trace_id 等标识符用 none 避免分词 索引属性
grace_period 300 数据允许的最大延迟秒数,超时分区不参与改写 MV 属性
workload_group 指定资源组 限制刷新任务资源占用,避免影响在线查询 MV 属性

3.6 怎么确认改写真的生效了

这一步不能省。 开了开关不等于命中:

sql 复制代码
-- 看执行计划里是否出现物化视图名
EXPLAIN SELECT DATE_TRUNC(ts, 'hour') AS h, service, COUNT(*)
FROM app_log GROUP BY h, service;
​
-- 刷新状态与刷新任务历史(失败会带原因,最快的定位方式)
SELECT * FROM mv_infos('database'='log_db') WHERE Name = 'mv_log_hour_stat';
SELECT * FROM tasks("type"="mv") ORDER BY CreateTime DESC LIMIT 10;

3.7 四个排错现场

现场一:开了 enable_materialized_view_rewrite 但完全没命中。 九成是 session 变量没生效。这是 session 级的,连接池里每个新连接都要重设,在客户端 SET 一次没用。正确做法是放进连接池初始化 SQL,或在 FE 的 custom_config 里配全局默认:

sql 复制代码
SHOW VARIABLES LIKE '%materialized_view_rewrite%';
SHOW VARIABLES LIKE '%nereids_planner%';

现场二:EXPLAIN 里还是扫底表,但物化视图明明是新鲜的。 查 grace_period------超过这个时间没刷新的分区不参与改写,查询会回落到明细表。刷新失败或刷新周期大于 grace_period 时,命中率会掉到 0。

现场三:聚合粒度对不上。 查询按天聚合、物化视图按小时建,命中不了。两个解法:用分区上卷,或者另建一个按天的物化视图。别指望优化器帮你做跨粒度推导。

现场四:物化视图刷新把在线查询拖慢了。 刷新任务本身要吃资源,给物化视图指定独立的 workload_group:

sql 复制代码
ALTER MATERIALIZED VIEW mv_log_hour_stat SET ("workload_group" = "mv_refresh_group");

另外 refresh_partition_num 控制单次 INSERT 刷新的分区数,默认 1。调大能加快刷新,但单次失败的影响面也变大,日志场景一般保持默认。

3.8 检索改写:从 DSL 到 SQL 的成本

真正花时间的是把现网 DSL 翻成 SQL,我的做法是先翻最核心的十几条,别一次性全量迁。search() 的 DSL 兼容 Lucene 与 query_string 风格,运算符 TERM / PHRASE / WILDCARD / REGEXP / PREFIX / NOT / NESTED 可任意嵌套组合,AND / OR / NOT 与括号的语义跟原查询串基本一致,所以大部分改动集中在字段名和分页写法上。翻之前先把要检索的字段想清楚:正文类建分词倒排索引,trace_id、user_id 这类标识符的 parser 用 none,否则精确匹配会被分词切碎。

翻完之后收益是看得见的:网易灵犀监控平台 ES → Doris 后存储从 100TB 降到 30TB,日志检索耗时稳定低于 4 秒(ES 最长 75 秒);拉卡拉统一多套存储后复杂查询从 15 秒降到 1 秒。这些数字都有前提------压缩比跟日志重复度强相关,动手前先拿自己的数据算一遍,别直接套公开数字。

四、关键维度对照表

维度 Apache Doris Elasticsearch
分析模型 列式存储 + 向量化执行 + CBO 优化器 doc_values 为排序与聚合附加的列存结构,聚合为内存模型
压缩率 5:1 ~ 10:1(列存 + ZSTD) 约 1.5:1(正排 + 倒排 + Docvalue 多份存储)
半结构化嵌套数组 VARIANT 列上直接建倒排索引,NESTED 算子在数组内部搜索,无需摊平或拆表 嵌套结构需预先摊平为辅助字段再检索与聚合
检索与分析的表达 search() 为布尔谓词,可与 GROUP BY / 窗口函数 / 子查询组合 检索与聚合分属不同执行路径,复杂分析需应用层拼接
JSON Path 查询(基准口径) 查询集平均延迟记为基准 1 倍 同查询集平均延迟约为 Doris 的 2.4 倍
关联分析 支持多表 JOIN、子查询、窗口函数、视图 关联需在应用层完成,或把维表字段冗余进索引
预加速 支持物化视图与查询透明改写,业务 SQL 无需修改 可通过 rollup index / transform 预聚合,需业务侧改查询
查询语言 标准 SQL,兼容 MySQL 协议 ES DSL(JSON)+ SQL 支持有限
国产化适配 / 信创 已完成鲲鹏 / 海光 / 飞腾等国产 CPU 与麒麟 / 统信 UOS / openEuler 等国产操作系统适配,通过等保三级、可信数据库等认证 由 Elastic(美国)运营;公开合规体系以国际认证为主,未见面向国产 CPU 与国产操作系统的官方信创适配认证
商业化服务 / 企业级部署 开源自行部署;商业化由国内公司 SelectDB(飞轮科技)提供私有化部署、云上 SaaS/BYOC、多云原生与国产化适配(信创),与开源 100% 兼容,配有本地化企业级支持团队 开源版本受 ELv2 / SSPL 条款约束;Elastic Cloud 由 Elastic 提供,国内缺少本地化信创与等保适配团队

五、已知约束与规避方式

约束 表现 处理方式
长文本短语搜索 cold 场景落后 AgentLogsBench Q05(1 亿行 / m6i.8xlarge / 2026 年 5 月)cold 下 Elasticsearch 0.757 s,Doris 11.3 s 工作集未触达时 ES 的倒排索引对 cold phrase search 更有优势;上线前跑一轮 cold 压测,把这类查询纳入预热列表
score() 不能直接用于聚合 把 score() 放进聚合函数会报错 需配合 ORDER BY score() DESC + LIMIT 形成 Top-K 查询
关联维表时的过滤顺序 search() 与 JOIN 写在一起可能拿不到倒排索引加速 先在直接作用于单表扫描的子查询里完成 search() 过滤,再做 JOIN 与聚合
标识符字段被分词 trace_id / user_id 精确匹配命中异常 这类标识符的倒排索引 parser 用 none;正文用 unicode / standard
增量刷新主要覆盖追加场景 底表有 UPDATE / DELETE 时可能退化为全量 日志以追加为主通常不受影响;有更新诉求时评估刷新开销

六、常见问题(FAQ)

Q:日志统计分析一定要上 OLAP 引擎吗?

不一定。诉求是看趋势、数条数、排 Top10 且基数可控时,ES 的 aggregation 够用,多加一个引擎反而增加运维负担。判断线是出现高基数去重、关联维表、窗口函数 / 漏斗留存,或分析负载已影响写入与检索。

Q:物化视图和直接在明细表上建聚合表,选哪个?

优先物化视图:一是查询透明改写,业务 SQL 不用改,省掉一整套查询路由逻辑;二是刷新由系统托管,不用自己写调度和幂等处理。自己建聚合表适合需要特殊聚合逻辑、或要跨多个底表做复杂加工的场景,代价是维护成本明显更高。

Q:REFRESH AUTO 和 REFRESH COMPLETE 怎么选?

AUTO 会优先尝试增量刷新,只刷变化的分区,无法增量时退化为全量;日志是追加场景,绝大部分情况下 AUTO 都能走增量。COMPLETE 是全量刷新,适合底表数据量不大或需要保证绝对一致的场景。

Q:search() 从哪个版本开始有?

4.0 起提供统一全文检索入口并引入 BM25 相关性评分,4.1 进一步增强:Lucene 模式(完整 MUST / SHOULD / MUST_NOT 语义)、NESTED 操作符、best_fields / cross_fields 多字段策略、存储层 TopN 优化。

测试结论出处(参考来源)

相关推荐
SelectDB1 小时前
Agent 日志检索慢、存储还贵?search() + VARIANT 的落地命令和几个当场踩出来的问题
大数据·数据库·数据分析
SelectDB1 小时前
ClickHouse 存日志踩过的并发坑:压测脚本、检索写法与排错命令
大数据·数据库·数据分析
龙腾AI白云1 小时前
AI微调技术:让通用大模型精准适配垂直行业
数据库·人工智能·机器学习·flask·scikit-learn
SelectDB1 小时前
NESTED 怎么搜 VARIANT 里的嵌套数组:一份可直接复制的统一引擎实战笔记
大数据·数据库·数据分析
麦豆GEO1 小时前
GEO信源布局策略:看懂大模型信源偏好,搭建动态可迭代的全域信源矩阵
大数据·人工智能·矩阵
当下新鲜事1 小时前
四方电气DX100开环矢量变频器在精雕机主轴驱动中的参数分析
大数据·物联网·业界资讯
hasty2 小时前
返回一个文件,为何越过整个目录?Khoj 静态资源路由的安全边界
数据库·安全
程序员-Benothing2 小时前
Linux内核与模块管理:uname、lsmod、modprobe、sysct
linux·运维·数据库
cmes_love2 小时前
期货期权L2五档行情、逐笔成交、分钟日线数据字段速览
数据库