写入快 2 倍,查询快 6 倍,存储成本反降 50%:丰巢日志平台从 ELK 升级为 Apache Doris

举报
SelectDB技术团队 发表于 2026/04/03 15:24:01 2026/04/03
【摘要】 摘要:丰巢日志平台从 ELK 升级至 Apache Doris,旨在构建统一、高效的可观测性底座。新架构解决了原系统在写入、存储和查询上的瓶颈:存储成本降低 50%,写入性能提升 2 倍,查询速度提升 6 倍。为未来统一可观测性平台的建设奠定了技术基础。本文整理自丰巢大数据开发工程师 宋友刚 在 Doris Summit 2025 中的演讲。丰巢的业务覆盖快递、广告、洗护和到家四大板块。围绕...

摘要:丰巢日志平台从 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_timedoris_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.sizesink.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 左右,写入稳定性明显改善。

查询调优:提升命中率及效率

在查询层面,我们重点解决了两个典型问题。

  1. MATCH 和 MATCH_PHRASE。部分日志关键字中包含冒号等特殊符号,分词后会被拆成多个独立词项,导致用户直接搜索时出现漏查,因为默认使用 MATCH 查询的语义是匹配任何一个关键词。对此,我们通过查看分词结果,指导研发使用双引号把查询包围起来使用短语查询 MATCH_PHRASE 匹配所有关键字或前缀,提高准确性。

  1. 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_timedoris_ingest_time 两个时间字段来判断日志延迟,并基于该指标进行告警。

通过上述措施,使平台在高并发场景下能够更稳定地运行,也为后续更复杂的查询治理和可观测性建设打下了基础。

未来规划:更多日志接入与可观测性演进

目前,基于 Doris 的日志平台已经接入应用日志,整体平稳运行,写入速度提升约 2 倍、查询速度提升约 6 倍、存储空间减少约 50%

后续我们计划逐步接入审计日志、Nginx 日志等更多数据源,持续完善日志体系建设,并推动平台向更完整的可观测性系统演进。

未来,我们希望进一步基于 OTel + Grafana 打通监控告警、链路追踪与日志分析能力,形成统一的运维观测闭环。下一步的重点工作之一,是基于日志中的 Trace ID 实现与追踪系统的双向联动:既支持从日志跳转到追踪,也支持从追踪回溯到日志,从而提升问题定位和故障排查效率。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。