Apache Doris Deep Practice (Part 1): OLAP Engine Core Features and Tuning
In coomia-dip's Data Layer (Data Layer), we need an OLAP engine that simultaneously satisfies:
“Series: S8 Technology Deep Dives · Article 1 | Level: Advanced | Reading Time: 20 min
Apache Doris Deep Practice (Part 1): OLAP Engine Core Features and Tuning
#TL;DR
- Apache Doris serves as the core OLAP engine in coomia-dip's analytics layer, leveraging MPP architecture to achieve sub-second interactive queries, replacing the traditional ClickHouse + Elasticsearch dual-engine approach
- Through proper data model selection (Duplicate/Aggregate/Unique), partition and bucket strategies, and materialized view pre-aggregation, P99 < 2s query latency is achievable on billion-row datasets
- This article dissects Doris's deployment architecture, table model design, query tuning, and Iceberg external table integration within coomia-dip
#1. Why Apache Doris
#1.1 OLAP Engine Selection Context
In coomia-dip's Data Layer (Data Layer), we need an OLAP engine that simultaneously satisfies:
- Sub-second interactive queries: Dashboards and reports demand low latency
- High query concurrency: Multi-tenant SaaS mode can reach hundreds of concurrent queries
- Real-time data ingestion: Support for streaming data import from Kafka
- Standard SQL compatibility: Reduce user learning curve
- Federated query capability: Query external data sources like Iceberg and Hive
#1.2 Candidate Comparison
We evaluated the following OLAP engines:
| Feature | Apache Doris | ClickHouse | Apache Druid | StarRocks |
|---|---|---|---|---|
| Architecture | MPP (FE/BE) | Shared-Nothing | Lambda | MPP (FE/BE) |
| SQL Compatibility | MySQL Protocol | Proprietary | Limited SQL | MySQL Protocol |
| Real-time Import | Routine Load | Kafka Engine | Kafka Indexing | Routine Load |
| Federated Query | Multi-Catalog | Limited | None | Multi-Catalog |
| Join Performance | Excellent | Fair | Poor | Excellent |
| Operations Complexity | Low | Medium | High | Low |
| Community Activity | High (Apache TLP) | High | Medium | Medium |
| Vector Index | Supported (2.1+) | Not Supported | Not Supported | Not Supported |
Core reasons for choosing Doris:
- Unified engine: Doris 2.1+ simultaneously supports OLAP analytics, vector search, and full-text search, eliminating the operational burden of maintaining multiple engines
- MySQL compatibility: Business teams can connect directly using familiar MySQL clients and JDBC drivers
- Multi-Catalog federated query: Direct querying of Iceberg tables, seamlessly integrating with our Lakehouse architecture
- Cloud-native friendly: Storage-compute separation architecture supports elastic scaling
#1.3 Doris's Position in coomia-dip Architecture
┌─────────────────────────────────────────────────┐
│ 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 serves three core roles in the platform:
- Analytical query engine: Provides aggregation analytics for Ontology objects
- Real-time data serving layer: Receives change data written in real-time by Flink CDC
- Federated query gateway: Queries cold data in Iceberg via Multi-Catalog
#2. Deployment Architecture and Cluster Planning
#2.1 Doris Deployment Topology in coomia-dip
coomia-dip adopts the classic storage-compute coupled deployment model (suitable for small-to-medium scale), while preserving the migration path toward storage-compute separation.
# 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 Cluster Sizing Guide
| Data Volume | FE Nodes | BE Nodes | BE Memory | BE CPU | Storage |
|---|---|---|---|---|---|
| < 1TB | 1 (Follower) | 3 | 16GB | 8 cores | 500GB SSD |
| 1-10TB | 3 (1 Leader + 2 Follower) | 5-10 | 32GB | 16 cores | 2TB SSD |
| 10-50TB | 3 FE + 2 Observer | 10-20 | 64GB | 32 cores | 4TB NVMe |
| > 50TB | 5 FE + Observer | 20+ | 128GB | 64 cores | 8TB NVMe RAID |
#2.3 Critical Configuration Parameters
FE Configuration (fe.conf):
# Metadata directory
meta_dir = /opt/apache-doris/fe/doris-meta
# JVM heap memory (recommended 50-70% of physical memory)
JAVA_OPTS="-Xmx6g -Xms6g -XX:+UseG1GC -XX:MaxGCPauseMillis=200"
# Query timeout (seconds)
query_timeout = 300
# Maximum connections
qe_max_connection = 4096
# Parallel execution instance count
parallel_fragment_exec_instance_num = 8
# Enable Pipeline execution engine
enable_pipeline_engine = true
# Catalog related
enable_multi_catalog = true
# Audit log
audit_log_dir = /opt/apache-doris/fe/log
audit_log_roll_num = 90
BE Configuration (be.conf):
# Storage directories (multi-disk for higher IO throughput)
storage_root_path = /data1;/data2;/data3
# Memory limit (recommended 80% of physical memory)
mem_limit = 80%
# Compaction thread count
compaction_task_num_per_disk = 4
# Vectorized execution engine
enable_vectorized_engine = true
# Page Cache
disable_storage_page_cache = false
storage_page_cache_limit = 20%
# Chunk size (affects query memory usage)
chunk_reserved_bytes_limit = 2147483648
# Network buffer
brpc_num_threads = 256
thrift_server_max_worker_threads = 4096
#3. Data Model Deep Analysis
#3.1 Three Data Models Compared
Doris offers three table models. Choosing the correct model is critical for query performance:
Duplicate Model (Detail Model)
Retains all raw data without any aggregation. Suitable for scenarios requiring complete detail preservation.
-- Ontology operation audit log table in coomia-dip
CREATE TABLE ontology_audit_log (
event_time DATETIME NOT NULL COMMENT 'Event time',
tenant_id VARCHAR(64) NOT NULL COMMENT 'Tenant ID',
user_id VARCHAR(64) NOT NULL COMMENT 'User ID',
object_type VARCHAR(128) NOT NULL COMMENT 'Object type',
object_rid VARCHAR(256) NOT NULL COMMENT 'Object RID',
action VARCHAR(32) NOT NULL COMMENT 'Action type',
property_name VARCHAR(128) COMMENT 'Property name',
old_value TEXT COMMENT 'Old value',
new_value TEXT COMMENT 'New value',
source_ip VARCHAR(45) COMMENT 'Source IP',
request_id VARCHAR(64) COMMENT 'Request ID',
duration_ms INT COMMENT 'Processing time (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 Model
Rows with the same key are automatically aggregated during import. Suitable for metric statistics scenarios.
-- Object property change statistics table in coomia-dip
CREATE TABLE ontology_property_stats (
stat_date DATE NOT NULL COMMENT 'Statistics date',
tenant_id VARCHAR(64) NOT NULL COMMENT 'Tenant ID',
object_type VARCHAR(128) NOT NULL COMMENT 'Object type',
property_name VARCHAR(128) NOT NULL COMMENT 'Property name',
change_count BIGINT SUM COMMENT 'Change count',
unique_objects HLL HLL_UNION COMMENT 'Distinct object count',
last_changed DATETIME MAX COMMENT 'Last change time',
avg_duration DOUBLE AVG COMMENT 'Average processing time'
)
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 Model (Unique Key Model)
Rows with the same key are automatically replaced. Suitable for dimension tables requiring updates.
-- Ontology object current state table in coomia-dip
CREATE TABLE ontology_object_current (
tenant_id VARCHAR(64) NOT NULL COMMENT 'Tenant ID',
object_type VARCHAR(128) NOT NULL COMMENT 'Object type',
object_rid VARCHAR(256) NOT NULL COMMENT 'Object RID',
display_name VARCHAR(512) COMMENT 'Display name',
status VARCHAR(32) COMMENT 'Status',
properties JSON COMMENT 'Properties JSON',
created_at DATETIME COMMENT 'Created at',
updated_at DATETIME COMMENT 'Updated at',
version BIGINT COMMENT 'Version'
)
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 Model Selection Decision Tree
Need to retain all detail records?
├─ Yes → Duplicate Model
│ (Audit logs, event streams, behavioral records)
└─ No → Data has a unique business key?
├─ Yes → Need partial column updates?
│ ├─ Yes → Unique Model (Merge-on-Write)
│ │ (Dimension tables, state tables, real-time update tables)
│ └─ No → Only need pre-aggregated metrics?
│ ├─ Yes → Aggregate Model
│ │ (Metric statistics, counters, HLL dedup)
│ └─ No → Unique Model
└─ No → Duplicate Model
#4. Partition and Bucket Strategy
#4.1 Partition Strategy
Partitioning is Doris's first-level data management granularity. coomia-dip employs different partition strategies based on data characteristics:
Time Range Partition (Most Common):
-- Monthly partition, suitable for audit logs and metric data
PARTITION BY RANGE(event_date) (
PARTITION p202401 VALUES [('2024-01-01'), ('2024-02-01')),
PARTITION p202402 VALUES [('2024-02-01'), ('2024-03-01')),
-- Use dynamic partitions for automatic management
)
Dynamic Partition Configuration:
PROPERTIES (
"dynamic_partition.enable" = "true",
"dynamic_partition.time_unit" = "DAY",
"dynamic_partition.start" = "-30", -- Retain 30 days of history
"dynamic_partition.end" = "3", -- Pre-create 3 days of partitions
"dynamic_partition.prefix" = "p",
"dynamic_partition.buckets" = "16",
"dynamic_partition.create_history_partition" = "true"
);
List Partition (Multi-Tenant Isolation):
-- Partition by tenant for physical isolation
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 Bucket Strategy
Bucketing is Doris's second-level data distribution granularity, directly affecting query parallelism:
-- Hash bucketing: suitable for point queries and joins
DISTRIBUTED BY HASH(tenant_id, object_rid) BUCKETS 16
-- Random bucketing: suitable for scenarios without clear distribution keys
DISTRIBUTED BY RANDOM BUCKETS 32
Bucket Count Formula:
Recommended buckets = max(
BE_nodes * CPU_cores / 2,
partition_data_size_GB * 1024 / 256 -- ~256MB per bucket
)
#4.3 Partition and Bucket Best Practices in coomia-dip
| Data Type | Partition Method | Bucket Key | Buckets |
|---|---|---|---|
| Audit logs | Daily dynamic partition | tenant_id | 16 |
| Metric statistics | Monthly range partition | tenant_id + metric_name | 8 |
| Object state | No partition | tenant_id + object_type | 32 |
| Relationship data | Daily dynamic partition | source_rid | 16 |
| Decision results | Monthly range partition | tenant_id + decision_type | 8 |
#5. Query Optimization in Practice
#5.1 Execution Plan Analysis
Using EXPLAIN to analyze query plans is the first step in tuning:
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;
Key areas to examine:
- Scan node: Verify correct partition and bucket pruning
- Exchange node: Check data shuffle strategy
- Agg node: Check if aggregation is pushed down to the scan phase
- Runtime Filter: Check if runtime filters are generated
#5.2 Materialized View Acceleration
Materialized views are one of Doris's most important acceleration mechanisms:
-- Create aggregation materialized view for audit logs by object type
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 automatically routes queries to the materialized view
SELECT object_type, SUM(action_count)
FROM ontology_audit_log
WHERE tenant_id = 'tenant-001'
GROUP BY object_type;
-- [Automatically hits mv_audit_by_object_type]
#5.3 Colocation Join Optimization
For frequently joined tables, use Colocation Groups to avoid data shuffle:
-- Place frequently joined tables in the same 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 Tuning
Runtime Filters pre-filter data during joins, dramatically reducing scan volume:
-- Global session variable settings
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 Query Performance Benchmarks
We conducted benchmarks on coomia-dip's test environment (3 BE nodes, 32GB/16 cores each):
| Query Scenario | Data Volume | Unoptimized Latency | Optimized Latency | Optimization Method |
|---|---|---|---|---|
| Single tenant audit aggregation | 500M rows | 3.2s | 0.4s | Materialized view + partition pruning |
| Cross-table Join (objects + relations) | 100M rows | 8.5s | 1.2s | Colocation Join |
| Multi-dimensional metric query | 1B rows | 12.1s | 1.8s | Materialized view + Runtime Filter |
| Fuzzy search | 50M rows | 5.6s | 0.3s | Inverted index (see Article 2) |
| Vector similarity search | 10M vectors | 2.1s | 0.15s | Vector index (see Article 2) |
| Point query (single record) | 1B rows | 0.5s | 0.02s | Row storage + short path |
#6. Real-Time Data Import
#6.1 Routine Load (Kafka Consumption)
coomia-dip uses Routine Load to consume Ontology change events from Kafka in real time:
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 (Bulk Import)
For Flink job bulk writes, use 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 client wrapper"""
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:
"""Import JSON data via Stream Load"""
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 Import Performance Comparison
| Import Method | Throughput | Latency | Use Case |
|---|---|---|---|
| Stream Load | 100-200MB/s | Seconds | Flink bulk writes |
| Routine Load | 50-100MB/s | Seconds | Kafka real-time consumption |
| Broker Load | 200-500MB/s | Minutes | HDFS/S3 large file import |
| INSERT INTO | 10-50MB/s | Seconds | Small batch/single row writes |
#7. Multi-Catalog Federated Query
#7.1 Iceberg Catalog Configuration
coomia-dip queries Iceberg tables directly through Doris's Multi-Catalog:
-- Create Iceberg Catalog (using Nessie as metadata backend)
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}"
);
-- Cross-catalog query: Doris internal table JOIN Iceberg external table
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 Query Pushdown Optimization
Doris supports predicate pushdown and column pruning for Iceberg external tables:
-- The WHERE conditions in this query are pushed down to the Iceberg layer
-- Utilizing Iceberg's partition pruning and Min/Max statistics
SELECT * FROM iceberg_catalog.lakehouse.ontology_snapshots
WHERE snapshot_date = '2025-03-01' -- Partition pruning
AND tenant_id = 'tenant-001' -- Predicate pushdown
AND status = 'ACTIVE'; -- Predicate pushdown
#8. Monitoring and Operations
#8.1 Key Monitoring Metrics
-- Check cluster status
SHOW BACKENDS;
SHOW FRONTENDS;
-- Check table data distribution
SHOW DATA FROM ontology_audit_log;
-- Check running queries
SHOW PROCESSLIST;
-- Check Compaction status
SHOW TABLET STORAGE FORMAT;
-- Check Routine Load status
SHOW ROUTINE LOAD FOR load_audit_events;
#8.2 Prometheus + Grafana Monitoring Integration
# 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
Core Dashboard Metrics:
| Metric | Alert Threshold | Description |
|---|---|---|
doris_be_mem_usage_percent | > 85% | BE memory utilization |
doris_be_disk_usage_percent | > 80% | Disk utilization |
doris_fe_query_latency_ms_p99 | > 5000 | P99 query latency |
doris_be_compaction_score | > 100 | Compaction backlog |
doris_fe_connection_total | > 3000 | Active connections |
doris_be_stream_load_rows_rate | < 10000 | Import rate drop |
#8.3 Common Operational Procedures
Scaling BE Nodes:
-- 1. Start new BE node
-- 2. Register with FE
ALTER SYSTEM ADD BACKEND "new-be-host:9050";
-- 3. Check balancing status
SHOW PROC '/cluster_balance/cluster_load_stat';
-- 4. Manually trigger data rebalancing (if needed)
ADMIN SET FRONTEND CONFIG ("tablet_rebalancer_type" = "BeLoad");
Table Schema Changes:
-- Add column (Light Schema Change, completes in seconds)
ALTER TABLE ontology_audit_log
ADD COLUMN correlation_id VARCHAR(128) COMMENT 'Correlation ID';
-- Modify column type
ALTER TABLE ontology_audit_log
MODIFY COLUMN old_value VARCHAR(4096);
#9. Common Pitfalls and Solutions
#9.1 Data Skew
Problem: Some tenants have significantly more data than others, causing certain buckets to bloat.
Solution:
-- Use composite bucket keys to distribute data
DISTRIBUTED BY HASH(tenant_id, object_rid) BUCKETS 32
-- Use separate partitions for very large tenants
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 Backlog
Problem: High-frequency writes cause compaction to fall behind, impacting query performance.
Solution:
# be.conf adjustments
compaction_task_num_per_disk = 8
cumulative_compaction_rounds_for_each_base_compaction_round = 15
compaction_policy = time_series # Policy optimized for time-series data
#9.3 Memory Overflow
Problem: Complex queries consume too much memory, causing BE OOM.
Solution:
-- Set per-query memory limit
SET exec_mem_limit = 4294967296; -- 4GB
-- Enable spilling to disk
SET enable_spill = true;
SET spill_storage_limit = "50GB";
-- Use resource groups to isolate different workloads
CREATE WORKLOAD GROUP 'analytics' PROPERTIES (
"cpu_share" = "20",
"memory_limit" = "50%",
"enable_memory_overcommit" = "true"
);
#9.4 Query Timeout
Problem: Wide-range scan queries timeout.
Solution:
-- 1. Check if partition pruning is working
EXPLAIN SELECT * FROM ontology_audit_log
WHERE event_time > '2025-01-01';
-- 2. Add materialized views for pre-computation
-- 3. Adjust timeout parameters
SET query_timeout = 600;
-- 4. Use async queries (for large query scenarios)
-- Submit async query, returns query_id
-- Background execution, client polls for results
#10. Comparison with Palantir Foundry
| Capability | Palantir Foundry | coomia-dip (Doris) |
|---|---|---|
| OLAP Query | Foundry Analytics | Doris MPP Query |
| Real-time Data | Pipeline Builder | Routine Load + Flink |
| Data Federation | Foundry Datasets | Multi-Catalog |
| Search | Phonograph | Inverted Index (see Article 2) |
| Vector Search | No native support | Vector Index (see Article 2) |
| Materialized Views | Derived Datasets | Sync/Async Materialized Views |
| Data Governance | Foundry Governance | Workload Group |
coomia-dip achieves analytics capabilities on par with Foundry through Doris, while providing a unified engine advantage in vector search and full-text search that Foundry lacks.
#Key Takeaways
-
Model selection is the performance foundation: Choosing the correct Duplicate/Aggregate/Unique model based on business scenarios avoids 80% of performance issues. In coomia-dip, audit logs use Duplicate, metric statistics use Aggregate, and state data uses Unique.
-
Partition and bucket strategy determines the query ceiling: Proper partitioning (time dimension) + bucketing (business key) strategy is the prerequisite for achieving sub-second queries. All time-series tables in coomia-dip use dynamic partition lifecycle management.
-
Materialized views are the most effective acceleration method: For fixed-pattern aggregation queries, materialized views can reduce latency from seconds to milliseconds, providing 5-10x speedup in coomia-dip dashboard scenarios.
#Next Article Preview
S8-02: Apache Doris Deep Practice (Part 2): Vector Index + Inverted Index — Deep dive into Doris 2.1+'s vector search and full-text search capabilities, including HNSW index construction, hybrid query (OLAP + vector + full-text) implementation, and performance comparison with standalone Elasticsearch/Milvus solutions.
Tags: #apache-doris #olap #mpp #query-optimization #data-modeling #coomia-dip #Layer-c