Apache Doris 深度实践(上):OLAP 引擎核心特性与调优
在 coomia-dip 的数据分析层(Data Layer)中,我们需要一个能够同时满足以下需求的 OLAP 引擎:
“系列:S8 技术组件深潜 · 第 1 篇 | 难度:高级 | 阅读时间:20 分钟
Apache Doris 深度实践(上):OLAP 引擎核心特性与调优
#TL;DR
- Apache Doris 是 coomia-dip 数据分析层的核心 OLAP 引擎,采用 MPP 架构实现亚秒级交互式查询,替代了传统 ClickHouse + Elasticsearch 的双引擎方案
- 通过合理的数据模型选择(Duplicate/Aggregate/Unique)、分区分桶策略、以及物化视图预聚合,可在十亿级数据上实现 P99 < 2s 的查询延迟
- 本文深入剖析 Doris 在 coomia-dip 中的部署架构、表模型设计、查询调优、以及与 Iceberg 外表的集成实践
#1. 为什么选择 Apache Doris
#1.1 OLAP 引擎选型背景
在 coomia-dip 的数据分析层(Data Layer)中,我们需要一个能够同时满足以下需求的 OLAP 引擎:
- 亚秒级交互式查询:仪表盘和报表场景要求低延迟
- 高并发查询:多租户 SaaS 模式下并发查询数可达数百
- 实时数据摄入:支持从 Kafka 实时导入流式数据
- 标准 SQL 兼容:降低用户学习成本
- 联邦查询能力:能够查询 Iceberg、Hive 等外部数据源
#1.2 候选方案对比
我们评估了以下 OLAP 引擎:
| 特性 | Apache Doris | ClickHouse | Apache Druid | StarRocks |
|---|---|---|---|---|
| 架构模型 | MPP (FE/BE) | Shared-Nothing | Lambda | MPP (FE/BE) |
| SQL 兼容性 | MySQL 协议 | 自有协议 | 有限 SQL | MySQL 协议 |
| 实时导入 | Routine Load | Kafka Engine | Kafka Indexing | Routine Load |
| 联邦查询 | Multi-Catalog | 有限 | 无 | Multi-Catalog |
| Join 性能 | 优秀 | 一般 | 差 | 优秀 |
| 运维复杂度 | 低 | 中 | 高 | 低 |
| 社区活跃度 | 高 (Apache TLP) | 高 | 中 | 中 |
| 向量索引 | 支持 (2.1+) | 不支持 | 不支持 | 不支持 |
最终选择 Doris 的核心原因:
- 统一引擎:Doris 2.1+ 同时支持 OLAP 分析、向量检索和全文检索,避免了维护多套引擎的运维负担
- MySQL 兼容:业务团队可以使用熟悉的 MySQL 客户端和 JDBC 驱动直接连接
- Multi-Catalog 联邦查询:可以直接查询 Iceberg 表,与我们的 Lakehouse 架构无缝集成
- 云原生友好:存算分离架构支持弹性扩缩容
#1.3 Doris 在 coomia-dip 架构中的位置
┌─────────────────────────────────────────────────┐
│ Control Layer (Control) │
│ Spring Boot 3.x + gRPC │
└──────────────┬──────────────────┬────────────────┘
│ gRPC │ gRPC
┌──────────▼──────────┐ ┌───▼────────────────┐
│ Data Layer (Data) │ │ Reasoning & Decision Layer (Reasoning)│
│ Quarkus 3.x │ │ Python + FastAPI │
│ ┌──────────────┐ │ └────────────────────┘
│ │ Apache Doris │ │
│ │ (OLAP Engine) │ │
│ └──────┬───────┘ │
│ │ │
│ ┌──────▼───────┐ │
│ │ Apache Iceberg│ │
│ │ + Nessie │ │
│ └──────────────┘ │
└──────────────────────┘
Doris 在平台中承担三个核心角色:
- 分析查询引擎:为 Ontology 对象提供聚合分析能力
- 实时数据服务层:接收 Flink CDC 实时写入的变更数据
- 联邦查询网关:通过 Multi-Catalog 查询 Iceberg 冷数据
#2. 部署架构与集群规划
#2.1 coomia-dip 中的 Doris 部署拓扑
coomia-dip 采用存算一体的经典部署模式(适用于中小规模场景),同时预留了向存算分离演进的路径。
# deployment-Layer/docker-compose/doris-cluster.yml
version: '3.8'
services:
doris-fe-1:
image: apache/doris:2.1.4-fe
hostname: doris-fe-1
environment:
- FE_SERVERS=doris-fe-1:9010,doris-fe-2:9010,doris-fe-3:9010
- FE_ID=1
ports:
- "8030:8030" # HTTP API
- "9030:9030" # MySQL Protocol
- "9010:9010" # Edit Log Port
volumes:
- doris-fe-1-meta:/opt/apache-doris/fe/doris-meta
deploy:
resources:
limits:
memory: 8G
cpus: '4'
networks:
- coomia-dip-net
doris-fe-2:
image: apache/doris:2.1.4-fe
hostname: doris-fe-2
environment:
- FE_SERVERS=doris-fe-1:9010,doris-fe-2:9010,doris-fe-3:9010
- FE_ID=2
volumes:
- doris-fe-2-meta:/opt/apache-doris/fe/doris-meta
deploy:
resources:
limits:
memory: 8G
cpus: '4'
networks:
- coomia-dip-net
doris-fe-3:
image: apache/doris:2.1.4-fe
hostname: doris-fe-3
environment:
- FE_SERVERS=doris-fe-1:9010,doris-fe-2:9010,doris-fe-3:9010
- FE_ID=3
volumes:
- doris-fe-3-meta:/opt/apache-doris/fe/doris-meta
deploy:
resources:
limits:
memory: 8G
cpus: '4'
networks:
- coomia-dip-net
doris-be-1:
image: apache/doris:2.1.4-be
hostname: doris-be-1
environment:
- FE_SERVERS=doris-fe-1:9010,doris-fe-2:9010,doris-fe-3:9010
- BE_ADDR=doris-be-1:9050
volumes:
- doris-be-1-data:/opt/apache-doris/be/storage
deploy:
resources:
limits:
memory: 32G
cpus: '16'
networks:
- coomia-dip-net
doris-be-2:
image: apache/doris:2.1.4-be
hostname: doris-be-2
environment:
- FE_SERVERS=doris-fe-1:9010,doris-fe-2:9010,doris-fe-3:9010
- BE_ADDR=doris-be-2:9050
volumes:
- doris-be-2-data:/opt/apache-doris/be/storage
deploy:
resources:
limits:
memory: 32G
cpus: '16'
networks:
- coomia-dip-net
doris-be-3:
image: apache/doris:2.1.4-be
hostname: doris-be-3
environment:
- FE_SERVERS=doris-fe-1:9010,doris-fe-2:9010,doris-fe-3:9010
- BE_ADDR=doris-be-3:9050
volumes:
- doris-be-3-data:/opt/apache-doris/be/storage
deploy:
resources:
limits:
memory: 32G
cpus: '16'
networks:
- coomia-dip-net
volumes:
doris-fe-1-meta:
doris-fe-2-meta:
doris-fe-3-meta:
doris-be-1-data:
doris-be-2-data:
doris-be-3-data:
networks:
coomia-dip-net:
external: true
#2.2 集群规模规划指南
| 数据规模 | FE 节点 | BE 节点 | BE 内存 | BE CPU | 存储 |
|---|---|---|---|---|---|
| < 1TB | 1 (Follower) | 3 | 16GB | 8核 | 500GB SSD |
| 1-10TB | 3 (1 Leader + 2 Follower) | 5-10 | 32GB | 16核 | 2TB SSD |
| 10-50TB | 3 FE + 2 Observer | 10-20 | 64GB | 32核 | 4TB NVMe |
| > 50TB | 5 FE + Observer | 20+ | 128GB | 64核 | 8TB NVMe RAID |
#2.3 关键配置参数
FE 配置 (fe.conf):
# 元数据目录
meta_dir = /opt/apache-doris/fe/doris-meta
# JVM 堆内存(建议物理内存的 50-70%)
JAVA_OPTS="-Xmx6g -Xms6g -XX:+UseG1GC -XX:MaxGCPauseMillis=200"
# 查询超时(秒)
query_timeout = 300
# 最大连接数
qe_max_connection = 4096
# 并行执行实例数
parallel_fragment_exec_instance_num = 8
# 开启 Pipeline 执行引擎
enable_pipeline_engine = true
# Catalog 相关
enable_multi_catalog = true
# 审计日志
audit_log_dir = /opt/apache-doris/fe/log
audit_log_roll_num = 90
BE 配置 (be.conf):
# 存储目录(多盘配置提高 IO 吞吐)
storage_root_path = /data1;/data2;/data3
# 内存限制(建议物理内存的 80%)
mem_limit = 80%
# Compaction 线程数
compaction_task_num_per_disk = 4
# 向量化执行引擎
enable_vectorized_engine = true
# Page Cache
disable_storage_page_cache = false
storage_page_cache_limit = 20%
# Chunk 大小(影响查询内存使用)
chunk_reserved_bytes_limit = 2147483648
# 网络缓冲区
brpc_num_threads = 256
thrift_server_max_worker_threads = 4096
#3. 数据模型深度解析
#3.1 三种数据模型对比
Doris 提供三种表模型,选择正确的模型对查询性能至关重要:
Duplicate 模型(明细模型)
保留所有原始数据,不做任何聚合。适用于需要保留完整明细的场景。
-- coomia-dip 中的 Ontology 操作审计日志表
CREATE TABLE ontology_audit_log (
event_time DATETIME NOT NULL COMMENT '事件时间',
tenant_id VARCHAR(64) NOT NULL COMMENT '租户ID',
user_id VARCHAR(64) NOT NULL COMMENT '用户ID',
object_type VARCHAR(128) NOT NULL COMMENT '对象类型',
object_rid VARCHAR(256) NOT NULL COMMENT '对象RID',
action VARCHAR(32) NOT NULL COMMENT '操作类型',
property_name VARCHAR(128) COMMENT '属性名',
old_value TEXT COMMENT '旧值',
new_value TEXT COMMENT '新值',
source_ip VARCHAR(45) COMMENT '来源IP',
request_id VARCHAR(64) COMMENT '请求ID',
duration_ms INT COMMENT '处理耗时(ms)'
)
DUPLICATE KEY(event_time, tenant_id, user_id)
PARTITION BY RANGE(event_time) (
FROM ("2024-01-01") TO ("2026-12-31") INTERVAL 1 MONTH
)
DISTRIBUTED BY HASH(tenant_id) BUCKETS 16
PROPERTIES (
"replication_num" = "3",
"dynamic_partition.enable" = "true",
"dynamic_partition.time_unit" = "MONTH",
"dynamic_partition.start" = "-12",
"dynamic_partition.end" = "3",
"dynamic_partition.prefix" = "p",
"dynamic_partition.buckets" = "16",
"compaction_policy" = "time_series"
);
Aggregate 模型(聚合模型)
相同 Key 的行在导入时自动聚合。适用于指标统计场景。
-- coomia-dip 中的对象属性变更统计表
CREATE TABLE ontology_property_stats (
stat_date DATE NOT NULL COMMENT '统计日期',
tenant_id VARCHAR(64) NOT NULL COMMENT '租户ID',
object_type VARCHAR(128) NOT NULL COMMENT '对象类型',
property_name VARCHAR(128) NOT NULL COMMENT '属性名',
change_count BIGINT SUM COMMENT '变更次数',
unique_objects HLL HLL_UNION COMMENT '去重对象数',
last_changed DATETIME MAX COMMENT '最后变更时间',
avg_duration DOUBLE AVG COMMENT '平均处理耗时'
)
AGGREGATE KEY(stat_date, tenant_id, object_type, property_name)
PARTITION BY RANGE(stat_date) (
FROM ("2024-01-01") TO ("2026-12-31") INTERVAL 1 MONTH
)
DISTRIBUTED BY HASH(tenant_id) BUCKETS 8
PROPERTIES (
"replication_num" = "3"
);
Unique 模型(唯一键模型)
相同 Key 的行自动覆盖更新。适用于需要更新的维度表场景。
-- coomia-dip 中的 Ontology 对象当前状态表
CREATE TABLE ontology_object_current (
tenant_id VARCHAR(64) NOT NULL COMMENT '租户ID',
object_type VARCHAR(128) NOT NULL COMMENT '对象类型',
object_rid VARCHAR(256) NOT NULL COMMENT '对象RID',
display_name VARCHAR(512) COMMENT '显示名称',
status VARCHAR(32) COMMENT '状态',
properties JSON COMMENT '属性JSON',
created_at DATETIME COMMENT '创建时间',
updated_at DATETIME COMMENT '更新时间',
version BIGINT COMMENT '版本号'
)
UNIQUE KEY(tenant_id, object_type, object_rid)
DISTRIBUTED BY HASH(tenant_id) BUCKETS 16
PROPERTIES (
"replication_num" = "3",
"enable_unique_key_merge_on_write" = "true",
"store_row_column" = "true"
);
#3.2 模型选择决策树
需要保留所有明细?
├─ 是 → Duplicate 模型
│ (审计日志、事件流、行为记录)
└─ 否 → 数据有唯一业务键?
├─ 是 → 需要部分列更新?
│ ├─ 是 → Unique 模型 (Merge-on-Write)
│ │ (维度表、状态表、实时更新表)
│ └─ 否 → 只需预聚合指标?
│ ├─ 是 → Aggregate 模型
│ │ (指标统计、计数器、HLL 去重)
│ └─ 否 → Unique 模型
└─ 否 → Duplicate 模型
#4. 分区分桶策略
#4.1 分区策略
分区是 Doris 的第一级数据管理粒度,coomia-dip 中按数据特征采用不同分区策略:
时间范围分区(最常用):
-- 按月分区,适用于审计日志、指标数据
PARTITION BY RANGE(event_date) (
PARTITION p202401 VALUES [('2024-01-01'), ('2024-02-01')),
PARTITION p202402 VALUES [('2024-02-01'), ('2024-03-01')),
-- 使用动态分区自动管理
)
动态分区配置:
PROPERTIES (
"dynamic_partition.enable" = "true",
"dynamic_partition.time_unit" = "DAY",
"dynamic_partition.start" = "-30", -- 保留30天历史
"dynamic_partition.end" = "3", -- 预创建3天分区
"dynamic_partition.prefix" = "p",
"dynamic_partition.buckets" = "16",
"dynamic_partition.create_history_partition" = "true"
);
List 分区(多租户隔离):
-- 按租户分区,实现物理隔离
PARTITION BY LIST(tenant_id) (
PARTITION p_tenant_a VALUES IN ("tenant-001"),
PARTITION p_tenant_b VALUES IN ("tenant-002"),
PARTITION p_tenant_c VALUES IN ("tenant-003")
)
#4.2 分桶策略
分桶是 Doris 的第二级数据分布粒度,直接影响查询并行度:
-- Hash 分桶:适用于点查和 Join 场景
DISTRIBUTED BY HASH(tenant_id, object_rid) BUCKETS 16
-- Random 分桶:适用于无明显分布键的场景
DISTRIBUTED BY RANDOM BUCKETS 32
分桶数计算公式:
推荐桶数 = max(
BE节点数 * CPU核数 / 2,
单分区数据量(GB) * 1024 / 256 -- 每桶约 256MB
)
#4.3 coomia-dip 中的分区分桶最佳实践
| 数据类型 | 分区方式 | 分桶键 | 桶数 |
|---|---|---|---|
| 审计日志 | 按天动态分区 | tenant_id | 16 |
| 指标统计 | 按月范围分区 | tenant_id + metric_name | 8 |
| 对象状态 | 无分区 | tenant_id + object_type | 32 |
| 关系数据 | 按天动态分区 | source_rid | 16 |
| 决策结果 | 按月范围分区 | tenant_id + decision_type | 8 |
#5. 查询优化实战
#5.1 执行计划分析
使用 EXPLAIN 分析查询计划是调优的第一步:
EXPLAIN VERBOSE
SELECT
t.object_type,
COUNT(*) as total_changes,
COUNT(DISTINCT object_rid) as unique_objects,
AVG(duration_ms) as avg_duration
FROM ontology_audit_log t
WHERE t.tenant_id = 'tenant-001'
AND t.event_time >= '2025-01-01'
AND t.event_time < '2025-02-01'
GROUP BY t.object_type
ORDER BY total_changes DESC
LIMIT 20;
关键关注点:
- Scan 节点:检查是否裁剪了正确的分区和桶
- Exchange 节点:检查数据 Shuffle 策略
- Agg 节点:检查聚合是否下推到 Scan 阶段
- Runtime Filter:检查是否生成了运行时过滤
#5.2 物化视图加速
物化视图是 Doris 最重要的加速手段之一:
-- 为审计日志创建按对象类型的聚合物化视图
CREATE MATERIALIZED VIEW mv_audit_by_object_type AS
SELECT
tenant_id,
DATE_TRUNC('day', event_time) as event_date,
object_type,
action,
COUNT(*) as action_count,
COUNT(DISTINCT user_id) as unique_users,
AVG(duration_ms) as avg_duration
FROM ontology_audit_log
GROUP BY tenant_id, DATE_TRUNC('day', event_time), object_type, action;
-- 查询时 Doris 自动路由到物化视图
SELECT object_type, SUM(action_count)
FROM ontology_audit_log
WHERE tenant_id = 'tenant-001'
GROUP BY object_type;
-- [自动命中 mv_audit_by_object_type]
#5.3 Colocation Join 优化
对于频繁 Join 的表,使用 Colocation Group 避免数据 Shuffle:
-- 将经常 Join 的表放入同一个 Colocation Group
CREATE TABLE ontology_objects (
tenant_id VARCHAR(64) NOT NULL,
object_rid VARCHAR(256) NOT NULL,
object_type VARCHAR(128) NOT NULL,
display_name VARCHAR(512),
created_at DATETIME
)
UNIQUE KEY(tenant_id, object_rid)
DISTRIBUTED BY HASH(tenant_id, object_rid) BUCKETS 16
PROPERTIES (
"colocate_with" = "ontology_group"
);
CREATE TABLE ontology_relations (
tenant_id VARCHAR(64) NOT NULL,
source_rid VARCHAR(256) NOT NULL,
target_rid VARCHAR(256) NOT NULL,
relation_type VARCHAR(128) NOT NULL,
created_at DATETIME
)
DUPLICATE KEY(tenant_id, source_rid, target_rid)
DISTRIBUTED BY HASH(tenant_id, source_rid) BUCKETS 16
PROPERTIES (
"colocate_with" = "ontology_group"
);
#5.4 Runtime Filter 调优
Runtime Filter 可以在 Join 时提前过滤数据,大幅减少扫描量:
-- 全局 Session 变量设置
SET runtime_filter_mode = "GLOBAL";
SET runtime_filter_type = "IN_OR_BLOOM_FILTER,MIN_MAX";
SET runtime_filter_max_in_num = 4096;
SET runtime_filter_wait_time_ms = 2000;
#5.5 查询性能基准测试
我们在 coomia-dip 的测试环境(3 BE 节点,各 32GB/16核)上进行了基准测试:
| 查询场景 | 数据量 | 未优化延迟 | 优化后延迟 | 优化手段 |
|---|---|---|---|---|
| 单租户审计日志聚合 | 5亿行 | 3.2s | 0.4s | 物化视图 + 分区裁剪 |
| 跨表 Join (对象+关系) | 1亿行 | 8.5s | 1.2s | Colocation Join |
| 多维指标查询 | 10亿行 | 12.1s | 1.8s | 物化视图 + Runtime Filter |
| 模糊搜索 | 5000万行 | 5.6s | 0.3s | 倒排索引(见第2篇) |
| 向量相似度搜索 | 1000万向量 | 2.1s | 0.15s | 向量索引(见第2篇) |
| 点查(单条记录) | 10亿行 | 0.5s | 0.02s | 行存 + 短路径 |
#6. 实时数据导入
#6.1 Routine Load(Kafka 消费)
coomia-dip 使用 Routine Load 从 Kafka 实时消费 Ontology 变更事件:
CREATE ROUTINE LOAD ontology_db.load_audit_events
ON ontology_audit_log
COLUMNS(
event_time, tenant_id, user_id, object_type,
object_rid, action, property_name,
old_value, new_value, source_ip,
request_id, duration_ms
),
COLUMNS TERMINATED BY "|"
PROPERTIES (
"desired_concurrent_number" = "3",
"max_batch_interval" = "20",
"max_batch_rows" = "200000",
"max_batch_size" = "104857600",
"strict_mode" = "false",
"format" = "json",
"jsonpaths" = "[
\"$.event_time\", \"$.tenant_id\", \"$.user_id\",
\"$.object_type\", \"$.object_rid\", \"$.action\",
\"$.property_name\", \"$.old_value\", \"$.new_value\",
\"$.source_ip\", \"$.request_id\", \"$.duration_ms\"
]"
)
FROM KAFKA (
"kafka_broker_list" = "kafka-1:9092,kafka-2:9092,kafka-3:9092",
"kafka_topic" = "ontology-audit-events",
"kafka_partitions" = "0,1,2,3,4,5",
"kafka_offsets" = "OFFSET_BEGINNING",
"property.group.id" = "doris_audit_consumer",
"property.kafka_default_offsets" = "OFFSET_END"
);
#6.2 Stream Load(批量导入)
对于 Flink 作业的批量写入,使用 Stream Load:
# data-Layer/src/main/python/doris_stream_load.py
import requests
import json
from typing import List, Dict
class DorisStreamLoader:
"""Doris Stream Load 客户端封装"""
def __init__(self, fe_host: str, port: int = 8030,
user: str = "root", password: str = ""):
self.base_url = f"http://{fe_host}:{port}"
self.auth = (user, password)
def load_json_data(
self,
database: str,
table: str,
data: List[Dict],
label: str = None
) -> Dict:
"""通过 Stream Load 导入 JSON 数据"""
url = f"{self.base_url}/api/{database}/{table}/_stream_load"
headers = {
"Content-Type": "application/json",
"Expect": "100-continue",
"format": "json",
"strip_outer_array": "true",
"max_filter_ratio": "0.1"
}
if label:
headers["label"] = label
payload = json.dumps(data)
response = requests.put(
url,
headers=headers,
data=payload,
auth=self.auth
)
result = response.json()
if result.get("Status") != "Success":
raise Exception(f"Stream Load failed: {result}")
return result
#6.3 导入性能对比
| 导入方式 | 吞吐量 | 延迟 | 适用场景 |
|---|---|---|---|
| Stream Load | 100-200MB/s | 秒级 | Flink 批量写入 |
| Routine Load | 50-100MB/s | 秒级 | Kafka 实时消费 |
| Broker Load | 200-500MB/s | 分钟级 | HDFS/S3 大文件导入 |
| INSERT INTO | 10-50MB/s | 秒级 | 小批量/单条写入 |
#7. Multi-Catalog 联邦查询
#7.1 Iceberg Catalog 配置
coomia-dip 通过 Doris 的 Multi-Catalog 直接查询 Iceberg 表:
-- 创建 Iceberg Catalog(使用 Nessie 作为元数据后端)
CREATE CATALOG iceberg_catalog PROPERTIES (
"type" = "iceberg",
"iceberg.catalog.type" = "rest",
"uri" = "http://nessie-server:19120/api/v2",
"warehouse" = "s3://coomia-dip-lakehouse/warehouse",
"s3.endpoint" = "http://minio:9000",
"s3.access-key" = "${S3_ACCESS_KEY}",
"s3.secret-key" = "${S3_SECRET_KEY}"
);
-- 跨 Catalog 查询:Doris 内部表 JOIN Iceberg 外表
SELECT
d.tenant_id,
d.object_type,
d.action,
COUNT(*) as recent_count,
i.total_historical_count
FROM internal.ontology_db.ontology_audit_log d
JOIN iceberg_catalog.lakehouse.historical_audit_stats i
ON d.tenant_id = i.tenant_id
AND d.object_type = i.object_type
WHERE d.event_time >= CURRENT_DATE - INTERVAL 7 DAY
GROUP BY d.tenant_id, d.object_type, d.action, i.total_historical_count;
#7.2 查询下推优化
Doris 对 Iceberg 外表支持谓词下推和列裁剪:
-- 以下查询的 WHERE 条件会被下推到 Iceberg 层
-- 利用 Iceberg 的分区裁剪和 Min/Max 统计信息
SELECT * FROM iceberg_catalog.lakehouse.ontology_snapshots
WHERE snapshot_date = '2025-03-01' -- 分区裁剪
AND tenant_id = 'tenant-001' -- 谓词下推
AND status = 'ACTIVE'; -- 谓词下推
#8. 监控与运维
#8.1 关键监控指标
-- 查看集群状态
SHOW BACKENDS;
SHOW FRONTENDS;
-- 查看表的数据分布
SHOW DATA FROM ontology_audit_log;
-- 查看正在运行的查询
SHOW PROCESSLIST;
-- 查看 Compaction 状态
SHOW TABLET STORAGE FORMAT;
-- 查看 Routine Load 状态
SHOW ROUTINE LOAD FOR load_audit_events;
#8.2 Prometheus + Grafana 监控集成
# deployment-Layer/monitoring/prometheus/doris-targets.yml
- targets:
- doris-fe-1:8030
- doris-fe-2:8030
- doris-fe-3:8030
labels:
component: doris-fe
- targets:
- doris-be-1:8040
- doris-be-2:8040
- doris-be-3:8040
labels:
component: doris-be
核心监控面板指标:
| 指标 | 告警阈值 | 说明 |
|---|---|---|
doris_be_mem_usage_percent | > 85% | BE 内存使用率 |
doris_be_disk_usage_percent | > 80% | 磁盘使用率 |
doris_fe_query_latency_ms_p99 | > 5000 | P99 查询延迟 |
doris_be_compaction_score | > 100 | Compaction 积压 |
doris_fe_connection_total | > 3000 | 活跃连接数 |
doris_be_stream_load_rows_rate | < 10000 | 导入速率下降 |
#8.3 常见运维操作
扩容 BE 节点:
-- 1. 启动新 BE 节点
-- 2. 注册到 FE
ALTER SYSTEM ADD BACKEND "new-be-host:9050";
-- 3. 检查均衡状态
SHOW PROC '/cluster_balance/cluster_load_stat';
-- 4. 手动触发数据均衡(如需)
ADMIN SET FRONTEND CONFIG ("tablet_rebalancer_type" = "BeLoad");
表 Schema 变更:
-- 添加列(Light Schema Change,秒级完成)
ALTER TABLE ontology_audit_log
ADD COLUMN correlation_id VARCHAR(128) COMMENT '关联ID';
-- 修改列类型
ALTER TABLE ontology_audit_log
MODIFY COLUMN old_value VARCHAR(4096);
#9. 常见陷阱与解决方案
#9.1 数据倾斜
问题:某些租户数据量远大于其他租户,导致部分桶数据膨胀。
解决方案:
-- 使用复合分桶键分散数据
DISTRIBUTED BY HASH(tenant_id, object_rid) BUCKETS 32
-- 对超大租户使用单独分区
PARTITION BY LIST(tenant_id) (
PARTITION p_big_tenant VALUES IN ("tenant-big-001"),
PARTITION p_others VALUES IN ("tenant-002", "tenant-003", ...)
)
#9.2 Compaction 积压
问题:高频写入导致 Compaction 跟不上,影响查询性能。
解决方案:
# be.conf 调整
compaction_task_num_per_disk = 8
cumulative_compaction_rounds_for_each_base_compaction_round = 15
compaction_policy = time_series # 时序数据专用策略
#9.3 内存溢出
问题:复杂查询消耗过多内存导致 BE OOM。
解决方案:
-- 设置单查询内存限制
SET exec_mem_limit = 4294967296; -- 4GB
-- 开启溢写磁盘
SET enable_spill = true;
SET spill_storage_limit = "50GB";
-- 使用资源组隔离不同负载
CREATE WORKLOAD GROUP 'analytics' PROPERTIES (
"cpu_share" = "20",
"memory_limit" = "50%",
"enable_memory_overcommit" = "true"
);
#9.4 查询超时
问题:大范围扫描查询超时。
解决方案:
-- 1. 检查是否命中分区裁剪
EXPLAIN SELECT * FROM ontology_audit_log
WHERE event_time > '2025-01-01';
-- 2. 添加物化视图预计算
-- 3. 调整超时参数
SET query_timeout = 600;
-- 4. 使用异步查询(大查询场景)
-- 提交异步查询,返回 query_id
-- 后台执行,客户端轮询结果
#10. 与 Palantir Foundry 的对比
| 能力 | Palantir Foundry | coomia-dip (Doris) |
|---|---|---|
| OLAP 查询 | Foundry Analytics | Doris MPP 查询 |
| 实时数据 | Pipeline Builder | Routine Load + Flink |
| 数据联邦 | Foundry Datasets | Multi-Catalog |
| 搜索 | Phonograph | 倒排索引(见第2篇) |
| 向量检索 | 无原生支持 | 向量索引(见第2篇) |
| 物化视图 | 衍生数据集 | 同步/异步物化视图 |
| 数据治理 | Foundry Governance | Workload Group |
coomia-dip 通过 Doris 实现了与 Foundry 对标的分析能力,且在向量检索、全文检索方面提供了 Foundry 不具备的统一引擎优势。
#Key Takeaways
-
模型选择是性能基石:根据业务场景选择正确的 Duplicate/Aggregate/Unique 模型,可以避免 80% 的性能问题。在 coomia-dip 中,审计日志用 Duplicate、指标统计用 Aggregate、状态数据用 Unique。
-
分区分桶策略决定查询上限:合理的分区(时间维度)+ 分桶(业务键)策略是实现亚秒级查询的前提。coomia-dip 中所有时序表采用动态分区管理生命周期。
-
物化视图是最有效的加速手段:对于固定模式的聚合查询,物化视图可以将延迟从秒级降到毫秒级,在 coomia-dip 仪表盘场景中提速 5-10 倍。
#下一篇预告
S8-02: Apache Doris 深度实践(下):向量索引+倒排索引 — 深入探讨 Doris 2.1+ 的向量检索和全文检索能力,包括 HNSW 索引构建、混合查询(OLAP + 向量 + 全文)的实现方案,以及与独立 Elasticsearch/Milvus 方案的性能对比。
Tags: #apache-doris #olap #mpp #query-optimization #data-modeling #coomia-dip #Layer-c