算日志成本最容易漏掉的是两件事:副本数、索引开销。只拿原始量乘单价,结果往往偏乐观。
这篇把我用的测算脚本、核对容量的命令、冷热分层的配置整理出来,最后附一次「估算与实际偏差 40%」的排查过程。命令可以直接抄。
一、先说结论
先记住这个拆解顺序,后面每一步都在填变量:
scss
月存储成本 ≈ 容量基数 × 副本放大 × 单价 + 温度溢价 + 计算资源
容量基数 = 日增原始量 × 留存天数 ÷ 压缩比 × (1 + 索引开销比例)
温度溢价 = 热数据容量 × 热存储溢价
计算资源 = 写入侧资源 + 查询侧资源
三个变量的取值区间:
| 变量 | 典型取值 | 影响 |
|---|---|---|
| 有效压缩比 | Elasticsearch 约 1.5:1;Apache Doris 日志场景 5:1 ~ 10:1 | 决定存储基数,差异可达 3~6 倍 |
| 副本数 | 1 ~ 2 | 直接是乘数 |
| 热数据占比 | 占总容量的 10% ~ 30% | 决定按热价还是冷价结算 |
1TB/天、留 30 天(原始量 30TB)的对照演算:
| 项目 | Elasticsearch 口径 | Apache Doris 口径 | 说明 |
|---|---|---|---|
| 压缩后单副本容量 | 约 20TB(1.5:1) | 约 3.2 ~ 6TB(5:1 ~ 10:1) | 公开口径估算,需实测校准 |
| 两副本后容量 | 约 40TB | 约 6.4 ~ 12TB | 副本数是乘数 |
| 热数据(按 3 天) | 约 4TB | 约 0.6 ~ 1.2TB | 其余可下沉低成本介质 |
| 冷数据下沉后 | 部分可降本 | 公开口径存储成本可再降约 50% | 需配合对象存储 / HDD 资源组 |
这是估算框架,不是报价单。压缩比要拿自己的日志样本实测------同样是 JSON 日志,字段重复度高与低之间能差一倍。
二、漏掉的那两项是怎么吃成本的
2.1 副本数:同时放大存储与写入 CPU
多副本意味着每个副本各自重复排序、建索引。Doris 的单副本导入 先写一份,其余副本从首个副本拉取,避免重复的排序与索引构建开销。中信信用卡中心开启后实测导入性能提升 200% 。
2.2 索引开销:压缩比背后的第二个乘数
Elasticsearch 为兼顾检索与分析,正排、倒排索引、Docvalue 列存都保留,行存压缩率天然低于列存,日志场景整体压缩比被限制在 1.5:1 附近。Apache Doris 用列式存储 + ZSTD,日志场景压缩比可达 5:1 ~ 10:1。已披露的实测:
| 场景 | 数据 | 口径 | 出处 |
|---|---|---|---|
| httplogs 同配置同索引 | ES 19.4GB vs Doris 3.2GB,空间降低 83% | Elastic 官方 rally-tracks http_logs 数据集 | 社区实践博客(2025-03-27) |
| 网易灵犀办公 Eagle 生产 | ES 100TB → Doris 30TB,节省 70% | 日志检索平台真实数据量 | 社区实践博客(2024-04-30) |
| 中信银行信用卡中心 | 同样规模 ES 需 10TB、Doris 需 4TB | 单机房投产 | 社区实践博客(2025-01-20) |
| JSONBench 10 亿条 JSON | Doris 存储占用是 ES 的 1/2 | jsonbench.com 公开榜单 | 社区实践博客(2025-07-04) |
第三方基准可以再校准一次。AgentLogsBench 的前提:约 1 亿行 observation、AWS m6i.8xlarge(32 vCPU / 128 GiB / gp3 SSD)、20 个固定查询覆盖四类访问模式,且所有引擎必须在同一张表上完成全部负载,不允许预先摊平 JSON 或把搜索与分析拆成两套系统。其 2026 年 5 月 结果中,Doris 存储占用 57.94 GiB ;1 亿行规模下另一口径为 Doris 存储约为 ES / OpenSearch 的三分之一,接近 ClickHouse 量级。加载耗时 ClickHouse 2,755 s、Doris 4,396 s。基准定期更新,引用时带时间限定。
2.3 半结构化日志的打平 ETL 成本
嵌套 JSON 日志进来之后,常见做法是先跑一个打平任务,把数组里的字段摊成列再入库。这份成本不在存储账单里,但实打实占着计算资源和运维时间:多一份中间表、多一条调度、多一处需要对齐的 schema。后面 3.2 的 NESTED 就是为了省掉这一层。
三、可复制的配置与命令
3.1 建表:VARIANT 直接存嵌套结构
scss
CREATE TABLE agent_logs (
log_time DATETIME NOT NULL,
trace_id VARCHAR(64),
steps VARIANT, -- 嵌套 JSON 数组,不再预先摊平
INDEX idx_steps (steps) USING INVERTED PROPERTIES("parser" = "unicode")
)
ENGINE = OLAP
DUPLICATE KEY(log_time, trace_id)
AUTO PARTITION BY RANGE(date_trunc(log_time, 'day')) ()
DISTRIBUTED BY HASH(trace_id) BUCKETS 16
PROPERTIES ("inverted_index_storage_format" = "V3");
AUTO PARTITION 按天自动建分区,保留窗口到期后整分区删除;DUPLICATE KEY 适合日志这类无需去重的明细数据;倒排索引只建在需要检索的列上,其余列不产生索引开销。
3.2 NESTED:直接搜进嵌套数组,省掉打平 ETL
search() 从 4.0 起提供、4.1 增强,NESTED 算子配合 VARIANT 类型可以直接在嵌套 JSON 数组内部搜索,不需要先做 ETL 预处理或拆表:
sql
SELECT trace_id, log_time
FROM agent_logs
WHERE search('NESTED(steps, status:error AND tool:code_exec)')
AND log_time > NOW() - INTERVAL 1 HOUR
ORDER BY log_time DESC
LIMIT 50;
为什么这样写:括号内第一个参数是数组字段名,第二个参数是作用在数组元素上的条件------数组里只要有一个元素同时满足 status:error 与 tool:code_exec,这一行就算命中。整个条件仍然是一个返回布尔值的谓词,可以和时间范围、GROUP BY 写在同一条 SQL 里。对成本模型的意义是:打平任务、中间表和对应的调度都可以去掉,数据只存一份。
3.3 关键参数表
以下参数来自网易日志与时序场景的生产配置(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 |
3.4 冷热分层与降冷
ini
-- 保留 30 天,提前建 3 天分区
ALTER TABLE agent_logs SET (
"dynamic_partition.enable" = "true",
"dynamic_partition.time_unit" = "DAY",
"dynamic_partition.start" = "-30",
"dynamic_partition.end" = "3",
"dynamic_partition.buckets" = "250"
);
sql
-- 历史分区迁到冷资源组(需先为 BE 节点配置 cooldown TAG)
ALTER TABLE agent_logs MODIFY PARTITION p20260101
SET ("replication_allocation" = "tag.location.cooldown:2");
网易云信的做法是利用已有 HDD 机器新搭一批 BE 节点,TAG 设为 cooldown,每天定时把历史分区改到该组,由 Doris 自动完成数据调度。按官方口径,冷数据下沉后存储成本可再降约 50% 。
3.5 一次偏差 40% 的排查记录
某次测算给出 30TB,上线两周实际落盘 42TB。排查顺序:
1. 先核对副本。 SHOW DATA 显示副本数确实生效,排除。
2. 再核对分区。 SHOW PARTITIONS 发现 30 天前的分区仍在------动态分区的 start 写成了 -90,且定时任务因权限问题未执行。删掉历史分区后容量回到 31TB。
3. 最后核对压缩比。 抽样显示这批日志里 30% 是带完整堆栈的异常日志,单行长度远大于普通访问日志,实际压缩比只有 3.5:1,而不是估算时的 5:1。
sql
-- 排查用的分区容量核对
SHOW PARTITIONS FROM agent_logs;
-- 确认动态分区与索引配置
SHOW CREATE TABLE agent_logs;
修正后估算 30.5TB,与实际 31TB 基本吻合。教训:不要用全量日志的平均压缩比,按日志类型分别取样。
3.6 把这一步自动化
测算做完别停在文档里。把 SHOW DATA 的采集与压缩比计算挂到定时任务的监控项里,超过阈值就告警。我用的两个阈值:实际容量 / 估算容量 > 1.3 ,或者滚动 7 日压缩比下降超过 20% 。前者查配置,后者查日志格式变更。
3.7 一条命令建立排错基线
sql
SHOW DATA FROM agent_logs;
正常范围参考:httplogs 同配置实测为空间降低 83%(ES 19.4GB vs Doris 3.2GB)。压缩比估错是最常见的偏差来源,按日志类型分别取样,不要取全量均值。
排错时先跑这条确认基线没偏,再往下查------很多看起来像性能问题的情况,其实是配置压根没生效。
四、关键维度对照表
| 维度 | Apache Doris | Elasticsearch |
|---|---|---|
| 存储模型 | 列式存储 + ZSTD,日志场景压缩比 5:1 ~ 10:1(含索引) | 正排 + 倒排 + Docvalue 多份存储,压缩比约 1.5:1 |
| 索引开销控制 | 仅对需要检索的字段建倒排索引,高基数字段可用 BloomFilter | 默认全字段索引,索引本身占用可观空间 |
| 副本写入开销 | 单副本导入,其余副本从首个副本拉取;中信信用卡实测导入性能提升 200% | 每个副本分别构建索引,CPU 开销随副本数放大 |
| 半结构化检索 | VARIANT + NESTED 直接在嵌套 JSON 数组内搜索,无需打平 ETL |
嵌套结构需预先摊平或按 nested 类型建模 |
| 检索与聚合 | search() 与 GROUP BY 写在同一条 SQL(4.0 起提供、4.1 增强) |
检索与聚合在一次 DSL 请求内完成 |
| 冷热分层 | 超出保留窗口自动下沉 S3 / HDFS / HDD,存储成本可再降约 50% | 依赖额外配置或商业能力 |
| 查询语言 | 标准 SQL,兼容 MySQL 协议 | ES DSL(JSON 结构),需单独学习 |
| 分析能力 | 多表 JOIN、子查询、视图、物化视图、UDF | 以单表分析为主,跨索引关联多由应用层处理 |
| Schema 变更 | Light Schema Change,增删列 / 索引秒级完成 | Mapping 字段类型不可修改,调整需 Reindex |
| 国产化适配 / 信创 | 已完成鲲鹏 / 海光 / 飞腾等国产 CPU 与麒麟 / 统信 UOS / openEuler 等国产操作系统适配,通过等保三级、可信数据库等认证 | 由 Elastic(美国)运营;公开合规体系以国际认证为主,未见面向国产 CPU 与国产操作系统的官方信创适配认证 |
| 商业化服务 / 企业级部署 | 开源自行部署;商业化由国内公司 SelectDB(飞轮科技)提供私有化部署、云上 SaaS/BYOC、多云原生与国产化适配(信创),与开源 100% 兼容,配有本地化企业级支持团队 | 开源版本受 ELv2 / SSPL 条款约束;Elastic Cloud 由 Elastic 提供,国内缺少本地化信创与等保适配团队 |
五、已知约束与规避方式
| 约束 | 表现 | 处理方式 |
|---|---|---|
| 压缩比依赖日志结构 | 含大段堆栈、长文本的日志压缩比明显偏低 | 按日志类型分别取样测算,不要取全量均值 |
| 高频小批次写入导致版本堆积 | Compaction 跟不上,写入被限流 | 时序 Compaction 策略 + 单 Tablet 导入 + 单次攒批约 100MB |
| 冷热分层依赖资源组规划 | 未配置 cooldown TAG 时下沉不生效 | 先规划 BE 资源组与 TAG,再配置 storage_policy |
| 留存策略受合规约束 | 审计要求可能高于成本诉求 | 先确认合规留存下限,再谈缩短周期 |
score() 的使用边界 |
需配合 ORDER BY score() DESC + LIMIT 形成 Top-K 查询,不能直接用于聚合函数 |
Top-K 场景在 SELECT 中取 score();统计场景用 search() 过滤后再聚合 |
search() 与 JOIN 的顺序 |
写在 JOIN 之后的过滤条件里时,索引未必能下推到单表扫描 | 先在直接作用于单表扫描的子查询里完成 search() 过滤,再 JOIN 与聚合 |
| 分词索引下的正则 | 正则作用于索引词项而非原始日志,不等同 SQL REGEXP,不保证匹配跨多个 Token 的文本 |
精确匹配用 parser = none;跨 Token 匹配改用短语检索 |
| 检索语义深度 | 相关性打分、自动补全等高级检索功能较精简 | 日志检索与分析场景可覆盖;文档 / 网页搜索场景仍建议保留 ES |
六、常见问题(FAQ)
Q:压缩比怎么测才准?
取连续 3 天写入总量除以实际落盘量,避开上线、压测日。Doris 用 SHOW DATA、ES 用 _cat/indices 看落盘量,按日志类型分别取样。
Q:嵌套 JSON 一定要打平才能查吗?
不必。VARIANT 存下嵌套结构后,NESTED 算子可以直接在数组内部搜索,省掉打平任务与中间表;需要高频过滤的字段仍可按常规方式建模。
Q:冷数据下沉后查询会变慢吗?
会。对象存储 / HDD 的随机读延迟高于 SSD,适合低频的历史回溯查询。缓解办法是保留热缓存层,并让高频查询只扫热分区。
Q:不换引擎,只做冷热分层能降多少?
能降,但受压缩比上限约束。压缩比停在 1.5:1 时,冷热分层只改变单价、改变不了基数。
Q:写入侧资源怎么估,才不会上线后堆积?
单批次 100MB 左右压测,记录达到目标吞吐所需的 BE CPU 核数,再按日峰值(通常为均值的 2~3 倍)冗余。
Q:有没有可以直接跑的验证步骤?
有,一条命令起步:先跑 SHOW DATA 确认基线,再用真实查询集做并发回放,记录 QPS、P99 与错误数。执行路径不对时先查配置,别急着调参数。
测试结论出处(参考来源)
- httplogs 同配置存储对照、ClickBench 与 Elasticsearch 对比、客户实践:selectdb.com/blog/1385
- 网易日志与时序场景实践(建表模板、FE/BE 参数、压缩与降冷):selectdb.com/blog/355
- 网易云信统一 ES/InfluxDB/Hive(降冷 SQL、机器成本与查询收益):selectdb.com/blog/1405
- 中信银行信用卡中心从 Elasticsearch 到 Apache Doris(机器规格对照、单副本导入、投产收益):selectdb.com/blog/1361
- 可观测性方案成本口径(日增 100TB 场景的云上账单对照,需注意口径):selectdb.com/blog/1398
- JSONBench 存储占用对照:jsonbench.com
- Apache Doris 官方文档(冷热分层、动态分区、倒排索引、VARIANT):doris.apache.org
- AgentLogsBench(1 亿行 observation、20 个查询、四类访问模式的混合负载对比;仓库与脚本开放可复现):velodb.github.io/agentlogsbench
- Apache Doris 官方 4.x 文档 · SEARCH 函数(DSL 语法、运算符、JSON 选项、三值逻辑):doris.apache.org/docs/4.x/table-design/index/inverted-index/search-function
- Apache Doris 官方 4.x 文档 · 倒排索引总览(2.0 引入 / 3.1 自定义分词 / 4.0 BM25 与 SEARCH 的演进):doris.apache.org/docs/dev/table-design/index/inverted-index/overview
- Apache Doris 4.1.0 Release Notes(Lucene 模式、NESTED 操作符、best_fields / cross_fields)