ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Apache Doris 在可观测性场景下的性能优势与实战调优

Apache Doris 在可观测性场景下的性能优势与实战调优

1. 项目概述:当可观测性数据遇上实时分析引擎

最近在搞可观测性平台的数据层选型,团队里几个兄弟为了日志和指标数据的存储分析方案吵得不可开交。Elasticsearch 是老牌选手,查询灵活但面对海量写入和复杂聚合时,资源开销和成本让人头疼;ClickHouse 在分析性能上是个狠角色,但它的数据更新和实时关联查询能力,在处理 Agent 上报的、带状态的复杂日志流时,总感觉有点“水土不服”。就在我们纠结的时候,一个内部压测项目的结果引起了我的注意:在模拟真实 Agent 可观测性数据(我们内部称之为 AgentLogsBench)的生产负载测试中,Apache Doris 的表现相当抢眼,不仅在吞吐和延迟上领先,其架构特性与可观测性场景的需求契合度之高,让我觉得有必要深入聊聊。

简单来说,这个测试的核心就是用一个接近真实生产环境的基准,去“折腾”各个分析型数据库,看谁能更好地承接来自成千上万台服务器上 Agent 实时上报的日志、指标、链路数据,并支撑业务人员即席的、多维的交互式分析。结果,Apache Doris 跑出来了。这不仅仅是一个性能数字的胜利,更深层次的原因是,Doris 的某些设计哲学,恰好命中了现代可观测性数据处理的几个关键痛点:高并发实时写入、宽表与多表关联分析并存、对新鲜数据的极低延迟查询需求,以及在大规模部署下对运维成本的控制。

如果你也在为微服务或云原生环境下的可观测性数据栈选型,或者正在被 Elasticsearch 集群的扩容成本和 ClickHouse 的查询复杂度困扰,那么这次围绕 AgentLogsBench 的实践与发现,或许能给你提供一个值得认真考虑的新选项。接下来,我会结合测试中的发现和我们自己的评估实践,拆解 Doris 为何能在这个场景下表现突出,以及在实际落地时你需要关注的那些细节和“坑”。

2. 可观测性数据负载的独特挑战与测试基准设计

在深入技术细节之前,我们得先搞清楚,来自 Agent 的可观测性数据负载,到底给数据库提出了哪些非常规的挑战。这决定了为什么一个通用的 OLAP 引擎可能不够,而需要针对性的优化。

2.1 Agent 数据流的四大核心特征

首先,Agent(如 Prometheus Node Exporter, OpenTelemetry Collector, 或各类商业 Agent)产生的数据流有其鲜明特点:

  1. 高吞吐、流式写入:成千上万的服务器、容器实例持续不断地产生日志和指标。数据是 7x24 小时不间断的流,写入压力稳定且巨大,要求数据库具备极高的写入吞吐能力,并且写入不能阻塞或严重影响并发查询。
  2. 数据热度过山车:新产生的数据(比如最近 5 分钟)被查询的概率极高,用于实时告警和故障排查。随着时间的推移,数据的查询频率急剧下降,但基于合规或历史分析需求,又需要长期保存。这要求存储系统具备高效的热冷数据分层管理能力。
  3. schema 灵活与半结构化:日志数据往往是半结构化的 JSON 或文本,字段可能动态增加。指标数据虽然结构相对固定,但维度(标签)组合可能非常丰富。这要求系统既能处理灵活的 schema,又能对结构化部分进行高效的列式存储和编码。
  4. 复杂的查询模式:查询很少是简单的点查。它通常是多维的:时间范围过滤是必选项,然后结合多个标签(如service=‘order’,host=‘192.168.1.1’,level=‘ERROR’)进行筛选、分组聚合(如按错误类型统计次数)、排序(如找出耗时最长的 API 调用),有时还需要进行多表关联(如将链路 Trace 数据与错误日志关联)。

2.2 AgentLogsBench 基准测试解析

