摘要:丰巢日志平台从 ELK 升级至 Apache Doris,旨在构建统一、高效的可观测性底座。新架构解决了原系统在写入、存储和查询上的瓶颈:存储成本降低 50%,写入性能提升 2 倍,查询速度提升 6 倍。为未来统一可观测性平台的建设奠定了技术基础。
本文整理自丰巢大数据开发工程师 宋友刚 在 Doris Summit 2025 中的演讲。
丰巢的业务覆盖快递、广告、洗护和到家四大板块。围绕这些核心业务,上层运行着大量应用系统,每天持续产生海量日志。当前日志写入规模已超过 100 万/秒、40TB/天,这对日志平台的实时写入、稳定查询和存储成本都提出了极高要求。
原 ELK 架构:实时性下降、成本压力攀升
原有日志平台基于典型的 ELK 架构构建。这套系统虽然具备较强的检索能力,但在高并发和大规模数据写入场景下,逐渐暴露出一系列结构性问题,已经难以支撑当前业务需求。
这些问题主要体现在以下几个方面:
- 实时性明显下降。在业务高峰期,日志延迟一度超过 1 小时,导致依赖日志的关键场景,如线上问题排查、错误监控、敏感日志检查以及异常数据补发几乎失效,严重影响了系统可观测性和运维效率。
- 资源压力持续攀升。在高峰写入阶段,集群 CPU 负载常常飙升至 200–300,系统整体响应能力明显下降,查询请求频繁出现超时甚至无响应的情况。
- 存储成本不断扩大。当前日志数据每天占用约 40TB 存储空间,在规模持续增长的背景下,这一成本已成为不可忽视的负担。
- 多维分析能力受限。在业务逐渐复杂化的过程中,日志分析需求也从简单检索演进为多维度分析。但在部分需要多表关联(Join) 的场景下,Elasticsearch 本身的能力存在明显限制,难以满足实际需求。
Elasticsearch 遇到 Scale 瓶颈
在原有架构中,使用的是一套规模较大的 Elasticsearch 集群:由 13 台 96C 256G 节点和 5 台 48C 128G 节点组成的异构集群。日志每天磁盘占用约 40TB,写入峰值约为 80 万条/秒。
为缓解写入压力,我们新增采购了 10 台配置为 112C 512G 的高性能机器,并基于这批机器搭建了一套新的 Elasticsearch 集群进行测试。测试结果显示:
- 存储占用与旧集群基本持平(无提升)
- 写入峰值提升至约 100 万条/秒(+ 20%)
但当前业务高峰期日志写入已达到 120 万条/秒,仍存在明显缺口,继续扩容无法从根本解决问题。
Doris vs. Elasticsearch:性能与成本的真实对比
团队之所以开始关注 Doris 的重要原因是:公司本身已经有业务集群在使用 Apache Doris。这意味着团队对 Doris 并不陌生,也有机会更低成本地评估其在日志分析场景下的适配性。随后,我们在同样的硬件条件下搭建了 Doris 集群进行对比测试。结果如下:
- 存储占用约 18TB/天,比 Elasticsearch 少了 50%
- 写入峰值达到 200 万条/秒,比Elasticsearch 快了 1 倍
可以看到,在写入能力和存储成本两个关键指标上,Doris 均显著优于 Elasticsearch。
在查询性能方面,由于当时的核心目标是优先解决写入瓶颈,我们选取了几个典型高频场景进行验证:基于关键字检索最近一小时、最近三小时和最近一天日志。测试共执行 5 次并取平均值,用于评估两者在基础查询场景下的表现结果显示,Doris 在典型场景下整体优于 Elasticsearch,能够更快返回查询结果。
结合测试结果与实际使用体验,我们总结了 Doris 在日志分析场景中的几项核心优势:
- 写入性能更强:Doris 支持 GB/s 高吞吐数据写入,能够有效支撑高峰期日志流量,保障数据的实时性与稳定性。
- 存储成本更低:在相同数据规模下,存储占用仅为 Elasticsearch 的一半,这对大规模日志的存储很重要。
- 更强检索分析能力:通过倒排索引与全文检索能力,Doris 覆盖大多数日志查询需求,常见查询可实现秒级响应。Doris 原生支持 Join、聚合和子查询,能够更好地支撑复杂日志分析场景。
- 资源隔离与权限控制:日志场景的写入优先级高于查询。Doris 支持对 CPU、内存及 IO 进行资源限制,有效避免查询任务影响写入稳定性。
- 简洁易用:借助 SelectDB Studio,用户可以通过 SQL 或可视化方式进行检索,整体使用体验更加统一和便捷。借助 Doris Manager,集群搭建可在一小时内完成,后续配置调整与运维操作也更加高效,显著降低运维复杂度。
基于 Apache Doris 的日志平台架构
综合上述对比及测试结果,决定引入 Apache Doris 替换之前架构中的 ELK。基于 Doris 提供日志的统一采集、清洗、计算、存储、检索、监控和分析等多项服务,在整体架构上,保留了原有的日志采集链路,即 Filebeat → Kafka,重点对后半段进行了重构。新的架构调整为:
- 使用 Flink 替代 Logstash,负责日志数据的处理与写入
- 使用 Doris 替代 Elasticsearch,作为核心存储与分析引擎
- 使用 SelectDB Studio 替代 Kibana,作为统一查询入口

