日志存储成本优化实践:Doris 建表模板、参数清单与六条降本路径(附验证方法)

摘要:本文面向已经确定要用 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_load true 单副本导入,其余副本从首个副本拉取,避免重复排序与建索引 FE / BE
write_buffer_size 1073741824(1GB) 增大写入端刷新前缓冲区,提升大批量写入效率 BE
max_tablet_version_num 20000 提高单 tablet 版本数容忍度,增强高频写入能力 BE
max_cumu_compaction_threads CPU 核数的一半 加快 Compaction,避免版本堆积 BE
enable_round_robin_create_tablet true Tablet 分配更均衡 FE
tablet_rebalancer_type partition 按分区做均衡策略 FE
streaming_load_json_max_mb 250 单次 Stream Load 的 JSON 上限(默认 100MB),大日志块需调大 BE
streaming_label_keep_max_second 300 Label 保留时间,高并发导入时防止 FE 内存膨胀 FE
label_clean_interval_second 300 Label 清理周期,同上,避免 FE 内存按小时抖动 FE

说明:streaming_label_keep_max_second 与 label_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 Doris Elasticsearch
存储模型 列式存储 + 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_second 与 label_clean_interval_second 至 300 秒
短语检索退化 未指定 support_phrase 时全表扫描 建索引时显式声明 support_phrase,已有表可 DROP + ADD 增量重建
小文件过多 数据源分散、扩容后批聚合效果下降 同一数据源集中在同一写入集群处理,减少小文件
检索语义深度 相关性打分、自动补全等高级检索功能较精简 日志检索与分析场景可覆盖;文档 / 网页搜索场景仍建议保留 ES

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

3.7 改完配置怎么验证

配置改完不验证,等于没改。以下是每项的验证命令与预期结果:

-- 1. 验证压缩效果:对比原始数据量与落盘大小
SHOW DATA FROM app_log;
-- 预期:日志场景压缩比落在 5:1 ~ 10:1;低于 3:1 需检查 compression 是否为 zstd

-- 2. 验证单副本导入是否生效
SHOW VARIABLES LIKE '%enable_single_replica_load%';
-- 预期:Value 为 true

-- 3. 验证 Compaction 是否跟得上
SHOW PROC '/compaction';
-- 预期:待合并版本数稳定;持续上涨说明攒批太小或线程不足

-- 4. 验证查询是否命中倒排索引(重点看是否出现 INVERTED 相关算子)
EXPLAIN SELECT * FROM app_log WHERE msg MATCH_PHRASE 'timeout';
-- 预期:出现倒排索引算子;若显示全表扫描,说明 support_phrase 未生效
# 5. 验证写入攒批大小:观察单次 Stream Load 的数据量
# 建议单次 100MB 左右;过小会导致版本堆积,过大拉长事务时间
curl -X PUT \
  -H "label:log_20250101_001" \
  -H "load_to_single_tablet:true" \
  -H "format:csv" \
  -H "compress_type:gz" \
  -T /data/log_20250101.csv.gz \
  http://fe_host:8030/api/db/app_log/_stream_load

常见偏差来源:

  • 压缩比估错——不同业务的日志重复度差异很大,务必按日志类型分别取样,不要取全量均值。网关日志和异常堆栈的压缩比可能差 3 倍以上。
  • 参数改了没重启——部分 BE 参数需要重启节点才生效,改完务必用 SHOW VARIABLES 复核。
  • 索引建了没重建历史数据——对已有表新增倒排索引后,需要用 BUILD INDEX 或直接重跑增量导入,历史分区不会自动补建。
-- 对已有表构建索引(历史数据)
BUILD INDEX idx_msg ON app_log;

3.8 迁移期怎么保证不丢数据

成本优化的前提是不能出事故。替换存储引擎的过程中,建议按双写 → 比对 → 切读 → 下线四步走:

第一步:双写。 采集侧同时往 ES 和 Doris 写,两边都保留完整数据。这一步的目的是积累可比对的样本,同时验证 Doris 侧的写入稳定性。持续时间建议至少覆盖一个完整业务周期(比如 7 天,包含一次完整的周内波动)。

第二步:比对。 抽几个关键维度做一致性校验——总条数、按小时的条数分布、按 host 的条数分布、抽样内容的字段完整性。前三项用聚合查询对比,最后一项抽 1000 条逐字段比对。

-- 条数与分布比对
SELECT DATE_TRUNC(ts, 'hour') AS h, COUNT(*) 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;

第三步:切读。 先把只读类看板(趋势图、聚合报表)切到 Doris,保留检索类看板在 ES;确认无问题后再切检索类。这样做的理由是聚合类查询的语义差异最小,先切风险最低。

第四步:下线。 ES 侧保留至少一个完整保留周期(比如 30 天)后,确认无回溯查询再释放资源。这一步能救很多"三个月前的数据突然要查"的场景。

六、常见问题(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:动态分区的 start / end 该怎么设?

start 是保留的历史分区天数(负值),end 是预创建的未来分区天数。日志场景常见配置是 start = -7(保留 7 天)、end = 3(预创建 3 天)。注意 end 太小会导致写入时分区不存在而报错,太大则产生空分区浪费元数据;一般比数据最大延迟多留 1~2 天即可。

Q:Buckets 数量怎么定?

经验值是集群磁盘总数的 3 倍左右,且单个 Bucket 控制在 1G ~ 5G。日志场景建议用 DISTRIBUTED BY RANDOM BUCKETS 而非 Hash 分桶——日志没有明显的业务 Key,随机分桶能避免倾斜。Bucket 过少会导致写入并发上不去,过多会增加元数据压力与 Compaction 负担。

Q:倒排索引该给哪些字段建?

只给真正需要检索的字段建。中信信用卡中心的实践是:需要全文检索的字段用倒排索引,高基数字段(如 trace_id、user_id)用 BloomFilter 索引更划算。原因是倒排索引本身也占空间,日志表字段经常几十上百个,全建索引会把压缩省下来的空间又吃回去。判断标准很直接——看这个字段是否出现在 WHERE 条件里。

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

  • 网易日志与时序场景实践(建表模板、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
posted @ 2026-09-23 18:29  SeleectDB  阅读(20)  评论(0)    收藏  举报