为了公平地衡量不同系统,我们设计的 AgentLogsBench 基准主要模拟了以下负载:

  • 数据模型:包含两张核心表。一张是agent_logs宽表,模拟典型的应用日志,包含时间戳、服务名、主机 IP、日志级别、线程 ID、动态的 JSON 字段(存放具体日志内容),以及从 JSON 中提取的常用字段(如error_code,duration_ms)。另一张是agent_metrics,模拟指标数据,包含时间戳、指标名、多组维度标签(Key-Value 对)和数值。
  • 写入负载:使用多个写入客户端,模拟数百个 Agent 实例以恒定速率向系统写入数据。重点考察系统的峰值写入吞吐(MB/s 或 条/秒)、写入稳定性(延迟是否抖动)以及写入对集群资源的消耗。
  • 查询负载:设计了一套混合查询模板(Query Template),包括:
    • 近期数据高频点查:查询特定服务、主机在最近 1 分钟内的错误日志。
    • 时间范围扫描与聚合:统计过去 1 小时内,各服务的错误日志总数、平均响应耗时。
    • 多维度下钻分析:按服务、主机、错误代码等多个维度进行分组、排序和分页查询。
    • 宽表与 JSON 字段查询:对动态 JSON 字段中的特定属性进行过滤和提取。
    • 表关联查询:将agent_logs中的异常请求与agent_metrics中同一时间段的系统资源指标(如 CPU 使用率)进行关联分析。
  • 评价指标:核心看三点:查询响应时间(P99 Latency)系统吞吐(QPS)以及资源利用率(CPU/Memory/Disk IO)。目标是追求在有限的硬件资源下,获得更低的查询延迟和更高的并发处理能力。

这个基准的意义在于,它不是一个单纯的“跑分”工具,而是将可观测性场景下的典型压力,抽象成可重复的测试用例,从而更真实地反映引擎在生产环境中的潜力。

3. Apache Doris 的核心架构优势解读

为什么 Doris 能在这样的测试中表现突出?这需要从其架构设计上找原因。Doris 是一个 MPP(大规模并行处理)架构的、基于 MySQL 协议的实时分析数据库。它在设计之初就融合了 Google Mesa 和 Apache Impala 的思想,针对现代实时分析场景做了大量优化。

3.1 高并发写入与数据组织

面对 Agent 数据流的高并发写入,Doris 的解决方案非常直接有效。

写入链路优化:Doris 的写入分为几个阶段。数据首先通过 FE(Frontend)进行协议解析和路由,然后被分发到对应的 BE(Backend)节点。BE 节点在内存中会维护一个 MemTable 用于快速接收数据,并定期或根据大小阈值 Flush 成不可变的磁盘文件(Segment)。这个过程类似于 LSM-Tree,但做了大量简化。最关键的是,Doris 支持Batch DeleteUpsert语义,这对于修正或删除 Agent 误报的数据非常有用,而这是 ClickHouse 的 MergeTree 引擎相对棘手的部分。

数据分区与分桶(Partition & Bucket):这是 Doris 数据组织的精髓。对于agent_logs表,我们通常会按时间进行分区(例如按天分区PARTITION BY RANGE(dt))。这样做有两个巨大好处:第一,对于时间范围的查询,可以快速定位到相关分区,避免全表扫描;第二,便于管理数据生命周期,可以轻松地删除或归档历史分区(如保留 30 天)。在每个分区内,数据还会通过DISTRIBUTED BY HASH语句进行分桶,将数据打散到多个 BE 节点上。合理的分桶键选择(例如service_name, host_ip)可以确保数据均匀分布,并且让涉及这些键的查询能够进行本地化计算,减少网络 Shuffle,极大提升聚合和过滤性能。

实操心得:分桶键的选择是性能关键分桶键的选择需要权衡数据均匀性和查询模式。如果只按host_ip分桶,当查询只按service_name过滤时,就需要跨桶进行数据聚合。一个常见的做法是选择 2-3 个最高频的过滤或分组字段作为组合分桶键。在我们的场景中,(service_name, host_ip)是一个不错的选择。分桶数量建议控制在每个 BE 节点 10-100 个之间,太少会导致并行度不够,太多会增加元数据开销。

3.2 向量化执行引擎与物化视图

这是 Doris 查询速度快的“秘密武器”。

向量化执行引擎:传统的数据库执行引擎是逐行处理(Row-by-Row),每次操作都要处理大量的函数调用和条件判断开销。Doris 的向量化引擎则将数据按列组织成批(Batch),在 CPU 的缓存中进行连续操作。对于分析型查询中大量存在的扫描、过滤、聚合操作,这种模式能极大地利用现代 CPU 的 SIMD(单指令多数据)指令集,实现成倍的性能提升。在 AgentLogsBench 的聚合查询中,向量化引擎的优势被充分释放。