新架构上线后,在写入性能、存储成本和查询效率等方面均取得了明显提升。**写入速度提升约 2 倍、查询速度提升约 6 倍、存储空间减少约 50% **。
为什么没保留 Logstash?
在写入链路上,我们最初也尝试过使用 Logstash 将日志写入 Doris,但在高并发场景下稳定性不够理想,频繁出现 OOM。考虑到已有业务通过 Flink 写入 Doris 且运行稳定,我们随后对 Flink → Doris 的链路进行了验证,最终确认该方案在稳定性和可控性上更适合日志场景。
Flink-Doris-Connector 支持流式和批量两种写入方式。结合日志场景的特点,选择了批量写入模式。相比依赖 Checkpoint 的流式写入,批量写入更便于控制写入频率,在稳定性和吞吐之间更容易取得平衡。
关键调优
表结构的设计:更高性能的基础
在表结构设计上,我们重点考虑了分区、分桶、索引和压缩策略,因为这些设置会直接影响写入稳定性、查询性能以及存储成本。
1、分区策略:按小时分区
分区粒度需要结合实际数据规模来确定。以当前每天约 18TB 的数据量来看,如果按天分区,分桶数可能达到数千个,管理和写入压力都会非常大。因此,我们最终选择了按小时分区,以降低单分区压力,更适合日志这种高频写入场景。
2、分桶策略:使用 RANDOM 分桶
在分桶方式上,我们采用了 RANDOM 分桶,并将单桶大小控制在 5GB 左右。 Doris 针对日志场景优化的一种方式是单 Tablet 导入,有助于减少导入开销并提升写入效率。
3、索引设计:按查询模式建立倒排索引
索引方面,我们根据实际查询场景对关键字段建立了倒排索引。对于需要全文检索的字段,开启分词;对于仅用于等值查询的字段,则不启用分词,以避免额外开销。
4、链路延迟监控:增加写入时间字段
为了更准确地监控日志链路延迟,我们额外增加了 doris_ingest_time 字段,用于记录日志写入 Doris 的时间。通过对比 log_time 和 doris_ingest_time,可以直观看到日志从产生到入库的延迟情况。
5、压缩策略:使用 ZSTD
在压缩算法上,我们采用了 ZSTD,在保证查询性能的同时,有效降低了日志存储空间占用。
示例表结构如下:
CREATE TABLE `applogs_all` (
`log_time` datetime(3) NOT NULL COMMENT "日志时间,精确到毫秒",
`app_name` varchar(256) COMMENT "应用名称",
`service_name` varchar(256) COMMENT "服务名称",
`log_level` varchar(10) COMMENT "日志级别",
`log_content` text COMMENT "日志正文内容",
`trace_id` varchar(256) COMMENT "全局追踪ID",
`thread_name` varchar(256) COMMENT "线程名称",
`bl_no` tinyint COMMENT "业务线编号",
`doris_ingest_time` datetime(3) DEFAULT CURRENT_TIMESTAMP(3) COMMENT "数据入库时间",
INDEX idx_log_content (`log_content`) USING INVERTED PROPERTIES("parser" = "unicode"),
INDEX idx_trace_id (`trace_id`) USING INVERTED
)
DUPLICATE KEY(`log_time`,`app_name`)
PARTITION BY RANGE(`log_time`)()
DISTRIBUTED BY RANDOM BUCKETS 180
PROPERTIES (
"compression"="zstd"
);
写入链路调优:提升吞吐量及稳定性
在日志场景下,系统优化的首要目标始终是保障写入稳定。因此,我们将优化重点放在写入链路和 Compaction 控制上。
在 Flink SQL 作业侧,我们主要做了三项优化:
- 开启批量写入模式,通过
sink.batch.size和sink.batch.interval控制批次大小与写入频率 - 开启单 Tablet 导入,减少导入文件数和写入开销
- 将 Flink Task 并行度与 Kafka 分区数尽量保持 1:1,提升上下游资源利用率
在 Compaction 侧,我们主要围绕三个方向进行控制:
- 控制单盘 Rowset 数量。开启单 Tablet 导入后,每次导入仅生成副本数个 Rowset,有助于降低单盘压力。
- 减少同时写入的分区数。日志延迟会导致多个小时分区并发写入,从而显著增加 Compaction 压力。我们曾观察到 Compaction Score 超过 2000,远高于官方建议的约 100。
- 调整分桶数。高峰期每小时数据量约 1.5TB,最初分桶偏多,导致桶过小、Compaction 频繁。经过多轮调整,分桶数从 360 下调到 240,最终稳定在 180。优化后,Compaction Score 降至 280 左右,写入稳定性明显改善。
查询调优:提升命中率及效率
在查询层面,我们重点解决了两个典型问题。
- MATCH 和 MATCH_PHRASE。部分日志关键字中包含冒号等特殊符号,分词后会被拆成多个独立词项,导致用户直接搜索时出现漏查,因为默认使用
MATCH查询的语义是匹配任何一个关键词。对此,我们通过查看分词结果,指导研发使用双引号把查询包围起来使用短语查询MATCH_PHRASE匹配所有关键字或前缀,提高准确性。

