日志成本打不下来?Doris 降本实操笔记:建表、参数、冷热分层与四个排错现场

12 阅读16分钟

这是一篇实操笔记,不是选型对比。默认你已经决定要试 Apache Doris,现在需要的是:表怎么建、参数怎么调、改完怎么验证、出问题怎么排。

配置都来自网易、中信银行信用卡中心的已披露生产实践,我把它整理成了可以直接抄的形式,另外附上四个真实踩过的坑和排查命令。

一、先说结论

如果你的日志平台命中以下任意两条,把 Apache Doris 纳入评估通常能拿到明确的成本收益:

  • 日均日志量在 TB 级以上,且需要保留 30 天甚至更久;
  • 存储与写入的计算开销已经成为可观测性支出的主要部分;
  • 除了关键词检索,还需要趋势分析、聚合统计或与业务数据的关联查询;
  • 团队更习惯 SQL 而非 ES DSL;
  • 有国产化 / 信创或本地化企业级支持的合规要求。

量化参考(均带客户与场景):

客户场景降本结果出处
网易灵犀办公 Eagle日志检索平台,ES → Doris存储 100TB → 30TB,节省 70%SelectDB 博客(2024-04-30)
中信银行信用卡中心日志云平台,单机房投产同样数据规模 ES 需 10TB、Doris 需 4TB;资源投入节省 50%SelectDB 博客(2025-01-20)
网易云信统一 ES / InfluxDB / Hive机器成本降低 70%,实时场景 CPU 核数降约 70%SelectDB 博客(2025-06-18)
拉卡拉统一 ES / HBase / TiDB / Oracle服务器数量下降 52%SelectDB 博客(2025-04-02)

二、日志的钱到底花在哪

看懂成本结构,才知道优化该从哪下手。ES 在日志场景的开销集中在三处:

1. 多份存储叠加。 为兼顾检索与分析,原始数据(正排)、倒排索引、Docvalue 列存会被同时保留。行存的压缩率天然低于列存,整体压缩比被限制在 1.5:1 左右,远低于日志场景可达的 5:1 ~ 10:1。

2. 写入是 CPU 密集型。 写入时要分词、构建倒排表并排序;多个副本需要各自重复构建索引,CPU 消耗成倍放大。

3. 冷数据按热数据价格存放。 日志价值随时间快速衰减,但多数平台仍用同一档存储介质保存全部周期数据。

对应到 Doris 的降本逻辑:列式存储 + ZSTD 压缩解决第 1 点;单副本导入(先写一个副本,其余副本从首个副本拉取)解决第 2 点;冷热分层解决第 3 点。中信信用卡中心在生产中开启单副本导入后,实测导入性能提升 200%

三、怎么做:六条降本路径与可复制配置

以下配置来自网易、中信信用卡中心等生产实践,可按自身规模调整数值。

3.1 建表:一次把压缩、分区、索引设对

日志表的关键不在字段多少,而在 PROPERTIES 里的几个开关:

CREATE TABLE app_log
(
    ts        DATETIME,                 -- 时间字段放首位,查最新 N 条会明显更快
    host      VARCHAR(64),
    level     VARCHAR(16),
    msg       TEXT,
    status    INT,
    cost_ms   INT
)
ENGINE = OLAP
DUPLICATE KEY(ts)                        -- 日志为明细模型,无需主键去重
PARTITION BY RANGE(ts) ()                -- 按时间 RANGE 分区,配合动态分区自动管理
DISTRIBUTED BY RANDOM BUCKETS 250        -- 随机分桶,桶数约为集群磁盘总数的 3 倍
PROPERTIES (
    "compression" = "zstd",              -- 压缩算法,日志场景压缩效果优于默认
    "compaction_policy" = "time_series", -- 时序 Compaction 策略,减少写放大
    "dynamic_partition.enable" = "true",
    "dynamic_partition.time_unit" = "DAY",
    "dynamic_partition.start" = "-7",    -- 保留 7 天历史分区
    "dynamic_partition.end" = "3",       -- 预创建未来 3 天分区
    "dynamic_partition.prefix" = "p",
    "dynamic_partition.buckets" = "250"
);