物化视图(Materialized View):这是应对可观测性场景中“固定模式分析”的利器。例如,业务经常需要查询“过去5分钟各服务的错误率”。如果每次都对原始亿级日志表进行GROUP BY计算,代价很高。我们可以在agent_logs表上创建一个物化视图,预先按照dt(天)、service_namelevel进行聚合,计算好countsum(duration_ms)。当用户查询匹配这个聚合模式时,Doris 的查询优化器会自动路由到物化视图上读取已经计算好的聚合结果,查询速度可以从秒级提升到毫秒级。Doris 的物化视图是自动增量更新的,对用户透明,维护成本很低。

-- 创建物化视图的示例 CREATE MATERIALIZED VIEW service_error_stats AS SELECT dt, service_name, level, count(*) as log_count, sum(duration_ms) as total_duration FROM agent_logs GROUP BY dt, service_name, level;

3.3 灵活的索引与数据湖分析

前缀索引与 Bloom Filter:Doris 的表数据存储在 Segment 文件中,每个 Segment 会为前 36 个字节自动生成一个前缀索引。如果查询条件包含这些列,可以快速定位数据块。对于其他列和等值查询,可以创建Bloom Filter 索引,它能以极小的空间代价,快速判断一个数据块中是否包含某个值,从而在扫描时跳过大量不相关的数据块。对于agent_logs表中的host_ip,trace_id这类高基数列的等值查询,Bloom Filter 效果显著。

与数据湖的联邦查询:可观测性数据并非全部需要实时分析,大量冷数据可以存储在更便宜的对象存储(如 S3、OSS)中。Doris 支持通过Multi-Catalog功能,直接对接 Hive、Iceberg、Hudi 等数据湖格式,或者通过 JDBC 连接外部数据库。这意味着你可以制定这样的策略:最近 7 天的热数据存放在 Doris 本地 SSD 上,提供极致查询体验;7 天到 1 年的温数据存放在对象存储(如通过 Apache Iceberg 管理),Doris 可以联邦查询;1 年以上的数据归档。这样实现了成本与性能的最佳平衡,这个架构与可观测性数据的热度衰减特性完美匹配。

4. 基于 AgentLogsBench 的实战配置与调优

纸上得来终觉浅,我们来看看在 Doris 上具体如何搭建一个能扛住 AgentLogsBench 测试的集群和表结构。

4.1 集群部署与表结构设计

假设我们有一个中等规模的集群:3个 FE(1个 Leader,2个 Follower)用于高可用和元数据管理,6个 BE 用于数据存储和计算。所有节点混合部署,磁盘使用 SSD。

建表示例是重中之重:

-- 创建日志宽表 CREATE TABLE agent_logs ( `ts` datetimev3 NOT NULL COMMENT "日志时间戳", `service_name` varchar(50) NOT NULL COMMENT "服务名", `host_ip` varchar(15) NOT NULL COMMENT "主机IP", `pod_name` varchar(100) COMMENT "Pod名称", `log_level` varchar(10) NOT NULL COMMENT "日志级别", `thread_id` int COMMENT "线程ID", `raw_json` string COMMENT "原始JSON日志", `extracted_error_code` varchar(20) COMMENT "从JSON提取的错误码", `extracted_duration_ms` int COMMENT "从JSON提取的耗时(ms)", `dt` date NOT NULL COMMENT "日期分区列,由 ts 生成" ) ENGINE=OLAP DUPLICATE KEY(`ts`, `service_name`, `host_ip`, `log_level`) -- 重复模型,适合日志 PARTITION BY RANGE(`dt`) ( PARTITION p20240501 VALUES [('2024-05-01'), ('2024-05-02')), PARTITION p20240502 VALUES [('2024-05-02'), ('2024-05-03')) -- ... 后续分区可以通过动态分区特性自动创建 ) DISTRIBUTED BY HASH(`service_name`, `host_ip`) BUCKETS 48 PROPERTIES ( "replication_num" = "3", -- 副本数,保障高可用 "storage_medium" = "SSD", "dynamic_partition.enable" = "true", -- 启用动态分区 "dynamic_partition.time_unit" = "DAY", "dynamic_partition.start" = -7, -- 保留最近7天分区 "dynamic_partition.end" = 3, -- 提前创建未来3天分区 "dynamic_partition.prefix" = "p", "bloom_filter_columns"="host_ip,extracted_error_code,trace_id" -- 创建BloomFilter索引 ); -- 创建物化视图,加速按服务和级别的聚合查询 CREATE MATERIALIZED VIEW mv_log_stats_by_service_level AS SELECT dt, service_name, log_level, count(*) FROM agent_logs GROUP BY dt, service_name, log_level;