- MATCH 与 LIKE 的选择。在日志场景下,
MATCH依赖分词匹配,而LIKE直接进行子串匹配。对于"clientMobile"、"smartphone"这类字段,LIKE往往比MATCH召回更多。因此,在实际使用中,我们并未机械依赖倒排索引,而是根据字段特征和查询目的选择更合适的查询方式。

稳定性保障
主要从资源隔离、查询治理、权限控制和监控告警四个方面做了稳定性保障。
资源隔离
在资源隔离上,Doris 提供了 Resource Group、Workload Group 等多种机制。结合现阶段的使用情况,我们主要基于 Workload Group 对查询资源进行隔离和限制,重点是避免查询任务挤占写入资源。
具体策略上,内存采用软限制,因为当前整体内存相对充足;CPU 采用硬限制,并将查询与写入资源按 1:1 进行分配,以确保高峰期写入的稳定性。IO 原本也尝试过进行硬限制,但实际观察发现,限制后查询性能下降较为明显,而当前 IO 尚未成为主要瓶颈,因此暂时放开,后续再根据运行情况持续评估。
CREATE WORKLOAD GROUP IF NOT EXISTS wg_applogs_select PROPERTIES (
"memory_limit"="50%",
"enable_memory_overcommit"="true",
"cpu_hard_limit"="50%",
"read_bytes_per_second" = "104857600"
...
);
大查询拦截
实际使用中,经常会出现用户写 SQL 时遗漏时间范围或过滤条件的情况,这类查询很容易演变为全表扫描的大查询,不仅影响执行效率,也会拖慢其他正常查询,影响整体使用体验。为此,我们增加了大查询拦截机制,优先在查询规划阶段进行识别和拦截,尽量将风险控制在执行之前。运行时拦截后续会继续完善。
-- 拦截无where条件的查询
CREATE SQL_BLOCK_RULE block_no_wherePROPERTIES (
"sql" = "(?i)^.*\\bFROM\\s+\\bapplogs_all\\s+((?!WHERE).)*$",
...
);
-- 拦截没有指定log_time的SQL
CREATE SQL_BLOCK_RULE block_no_log_time PROPERTIES (
"sql"="(?i)^.*`?\\bapplogs_all\\b`?\\s+\\bWHERE\\b(?!.*\\blog_time\\s*[><=]|.*\\blog_time\\s+BETWEEN.*AND.*).*$",
...
);
-- 拦截没有指定app_name的查询
CREATE SQL_BLOCK_RULE block_no_app_name PROPERTIES (
"sql"="(?i)^.*`?\\bapplogs_all\\b`?\\s+\\bWHERE\\b(?!.*\\bapp_name\\s*(=|in\\s*\\()).*$",
...
);
权限控制
在权限控制方面,考虑到 Doris 支持行级权限控制,选择在日志表中增加“业务线”字段,并在 Flink 写入时通过关联维表补齐该字段,再基于该字段实现按业务线的访问控制。这样既能满足隔离需求,也避免了多表管理的复杂度。查询时,单业务线场景可直接使用等值过滤,跨业务线场景则可以通过 IN 方式实现。
-- 单个业务线
CREATE ROW POLICY row_policy_test ON applogs_all AS PERMISSIVE TO ROLE applogs_test_role USING (bl_no = 1);
-- 跨业务线
CREATE ROW POLICY row_policy_test2 ON applogs_all AS PERMISSIVE TO ROLE applogs_test_role2 USING (bl_no in (1,2));
监控告警
在监控告警方面,我们重点关注两类问题:Kafka 消费积压 和 日志链路延迟。
对于前者,主要通过监控 Kafka 的消费堆积情况,在超过阈值时触发企微告警,便于及时发现消费异常。
对于后者,曾遇最新日志无法查询,但日志文件实际存在,Kafka 侧也没有明显积压。进一步通过 show partitions 排查后发现,同一时间存在写入多个分区的情况,因此怀疑问题出在生产端延迟。针对这一情况,我们通过对比 log_time 与 doris_ingest_time 两个时间字段来判断日志延迟,并基于该指标进行告警。
通过上述措施,使平台在高并发场景下能够更稳定地运行,也为后续更复杂的查询治理和可观测性建设打下了基础。
未来规划:更多日志接入与可观测性演进
目前,基于 Doris 的日志平台已经接入应用日志,整体平稳运行,写入速度提升约 2 倍、查询速度提升约 6 倍、存储空间减少约 50%。
后续我们计划逐步接入审计日志、Nginx 日志等更多数据源,持续完善日志体系建设,并推动平台向更完整的可观测性系统演进。
未来,我们希望进一步基于 OTel + Grafana 打通监控告警、链路追踪与日志分析能力,形成统一的运维观测闭环。下一步的重点工作之一,是基于日志中的 Trace ID 实现与追踪系统的双向联动:既支持从日志跳转到追踪,也支持从追踪回溯到日志,从而提升问题定位和故障排查效率。