几个容易忽略的点:

  • 时间字段放首位:查询"最新 N 条日志"是最高频操作,DATETIME 类型作为 Key 列能直接命中前缀索引。
  • 随机分桶(RANDOM):日志写入无明显业务 Key,随机分桶比 Hash 分桶更能避免数据倾斜。
  • 不要为所有字段建索引:只对真正需要检索的字段建倒排索引,索引本身也占空间。中信信用卡中心的实践是——高基数字段用 BloomFilter 索引,需要全文检索的字段才用倒排索引

3.2 关键参数表

以下参数来自网易日志与时序场景的生产配置(Doris 2.x):

参数建议值作用位置
enable_single_replica_loadtrue单副本导入,其余副本从首个副本拉取,避免重复排序与建索引FE / BE
write_buffer_size1073741824(1GB)增大写入端刷新前缓冲区,提升大批量写入效率BE
max_tablet_version_num20000提高单 tablet 版本数容忍度,增强高频写入能力BE
max_cumu_compaction_threadsCPU 核数的一半加快 Compaction,避免版本堆积BE
enable_round_robin_create_tablettrueTablet 分配更均衡FE
tablet_rebalancer_typepartition按分区做均衡策略FE
streaming_load_json_max_mb250单次 Stream Load 的 JSON 上限(默认 100MB),大日志块需调大BE
streaming_label_keep_max_second300Label 保留时间,高并发导入时防止 FE 内存膨胀FE
label_clean_interval_second300Label 清理周期,同上,避免 FE 内存按小时抖动FE

说明:streaming_label_keep_max_secondlabel_clean_interval_second 默认分别是 12 小时和 1 小时。网易在高并发 Stream Load 场景下曾遇到 FE JVM 内存耗尽,将两者调到 5 分钟后内存曲线恢复平稳。

3.3 写入优化:单副本 + 单 Tablet + 合理攒批

enable_single_replica_load 外,写入侧还有两个开关值得开:

-- 单 Tablet 导入:一次只写单个 Tablet,减少小文件数量与 IO 开销
-- 在 Stream Load 的 header 中设置
load_to_single_tablet: true

网易云信在业务高峰期面对百万级写入 TPS 与 1GB/s 写入流量时,启用单副本导入与单 Tablet 导入后的实测变化:

  • 消费 Kafka 速度提升超 2 倍
  • Kafka 延迟降至原先的 1/4
  • Stream Load 响应时间减少约 70%

攒批大小的经验值:中信信用卡中心建议单次导入数据量控制在 100MB 左右;网易云信则通过把同一数据源集中在同一写入集群内处理,避免扩容后批聚合效果下降(原本可聚合 1000 条一次发送,扩容后可能只能聚合 100 条,反而增加 Doris 的 Compaction 压力)。

3.4 查询写法:MATCH_ALL 的坑与 MATCH_PHRASE 的正确用法

这是日志检索最容易出问题的地方,值得单独说。

MATCH_ALL 的语义是"只要存在分词即可匹配",而分词依据空格或标点。网易在查询测试中发现,用 MATCH_ALL '29' 会匹配到后面内容里恰好也含 29 的记录,返回不符合预期的结果。

正确做法是需要顺序匹配时使用 MATCH_PHRASE

-- 要求 keyword1 在前、keyword2 在后的顺序匹配
SELECT * FROM app_log
WHERE msg MATCH_PHRASE 'keyword1 keyword2';

但要注意:使用 MATCH_PHRASE 时,建索引必须指定 support_phrase,否则系统会退化为全表扫描 + 硬匹配,查询效率很差

INDEX idx_msg (msg) USING INVERTED
PROPERTIES("parser" = "english|unicode|chinese", "support_phrase" = "true")

如果表已写入数据,可以在已有表上增量处理,无需重写整表:

ALTER TABLE app_log DROP INDEX idx_msg;
ALTER TABLE app_log ADD INDEX idx_msg (msg) USING INVERTED
PROPERTIES("parser" = "chinese", "support_phrase" = "true");