关键配置解析:

  1. 数据模型选择:我们选择了DUPLICATE KEY模型。对于日志数据,每条记录都是独立的,没有唯一主键的概念,且我们可能需要保留完全相同的多条日志。DUPLICATE KEY模型最适合这种场景,它按照指定的排序列(ts, service_name...)进行排序,相同前缀的数据在物理上会更接近,利于范围查询。
  2. 动态分区dynamic_partition相关属性是运维神器。它自动管理分区的创建和删除,比如每天自动创建明天的分区,并删除7天前的分区,完全免去了手动维护分区的工作。
  3. 分桶数BUCKETS 48。我们计划将数据分布在6个BE上,每个BE上该表会有8个桶。这个并行度对于我们的查询和集群规模是合适的。分桶数最好是 BE 节点数的整数倍,以保证数据均匀。

4.2 写入优化与 Compaction 调参

高并发写入下,需要关注 BE 的内存和 Compaction(数据压缩合并)策略。

Stream Load 与 Routine Load:对于 Agent 数据,推荐使用Stream Load(HTTP API)进行微批写入(如每批 100MB 或 10万条)。它的吞吐高,协议简单。Doris 也支持Routine Load,通过订阅 Kafka 等消息队列自动导入数据,更适合与流处理管道集成。在 AgentLogsBench 测试中,我们使用多客户端并发 Stream Load 来模拟压力。

内存与 Compaction 配置:写入性能的瓶颈常常在 Compaction。如果数据写入太快,而底层文件的合并速度跟不上,就会产生大量小文件,影响查询性能。需要调整 BE 的配置参数:

# 在 be.conf 中调整 # 提高单个Compaction任务的内存上限,加速合并过程 compaction_task_num_per_disk = 4 compaction_worker_threads = 8 # 调整各层级的Compaction策略,控制文件大小和合并频率 cumulative_compaction_num_threads_per_disk = 2 base_compaction_num_threads_per_disk = 1

注意事项:写入与查询的资源隔离在高并发写入期间,Compaction 会消耗大量 CPU 和 IO 资源,可能影响查询性能。在生产环境中,可以考虑将集群的 BE 节点分为两组:一组专门负责接收写入和 Base Compaction(write_group),另一组专门负责查询和 Cumulative Compaction(query_group)。通过 Doris 的 Tag 功能,可以将表的不同副本分布到不同组别的节点上,实现物理资源的隔离。这是应对极端写入负载的高级策略。

4.3 查询性能调优实战

即使有了好的表结构,查询也可能因为姿势不对而变慢。以下是一些实战技巧:

  1. 利用分区裁剪:确保你的查询条件中总是包含分区列dt。例如WHERE dt='2024-05-01' AND ts BETWEEN ...。这样 Doris 在规划阶段就能直接跳过无关分区。
  2. **避免 SELECT ***:明确列出需要的列。特别是raw_json这种大字段,除非必要,否则不要 select 出来。Doris 是列存,只读取需要的列能极大减少 IO。
  3. 善用物化视图:分析常用的查询模式,为其创建物化视图。Doris 优化器会自动选择,无需修改查询语句。
  4. 监控与诊断:使用EXPLAIN命令查看查询执行计划。重点关注是否有SCAN全表、AGGREGATE是否在本地进行、是否有不必要的SHUFFLE。Doris 的 Web UI 也提供了直观的查询 Profile,可以定位到耗时最长的算子。

5. 典型问题排查与运维心得

在实际运行和测试中,我们踩过一些坑,也总结了一些经验。

5.1 常见问题速查表

