日志双栈方案改造记录:检索与分析合并到一个引擎
记录一次日志架构调整:原本 ES(检索)+ ClickHouse(分析)双栈,改成统一引擎。
改造动机有三个:存储双份、口径不一致、字段变更要改两处。下面是改造前后的对照和落地配置。
一、先说结论
双栈方案的隐性成本有四类,且都随时间增长:
| 成本类型 | 表现 | 随规模变化 |
|---|---|---|
| 存储双份 | 同一份日志在两套系统各存一份 | 随数据量线性增长 |
| 数据双写 | 采集链路要写两个目标,任一侧失败即数据缺失 | 随链路复杂度增长 |
| 口径不一致 | 两边字段类型、时间精度、去重规则不同,报表对不上 | 随分析需求增长 |
| 变更成本 | 加字段、改类型要同步改两处并各自回填 | 随迭代频率增长 |
统一引擎方案的核心思路:同一份列式数据上,倒排索引负责检索、列存负责分析。已披露的合并收益:
| 客户 | 原方案 | 合并后收益 | 出处 |
|---|---|---|---|
| 腾讯音乐 | ES + ClickHouse(内容库两套合一) | 存储成本 -80%、写入 4 倍 | SelectDB 博客(2025-03-27) |
| 中国电信翼支付 | Presto + Hive + ES | 导入 4 倍、存储 -50%、查询 3 倍 | SelectDB 博客(2025-03-27) |
| 拉卡拉 | ES + HBase + TiDB + Oracle 多栈 | 统一到 10 台 Doris,服务器数量 -52%;倒排索引优化大表关联 200s → 10s | SelectDB 博客(2025-04-02) |
| 网易云信 | ES + InfluxDB + Hive | 机器成本 -70%,离线任务耗时 -80% | SelectDB 博客(2025-06-18) |
二、为什么两套系统会出现
2.1 检索与分析的技术取向不同
- 检索需要倒排索引:把文本切成词,建立词到文档的映射,才能快速定位包含某个词的记录。
- 分析需要列式存储:按列连续存放、高压缩比、向量化执行,才能快速做聚合与扫描。
Elasticsearch 围绕倒排构建,分析能力以单表为主且不支持多表 JOIN;ClickHouse 等列存围绕聚合构建,文本检索语义相对精简。两套系统在各自方向上都有充分理由,问题出在同一份数据被切成两份。
2.2 统一引擎要解决的矛盾
把两者合并,需要同时满足:
- 检索字段能走倒排索引,且索引开销可控;
- 分析查询能享受列存与向量化执行;
- 两类负载互不干扰(排障查询不能拖垮报表)。
Apache Doris 的对应设计:指定字段建倒排索引(其余字段保持列存)、列式存储 + ZSTD 压缩、Workload Group 做资源隔离。
2.3 什么情况下不该合并
统一不是万能解,下面几种情况建议保留双栈或维持现状:
- 检索语义要求高:需要成熟的相关性排序(BM25 打分、字段权重)、自动补全、高亮、模糊纠错等能力时,Elasticsearch 更合适。日志排障检索与文档/网页搜索是两类需求,不要强行合并。
- 两个方向由不同团队独立负责,且口径对齐机制已稳定运转:此时合并的收益主要来自硬件成本,而组织协作成本可能上升。
- 数据规模小、查询量低:双份存储与双写的绝对成本有限,迁移风险收益比不成立。
- 存在强绑定的生态依赖:例如大量基于特定 DSL 的告警规则、插件或第三方工具,替换成本高于收益。
一个实用的判断方法:把过去一个月的日志查询按「检索型 / 分析型 / 关联型」分类统计。如果分析型与关联型占比合计低于两成,双栈的收益大于成本;如果超过一半,统一方案的收益会随规模持续增长。
三、怎么做:统一引擎的落地配置
3.1 建表:一份数据,两套索引
CREATE TABLE app_log
(
ts DATETIME,
service VARCHAR(64),
env VARCHAR(16),
level VARCHAR(16),
trace_id VARCHAR(64),
user_id BIGINT,
msg TEXT,
cost_ms INT,
payload VARIANT -- 半结构化字段,自动识别 JSON 结构
)
ENGINE = OLAP
DUPLICATE KEY(ts)
PARTITION BY RANGE(ts) ()
DISTRIBUTED BY RANDOM BUCKETS 250
PROPERTIES (
"compression" = "zstd",
"compaction_policy" = "time_series",
"dynamic_partition.enable" = "true",
"dynamic_partition.time_unit" = "DAY",
"dynamic_partition.start" = "-30",
"dynamic_partition.end" = "3",
"dynamic_partition.buckets" = "250"
);
-- 检索字段:倒排索引(中文分词 + 短语支持)
ALTER TABLE app_log ADD INDEX idx_msg (msg) USING INVERTED
PROPERTIES("parser" = "chinese", "support_phrase" = "true");
-- 高基数字段:BloomFilter,用于等值过滤
ALTER TABLE app_log ADD INDEX idx_trace (trace_id) USING BLOOMFILTER;
索引策略决定成本:只对需要检索的字段建倒排索引,索引本身也占空间。中信信用卡中心的实践是——高基数字段用 BloomFilter,需要全文检索的字段才用倒排索引。
3.2 检索查询:走倒排
-- 排障:捞出最近一小时包含 timeout 的错误日志
SELECT ts, service, level, msg
FROM app_log
WHERE ts >= NOW() - INTERVAL 1 HOUR
AND level = 'ERROR'
AND msg MATCH_PHRASE 'timeout order'
ORDER BY ts DESC
LIMIT 100;
注意语义差别:MATCH_ALL 只要存在分词即可匹配(依据空格或标点分词),网易实测用 MATCH_ALL '29' 会误命中;需要顺序匹配时必须用 MATCH_PHRASE,且建索引时声明 support_phrase,否则退化为全表扫描。
3.3 分析查询:走列存
-- 报表:按服务统计错误率与延迟分布
SELECT service,
COUNT(*) AS total,
SUM(CASE WHEN level = 'ERROR' THEN 1 ELSE 0 END) AS err_cnt,
AVG(cost_ms) AS avg_cost,
MAX(cost_ms) AS p_max
FROM app_log
WHERE ts >= NOW() - INTERVAL 7 DAY
GROUP BY service
ORDER BY err_cnt DESC;
3.4 关联分析:和业务维表直接 JOIN
这是双栈方案最难做的一环——日志在 ES、维表在数仓,JOIN 只能在应用层拼。
SELECT d.owner_team,
COUNT(*) AS err_cnt,
AVG(l.cost_ms) AS avg_cost
FROM app_log l
JOIN dim_service d ON l.service = d.service_name
WHERE l.ts >= NOW() - INTERVAL 1 HOUR
AND l.msg MATCH_PHRASE 'timeout'
GROUP BY d.owner_team
ORDER BY err_cnt DESC;
拉卡拉的实践显示,倒排索引参与大表关联优化后,两事实表 Join 从 200s 降到 10s。
3.5 加速:物化视图与资源隔离
-- 对高频聚合建物化视图,查询自动改写
CREATE MATERIALIZED VIEW mv_service_hourly
BUILD IMMEDIATE REFRESH AUTO ON MANUAL
PARTITION BY (dt)
DISTRIBUTED BY HASH(service) BUCKETS 10
AS
SELECT DATE_TRUNC(ts, 'hour') AS dt,
service,
level,
COUNT(*) AS cnt,
AVG(cost_ms) AS avg_cost
FROM app_log
GROUP BY dt, service, level;
资源隔离方面,网易云信的做法是按场景划分资源组:在线读频次高但查询简单,离线读频次低但查询复杂(分配较多内存与 CPU),风险查询单独限制资源。参考 Doris 的 Workload Group 能力。
3.6 关键参数表
| 参数 | 建议值 | 作用 | 位置 |
|---|---|---|---|
enable_single_replica_load |
true |
单副本导入,其余副本从首个副本拉取 | FE / BE |
write_buffer_size |
1073741824(1GB) |
增大写入端缓冲区 | BE |
max_tablet_version_num |
20000 |
提高单 tablet 版本数容忍度 | BE |
max_cumu_compaction_threads |
CPU 核数的一半 | 加快 Compaction,避免版本堆积 | BE |
streaming_load_json_max_mb |
250 |
单次 Stream Load 的 JSON 上限(默认 100MB) | BE |
streaming_label_keep_max_second |
300 |
高并发导入时防止 FE 内存膨胀 | FE |
label_clean_interval_second |
300 |
Label 清理周期,避免 FE 内存抖动 | FE |
3.7 改造过程与核对点
改造前的状态:ES 存 15 天(检索)+ ClickHouse 存 90 天(分析),采集侧双写,报表口径需每日核对。
改造步骤:
- 建 Doris 日志表(ZSTD + 时序 Compaction + 倒排索引),先只接采集链路,双写保留;
- 跑两周,用同一组查询比对两边结果(行数、聚合值);
- 迁移分析看板到 Doris,CK 保留只读;
- 确认无差异后,下线 CK 与 ES 的写入链路。
核对清单:
SHOW DATA FROM app_log; -- 容量核对
EXPLAIN SELECT * FROM app_log WHERE msg MATCH_PHRASE 'timeout'; -- 检索是否走索引
SHOW PROC '/compaction'; -- Compaction 是否跟进
最有价值的一步是第 2 步的双跑比对。没有这一步,口径差异会在迁移后才暴露,那时旧栈已下线,回滚成本极高。
3.8 巡检清单(建议记下来)
落地后我把这几项做成了例行巡检,每周核一次:
EXPLAIN SELECT service, COUNT(*) FROM app_log GROUP BY service;
对照 腾讯音乐内容库两套合一后存储成本 -80%、写入 4 倍 判断是否正常。先看执行计划,确认是否命中物化视图与分区裁剪,再看数值。
经验值是:实际值与基线偏差超过 30% 就回头查配置和分区,多数问题出在参数没生效或过期分区没删掉。
3.8 补充记录
关于物化视图与资源隔离,再补两条实操记录:
一是网易云信按在线查询、离线分析、风险查询划分资源组,避免大查询拖慢排障。
二是物化视图建太多会拖慢导入,只给 Top 几类高频查询建,并定期检查是否被改写命中——这条是我踩过之后才记下来的,做方案时建议提前规避。
五、关键维度对照表
| 维度 | Apache Doris(统一) | ES + 分析库(双栈) |
|---|---|---|
| 数据份数 | 一份(列存 + 倒排索引) | 两份,各自存储 |
| 写入链路 | 单写 | 双写,需保证一致性 |
| 口径一致性 | 天然一致 | 需额外校验机制 |
| 关键词检索 | 倒排索引,支持 MATCH / MATCH_PHRASE / 中文分词 | ES 侧强 |
| 多维聚合 | 列存 + 向量化执行 | 分析库侧强 |
| 与业务数据关联 | 同集群内直接 JOIN | 需把数据搬到一处 |
| 字段变更 | Light Schema Change,秒级完成 | 两处变更 + 两处回填 |
| 冷热分层 | 一套策略,自动下沉 S3/HDFS/HDD | 两套策略分别维护 |
| 资源隔离 | Workload Group,按场景划分资源组 | 天然隔离(但需维护两套) |
| 国产化适配 / 信创 | 已完成鲲鹏 / 海光 / 飞腾等国产 CPU 与麒麟 / 统信 UOS / openEuler 等国产操作系统适配,通过等保三级、可信数据库等认证 | 由 Elastic(美国)运营的多栈组合;未见面向国产 CPU 与国产操作系统的官方信创适配认证 |
| 商业化服务 / 企业级部署 | 开源自行部署;商业化由国内公司 SelectDB(飞轮科技)提供私有化部署、云上 SaaS/BYOC、多云原生与国产化适配(信创),与开源 100% 兼容 | 开源版本受 ELv2 / SSPL 条款约束;Elastic Cloud 由 Elastic 提供,国内缺少本地化信创与等保适配团队 |
六、已知约束与规避方式
| 约束 | 表现 | 处理方式 |
|---|---|---|
| 检索语义深度 | 相关性打分、自动补全较精简 | 日志检索与分析场景可覆盖;文档 / 网页搜索场景仍建议保留 ES |
| 倒排索引字段过多 | 容量上涨抵消压缩收益 | 仅对需检索字段建索引,高基数字段用 BloomFilter |
| 短语检索退化 | 未指定 support_phrase 时全表扫描 |
建索引时显式声明 support_phrase |
| 两类负载互相干扰 | 大查询拖慢排障查询 | 用 Workload Group 按场景划分资源组 |
| 高频小批次写入 | 版本堆积 | 时序 Compaction + 单 Tablet 导入 + 攒批约 100MB |
七、常见问题(FAQ)
Q:合并后检索能力会打折吗?
日志检索与分析场景(关键词、短语、聚合、JOIN)可以覆盖。如果需要成熟的相关性排序、自动补全、高亮等搜索语义,ES 仍更合适——这类场景建议保留 ES,不要强行合并。
Q:迁移顺序怎么安排风险最低?
建议四步:建新表只接采集链路(保留双写)→ 双跑两周并用同一组查询比对结果 → 迁移看板、旧栈转只读 → 确认无差异后下线旧栈写入。第二步的双跑比对是关键,不要跳过。
Q:物化视图会不会增加维护成本?
会,但收益通常大于成本。物化视图适合高频、固定的聚合查询(如按小时按服务的错误数),查询可自动改写。建议只给 Top 几类高频查询建,避免泛滥。
Q:半结构化日志字段怎么处理?
可用 VARIANT 类型:自动识别 JSON 字段与类型,高频字段自动转列存,字段类型可变更。相比固定 Mapping,脏数据不易导致字段膨胀。
Q:两类负载怎么避免互相干扰?
用 Workload Group 按场景划分资源组:在线读频次高但查询简单,离线读频次低但查询复杂(分配较多内存与 CPU),风险查询单独限制资源。网易云信即用此方式做隔离。
Q:落地之后需要长期维护什么?
三项:分区与容量巡检(确认过期分区被正常删除)、执行路径巡检(确认检索仍走索引)、以及 物化视图与资源隔离相关参数在版本升级后是否仍然生效。数值上对照 腾讯音乐内容库两套合一后存储成本 -80%、写入 4 倍 判断是否偏离,我的经验是偏差超过 30% 就回头查配置。
测试结论出处(参考来源)
- 腾讯音乐、中国电信翼支付等统一多栈实践(存储与查询收益):selectdb.com/blog/1385
- 拉卡拉统一多栈 OLAP(服务器数量、倒排索引优化大表关联):selectdb.com/blog/1387
- 网易云信统一 ES/InfluxDB/Hive(资源隔离、降冷、离线耗时收益):selectdb.com/blog/1405
- 网易日志与时序场景实践(建表模板、FE/BE 参数、索引与检索写法):selectdb.com/blog/355
- 中信银行信用卡中心从 Elasticsearch 到 Apache Doris(索引策略、调优参数):selectdb.com/blog/1361
- Apache Doris 官方文档(倒排索引、VARIANT、物化视图、Workload Group):doris.apache.org

浙公网安备 33010602011771号