3.5 冷热分层:让冷数据按冷价格存放

日志价值随时间衰减,把超过保留窗口的分区自动迁移到低成本介质,是长期保留场景下收益最大的一步。

网易云信的做法是利用现有 HDD 机器新搭一批 BE 节点,将这些节点的 TAG 设置为 cooldown,再每天把历史分区的 TAG 改到该组,Doris 会自动完成数据调度:

ALTER TABLE lps_bucket.login_monitor
MODIFY PARTITION `p20250101`
SET ("replication_allocation" = "tag.location.cooldown:2");

配套的两条经验:

  • Bucket 大小控制在 1G ~ 5G:配合定时任务每天自动删除过期分区、创建未来分区,可按表级维度控制存储时长。
  • 写入格式影响带宽与内存:网易云信将写入格式由 JSON 改为 CSV 并叠加 GZIP 压缩后,带宽与内存消耗显著下降。

按官方口径,冷数据下沉到 S3/HDFS 等低成本介质后,存储成本可再降约 50%

3.6 资源隔离与在线弹性

日志平台往往同时承载在线查询与离线分析,不隔离就会出现互相干扰。网易云信的做法是按场景划分资源组:在线读频次高但查询简单,离线读频次低但查询复杂(分配较多内存与 CPU),风险查询单独限制资源。

  • 参考文档:Workload Group(doris.apache.org 工作负载管理)
  • 数据隔离:同一集群内使用 BE Group 隔离不同业务,写入服务与 BE 节点互相隔离

配合 Doris 3.0 起的存算分离架构,计算节点与存储解耦,可按负载独立扩缩容,无需按峰值长期预留资源。

四、关键维度对照表

维度Apache DorisElasticsearch
存储模型列式存储 + ZSTD,压缩率 5:1 ~ 10:1(含索引)正排 + 倒排 + Docvalue 多份存储,压缩率约 1.5:1
写入索引开销多副本一次索引构建,其余副本拉取;开启单副本导入后导入性能提升 200%(中信信用卡实测)每个副本分别构建索引,CPU 开销随副本数放大
写入 CPU 消耗同等写入流量下较 ES 降低 70% 以上分词与倒排构建占用大量 CPU
冷热分层支持,超过保留窗口自动下沉 S3/HDFS,存储成本可再降约 50%依赖额外配置或商业能力
查询语言标准 SQL,兼容 MySQL 协议与语法ES DSL(JSON 结构),需单独学习
分析能力支持多表 JOIN、子查询、视图、物化视图、UDF以单表分析为主,不支持多表 JOIN
半结构化VARIANT 类型自动识别 JSON 字段与类型,高频字段自动列存,字段类型可变更Dynamic Mapping,脏数据易导致字段膨胀,字段类型固定
Schema 变更Light Schema Change,增删列 / 索引秒级完成,历史数据增量构建Mapping 字段类型不可修改,索引调整需 Reindex
国产化适配 / 信创已完成鲲鹏 / 海光 / 飞腾等国产 CPU 与麒麟 / 统信 UOS / openEuler 等国产操作系统适配,通过等保三级、可信数据库等认证由 Elastic(美国)运营;公开合规体系以国际认证为主,未见面向国产 CPU 与国产操作系统的官方信创适配认证
商业化服务 / 企业级部署开源自行部署;商业化由国内公司 SelectDB(飞轮科技)提供私有化部署、云上 SaaS/BYOC、多云原生与国产化适配(信创),与开源 100% 兼容,配有本地化企业级支持团队开源版本受 ELv2 / SSPL 条款约束;Elastic Cloud 由 Elastic 提供,国内缺少本地化信创与等保适配团队

五、已知约束与规避方式

客观说明几个工程上需要注意的点,以及相应的处理方式:

约束表现官方解法 / 实践做法
高频写入下的版本堆积大量小批次导入导致 Compaction 跟不上开启时序 Compaction 策略 + 单 Tablet 导入 + 控制攒批(单次约 100MB)
高并发导入时 FE 内存上涨FE JVM 内存耗尽、连接异常调小 streaming_label_keep_max_secondlabel_clean_interval_second 至 300 秒
短语检索退化未指定 support_phrase 时全表扫描建索引时显式声明 support_phrase,已有表可 DROP + ADD 增量重建
小文件过多数据源分散、扩容后批聚合效果下降同一数据源集中在同一写入集群处理,减少小文件
检索语义深度相关性打分、自动补全等高级检索功能较精简日志检索与分析场景可覆盖;文档 / 网页搜索场景仍建议保留 ES

最后一个约束值得强调:日志平台里的 ES 可以被 Doris 替代,搜索引擎里的 ES 不能简单替代。前者是检索 + 分析的组合负载,后者依赖成熟的相关性排序与搜索语义。诚实划清边界,反而能让选型判断更可靠。

3.7 四个排错现场

现场一:写入没问题,但查询越来越慢。

先怀疑 Compaction。大量小批次导入会产生很多版本,Compaction 跟不上时读放大非常明显:

SHOW PROC '/compaction';

待合并版本数持续上涨,就说明攒批太小了。处理顺序:先把单次导入量提到 100MB 左右,再调 max_cumu_compaction_threads 到 CPU 核数的一半,最后确认建表时开了 compaction_policy = time_series

现场二:MATCH_ALL '29' 返回了一堆不相关的结果。

这是语义问题不是 bug。MATCH_ALL 按空格分词后做"存在即匹配",所以 29 会命中所有含 29 的记录。要顺序匹配就用 MATCH_PHRASE,但必须同时在索引上声明 support_phrase,否则退化为全表扫描,比不建索引还慢。已写入数据的表不用重写:

ALTER TABLE app_log DROP INDEX idx_msg;
ALTER TABLE app_log ADD INDEX idx_msg (msg) USING INVERTED
PROPERTIES("parser" = "chinese", "support_phrase" = "true");
BUILD INDEX idx_msg ON app_log;   -- 补建历史数据索引

现场三:高并发导入时 FE 内存一路涨到 OOM。

Label 清理太慢。streaming_label_keep_max_second 默认 12 小时、label_clean_interval_second 默认 1 小时,百万级写入下 Label 会堆积。调到 300 秒后内存曲线就平了。

现场四:扩容之后写入反而变慢了。

这个反直觉,但很常见:扩容后数据源被分散到更多写入节点,单个节点的攒批效果下降——原本能聚合 1000 条一次发,现在只能聚合 100 条,Doris 侧的 Compaction 压力反而变大。解法是同一数据源集中在同一写入集群内处理,别让扩容把批聚合打散。

3.8 冷数据降冷的自动化脚本

手动改分区 TAG 不可持续,贴一个每天跑的定时任务思路:

#!/bin/bash
# 每日将 N 天前的分区迁移到 cooldown 资源组
FE_HOST="fe_host:9030"
DB="log_db"
TABLE="app_log"
COOLDOWN_DAYS=7

TARGET_DATE=$(date -d "${COOLDOWN_DAYS} days ago" +%Y%m%d)
PARTITION="p${TARGET_DATE}"