问题现象可能原因排查步骤与解决方案
写入失败,报错Tablet writer add batch unknown errorBE 节点内存不足,或单个 Tablet 写入过于频繁。1. 检查 BE 节点内存使用top命令。
2. 增加 BE 的write_buffer_size或降低导入批次大小。
3. 检查表的分桶是否合理,如果数据严重倾斜导致单个 Tablet 过热,需要调整分桶键。
查询速度突然变慢1. 存在大量小文件未合并。
2. 集群负载不均衡,某个 BE 节点成为热点。
3. 查询未命中分区或索引。
1. 执行SHOW PROC '/compaction'查看 Compaction 状态和积压情况。
2. 执行SHOW PROC '/backends'查看各 BE 的负载(磁盘使用率、扫描行数)。
3. 使用EXPLAIN分析慢查询,确认是否扫描了过多分区。
磁盘空间增长过快1. 数据保留策略未生效。
2. 副本数设置过高。
3. 导入了大量临时测试数据。
1. 检查动态分区配置dynamic_partition.end是否生效,手动执行ALTER TABLE ... DROP PARTITION ...删除历史分区。
2. 评估replication_num是否可从 3 降为 2(需权衡可用性)。
3. 清理无用数据,并考虑开启 ZSTD 等更高压缩比的压缩算法。
FE 出现bdbje相关错误FE 元数据存储(BDB JE)出现异常,可能是磁盘满或文件损坏。这是严重问题!立即检查 FE 日志和磁盘空间。确保 FE 元数据目录有独立、可靠的磁盘。定期备份元数据。

5.2 容量规划与成本考量

对于可观测性场景,容量规划至关重要。一个简单的估算公式:

总原始数据量 per day = 单台服务器日志量 × 服务器数量 × 副本数

假设单台服务器每天产生 10GB 日志,有 1000 台服务器,采用 3 副本,那么每天新增的原始数据存储需求就是 30TB。Doris 的列存压缩比通常在 5:1 到 10:1 之间,我们按 5:1 估算,那么每天需要约 6TB 的物理磁盘空间。这只是热数据(比如最近7天)的本地 SSD 成本。更早的数据可以沉降到对象存储,成本会大幅下降。

成本控制的核心思路

  1. 数据分层:利用 Doris 2.0+ 的冷热数据分层功能,将冷数据自动迁移到对象存储。
  2. 压缩算法:在创建表时指定"compression" = "zstd",可以获得比默认 LZ4 更高的压缩比,节省存储空间,但会略微增加 CPU 开销。
  3. 降副本数:对于可容忍一定故障风险的测试或非核心环境,可以将replication_num设为 2。
  4. 按需升配:初期可以采用较小规模的集群,随着数据量增长,通过增加 BE 节点进行水平扩容,这个过程在 Doris 中是在线的,相对平滑。

5.3 与 Elasticsearch/ClickHouse 的对比思考

最后,谈谈我们为什么在 AgentLogsBench 后更倾向于 Doris,而不是另外两个流行选择:

  • vs Elasticsearch:ES 的强项在于全文检索和灵活的 JSON 查询。但对于大规模、高基数的数值聚合分析,其资源消耗(尤其是内存)巨大,查询延迟也更高。Doris 在纯分析场景下的性能和资源效率优势明显,且 SQL 生态对分析师更友好。如果业务强依赖全文检索(如日志关键词模糊匹配),可以考虑 Doris 对接 ES 进行联邦查询,或者使用 Doris 的倒排索引功能(仍在完善中)。
  • vs ClickHouse:ClickHouse 是性能怪兽,在单表聚合分析上甚至可能略胜一筹。但它的短板也明显:1) 集群管理相对复杂,需要自行处理分片和副本;2) 对高频数据更新和删除(MergeTree 的ReplacingMergeTree/CollapsingMergeTree语义最终一致)支持不如 Doris 的Unique Key/Aggregate Key模型直观和实时;3) 多表关联性能是弱项。Doris 提供了更接近传统数据库的使用体验,更完善的 SQL 支持(如窗口函数)、更易用的物化视图和更简单的集群运维,在可观测性这种需要灵活关联和实时修正的场景下,整体体验更均衡。

我个人在实际测试和预生产环境验证中的体会是,Apache Doris 提供了一个在性能、功能、易用性和运维成本之间取得出色平衡的选项。它可能不是每个单项的“冠军”,但作为支撑 Agent 可观测性生产负载的“全能选手”,其综合得分非常高。特别是对于已经熟悉 MySQL 协议和生态的团队,它的上手门槛很低,而性能上限又能满足绝大多数企业的实时分析需求。当然,没有银弹,最终选型还是要结合自己团队的技术栈、具体的查询模式和运维能力来决定。至少,在下次进行数据平台技术选型时,你的清单里应该给 Apache Doris 留一个重要的位置。

返回列表