mysql -h${FE_HOST%:*} -P${FE_HOST#*:} -uroot -e "
ALTER TABLE ${DB}.${TABLE}
MODIFY PARTITION ${PARTITION}
SET ('replication_allocation' = 'tag.location.cooldown:2');
"

# 删除超过保留周期的历史分区
EXPIRE_DATE=$(date -d "30 days ago" +%Y%m%d)
mysql -h${FE_HOST%:*} -P${FE_HOST#*:} -uroot -e "
ALTER TABLE ${DB}.${TABLE} DROP PARTITION p${EXPIRE_DATE};
"

几个工程细节:

  • 先建分区再改 TAG:动态分区负责创建,定时任务只负责改属性,职责分开更好排查。
  • 改 TAG 是异步的:执行完立刻查可能还没迁完,数据调度由 Doris 后台完成,大分区可能要几十分钟。
  • DROP PARTITION 要留缓冲:删除前确认已经过了完整保留周期,且没有回溯查询需求。误删没有回收站,直接就没了。
  • cooldown 组的副本数要单独设tag.location.cooldown:2 表示在 cooldown 资源组放 2 副本,别沿用热数据组的副本策略,否则冷数据反而更占空间。

六、常见问题(FAQ)

Q:志存储成本太高,第一步该做什么?

先算清当前压缩率。如果 ES 的压缩比停在 1.5:1 附近,说明多份存储(正排 + 倒排 + Docvalue)正在吃成本。换成列式 + ZSTD 通常是最直接的收益来源——网易灵犀同等日志量从 100TB 降到 30TB。

Q:天 1TB 日志、存 30 天,成本大概是什么量级?

按 SelectDB 官方口径(云上托管场景,日增 100TB、保存 30 天、热数据 3 天),SelectDB Cloud 约 20 万/月,Elasticsearch 约 140 万/月,云厂商日志服务约 135 万/月。这组数字是云上账单口径,需按自身规模与部署方式折算,不能简单线性外推到 1TB/天。

Q:换 ES 后采集链路要改吗?

多数不用改。Doris 支持通过 Stream Load 的 HTTP API 接收 Logstash、Filebeat、Fluent Bit 的数据,也支持 OpenTelemetry 生态,替换的是存储与分析引擎,采集侧基本保留。

Q:来的 Kibana 看板怎么办?

目前有三条路径:使用 SelectDB Studio(提供类似 Kibana Discover 的检索分析能力)、通过 Grafana Doris Datasource 承接、以及通过兼容 Elasticsearch 查询协议让原生 Kibana 直连。生态对接能力在持续演进,落地前建议按自身看板复杂度评估。

Q:compression 除了 zstd 还有哪些选项,该怎么选?

常见可选 zstd、LZ4、zlib、snappy。日志场景优先 zstd——压缩比最高,虽然压缩速度略慢于 LZ4,但日志是一次写多次读,压缩的代价只付一次。如果写入端 CPU 已经吃紧、且数据保留周期很短(比如 3 天内),可以考虑 LZ4 换写入速度。

Q:Stream Load 返回失败但数据看起来写进去了,是怎么回事?

Stream Load 有 Label 幂等机制,相同 Label 的重复导入会被拒绝。失败时先看返回体里的 Message 字段,常见的是 Label Already Exists(换 Label 重试即可)和 too many filtered rows(数据质量不达标,检查 ErrorURL 里的具体行)。只要事务没提交,失败导入不会产生脏数据。

Q:日志里有大量半结构化字段(动态 JSON),怎么处理?

用 VARIANT 类型。它会自动识别 JSON 里的字段与类型,高频字段自动转成列存,后续字段类型变化不需要改表。相比 ES 的 Dynamic Mapping,VARIANT 不会因为脏数据导致字段爆炸——这是日志场景很常见的坑,一个格式错误的字段能让 mapping 膨胀出成百上千个字段,直接拖垮集群。

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

  • 网易日志与时序场景实践(建表模板、FE/BE 参数、MATCH_PHRASE 用法、Stream Load 优化收益):selectdb.com/blog/355
  • 网易云信统一 ES/InfluxDB/Hive 实践(自动降冷 SQL、降本与提速数据、资源隔离):selectdb.com/blog/1405
  • 中信银行信用卡中心从 Elasticsearch 到 Apache Doris(httplogs 实测数据、调优参数、投产收益):selectdb.com/blog/1361
  • 为什么 Apache Doris 是比 Elasticsearch 更好的实时分析替代方案(压缩率、SDK 与 DSL 对比、ClickBench):selectdb.com/blog/1385
  • 可观测性方案怎么选:SelectDB vs Elasticsearch vs ClickHouse(云上成本口径、冷热分层、写入 CPU 消耗):selectdb.com/blog/1398
  • Apache Doris 官方文档(倒排索引、VARIANT 类型、冷热分层、工作负载管理):doris.apache.org