GreptimeDB 深度拆解:从「三套系统」到 Observability 2.0 —— Rust 列存引擎、存算分离架构与宽事件统一查询实战
引言:可观测性基础设施的「三座大山」
2026 年,云原生架构已成标配,但可观测性基础设施却陷入了一个悖论:工具越丰富,排查越困难。
一个典型的微服务生产环境,往往部署着三套独立系统:
- Prometheus —— 存指标,用 PromQL 查询
- Loki —— 存日志,用 LogQL 查询
- Jaeger / Tempo —— 存链路,用 Trace ID 检索
表面上看,三套系统各司其职、分工明确。但真正排查故障时,开发者的噩梦开始了:
痛点一:跨信号关联极其困难
当 CPU 使用率飙升时,你需要在 Prometheus 里找到异常的 metric,然后手动复制时间戳、服务名,再到 Loki 里搜索日志,最后到 Jaeger 里查链路。三种查询语言、三套 UI、三套权限配置,跨信号 JOIN 几乎不可能。
痛点二:预聚合丢失细节
Prometheus 的 metric 数据往往在入库时就已经聚合(如 rate(http_requests_total[5m])),原始事件被丢弃。当你想回溯「5 分钟前那波异常请求的 trace ID」时,发现数据根本不存在。
痛点三:运维复杂度爆炸
每套系统都有自己的存储层、缓存策略、告警规则、备份机制。规模越大,本地磁盘越散,运维成本呈指数增长。
痛点四:成本居高不下
Elasticsearch 的倒排索引开销、Prometheus 的本地磁盘存储、Loki 的 chunk 管理,每套系统都在「烧钱」。某客户迁移到 GreptimeDB 后,存储成本下降 60%+。
GreptimeDB 的答案:Observability 2.0 —— 一个引擎,宽事件统一存储。
一、核心理念:宽事件(Wide Events)如何统一三大信号?
1.1 传统模型的根本缺陷
传统可观测性架构将数据分为三类:
- Metrics:聚合的时间序列(counter、gauge、histogram)
- Logs:离散的事件记录
- Traces:分布式请求的调用链
这三者的技术栈完全不同:Prometheus 用时间序列数据库(TSDB),Loki 用日志存储引擎,Jaeger 用图数据库或 Cassandra。
但这种「三分法」是人为的,不是数据的本质。
1.2 宽事件模型:一切皆事件
GreptimeDB 的核心洞察:所有可观测性信号,本质上都是带时间戳的事件。
一条 HTTP 请求日志,包含了:
- 时间戳
- 服务名、实例 IP
- HTTP 方法、路径、状态码、延迟
- Trace ID、Span ID
- 用户 ID、请求参数
这不仅仅是一条 log,它同时是:
- Metric 数据源:按时间窗口聚合,得到 QPS、P99 延迟、错误率
- Trace 起点或节点:通过 Trace ID 关联上下游
- Log 记录:按关键词检索
GreptimeDB 将所有数据存储为 宽事件(Wide Events) —— 每行数百个字段的高维事件数据,通过列存引擎高效压缩和查询稀疏列。
1.3 一条 SQL 同时 JOIN 指标、日志、链路
传统架构下,跨信号查询需要:
- 在 Prometheus 中找到异常时间段的
service_name和endpoint - 在 Loki 中搜索对应时间段的日志
- 在 Jaeger 中手动输入 trace ID 查链路
GreptimeDB 中,一条 SQL 搞定:
-- 找出过去 1 小时内 P99 延迟超过 500ms 的请求的完整链路和日志
SELECT
t.timestamp,
t.service_name,
t.endpoint,
t.duration_ms,
t.trace_id,
l.log_message,
s.span_name,
s.duration_ms AS span_duration
FROM (
-- 指标数据:P99 延迟
SELECT
timestamp,
service_name,
endpoint,
percentile(duration_ms, 99) AS duration_ms,
trace_id
FROM http_requests
WHERE timestamp > now() - INTERVAL '1 hour'
GROUP BY timestamp, service_name, endpoint
HAVING duration_ms > 500
) t
JOIN logs l ON t.trace_id = l.trace_id AND t.timestamp = l.timestamp
JOIN traces s ON t.trace_id = s.trace_id
ORDER BY t.timestamp DESC
LIMIT 100;
这种能力在传统架构下需要三套系统、三种查询语言、大量手动操作,现在一个查询完成。
二、架构设计:Rust 列存引擎与存算分离
2.1 整体架构:四大组件
GreptimeDB 采用分布式架构,支持单机(Standalone)和集群(Distributed)两种模式:
┌─────────────────────────────────────────────────────────────┐
│ Frontend │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ OTel │ │ PromRW │ │ MySQL │ │ gRPC │ │
│ │ Protocol│ │ Protocol│ │ Protocol│ │ Protocol│ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Distributed Query Engine │ │
│ │ (SQL Parser + Planner + Optimizer + Executor) │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
┌───────────────────┼───────────────────┐
│ │ │
┌───────▼────────┐ ┌───────▼────────┐ ┌───────▼────────┐
│ Metasrv │ │ Datanode │ │ Flownode │
│ (Metadata, │ │ (Region Engine│ │ (Stream, │
│ Routing, │ │ WAL, Memtable│ │ Materialized │
│ Autopilot) │ │ SST, Cache) │ │ Views) │
└────────────────┘ └────────────────┘ └────────────────┘
│
┌───────▼────────┐
│ Object Storage │
│ (S3/GCS/Azure)│
└────────────────┘
四大组件职责:
Frontend:无状态入口,支持 OTel、Prometheus Remote Write、MySQL、PostgreSQL、gRPC、InfluxDB、Loki 等协议。内置分布式查询引擎,处理 SQL 和 PromQL 查询。
Datanode:存储引擎节点,负责数据持久化。包含 WAL(预写日志)、Memtable(内存表)、SST(Sorted String Table)、Cache(缓存)、Compaction(压缩合并)和索引(全文索引、倒排索引、跳过索引)。
Metasrv:元数据中心,管理表结构、分区路由、自动重分区、安全策略。底层可插拔 KV 存储(etcd 或 RDS)。
Flownode(可选):流式计算节点,支持 Flow 引擎、连续查询和物化视图。
2.2 为什么选择 Rust?
GreptimeDB 用 Rust 编写,核心优势:
内存安全,零成本抽象
Rust 的所有权系统在编译期杜绝了空指针、悬垂指针、数据竞争等内存安全问题,而运行时性能接近 C/C++。对于数据库这种对性能极其敏感的基础设施,Rust 是理想选择。
向量化查询引擎
GreptimeDB 基于 Apache Arrow 和 DataFusion 构建向量化查询引擎。Arrow 提供列式内存格式,DataFusion 提供 SQL 查询框架。Rust 对 SIMD 指令集的良好支持,使得向量化计算性能大幅提升。
异步运行时性能
Rust 的 tokio 异步运行时,支持百万级并发连接,内存占用极低。单机 GreptimeDB 可处理 每秒百万级数据点写入。
示例:Rust 高性能写入路径
// src/store/src/region.rs
pub struct RegionEngine {
memtable: Arc<Memtable>,
wal: Arc<Wal>,
sst_writer: Arc<SstWriter>,
}
impl RegionEngine {
pub async fn write(&self, rows: Vec<Row>) -> Result<WriteResponse> {
// 1. 写入 WAL(持久化保证)
self.wal.append(&rows).await?;
// 2. 写入 Memtable(内存索引)
for row in rows {
self.memtable.insert(row)?;
}
// 3. 触发刷盘(异步)
if self.memtable.size() > FLUSH_THRESHOLD {
self.flush_memtable();
}
Ok(WriteResponse::success())
}
fn flush_memtable(&self) {
let memtable = self.memtable.clone();
let sst_writer = self.sst_writer.clone();
tokio::spawn(async move {
// 后台异步刷盘,不阻塞写入
let sst = sst_writer.build_from_memtable(&memtable).await?;
sst.upload_to_object_storage().await?;
memtable.clear();
Ok(())
});
}
}
2.3 列存引擎:高效压缩稀疏列
宽事件数据每行可能有数百个字段,但大部分请求只使用其中一小部分。例如:
- 普通请求只有
method、path、status_code、duration - 特殊请求才有
user_id、session_id、error_stack
列存引擎优势:
高效压缩:每列数据类型相同,压缩率远超行存。例如
status_code列大量重复的200,使用 RLE(Run-Length Encoding)压缩后体积缩小 10 倍。稀疏列友好:未查询的列完全不需要加载。查询
SELECT status_code FROM logs,只读status_code列,不关心其他几百列。向量化计算:列式数据天然适合 SIMD 指令集加速。过滤
WHERE status_code != 200,可一次性处理 8 个值(AVX-256)。
GreptimeDB 的列存实现:
// src/storage/src/column.rs
pub struct ColumnWriter {
data_type: DataType,
encoder: Box<dyn Encoder>,
buffer: Vec<u8>,
}
impl ColumnWriter {
pub fn write_batch(&mut self, values: &[Value]) -> Result<usize> {
match self.data_type {
DataType::Int64 => {
// 使用 Delta + RLE 双重压缩
let int_values: &[i64] = values.try_into()?;
let delta_encoded = delta_encode(int_values);
let rle_encoded = rle_encode(&delta_encoded);
self.buffer.extend(rle_encoded);
}
DataType::String => {
// 使用字典编码 + LZ4
let dict = build_dictionary(values);
let indices = encode_with_dictionary(&dict, values);
let compressed = lz4_compress(&indices);
self.buffer.extend(compressed);
}
_ => unimplemented!(),
}
Ok(values.len())
}
}
2.4 对象存储优先:存算分离架构
传统 TSDB(如 Prometheus、InfluxDB)使用本地磁盘存储,扩展性差:
- 垂直扩展瓶颈:单机磁盘容量有限,无法突破
- 水平扩展复杂:需要手动分片、迁移数据、重新配置
GreptimeDB 从第一天就设计为 存算分离 架构:
- 存储层:对象存储(S3、GCS、Azure Blob)作为主存储
- 计算层:无状态 Frontend + Datanode,可独立扩展
存算分离的核心优势:
- 弹性扩展:流量高峰期加 Frontend 节点,存储在 S3 上自然增长,无需 resharding
- 成本降低:对象存储成本远低于本地 SSD(最高可降本 50 倍)
- 运维简化:无状态节点可随时扩缩容,故障自动恢复
写入路径:
Client → Frontend → Datanode → Memtable (内存)
↓
WAL (本地磁盘,保证持久性)
↓
SST (对象存储)
查询路径:
Client → Frontend → Metasrv (路由) → Datanode
↓
Cache (内存 + 本地磁盘)
↓ (未命中)
Object Storage (S3)
示例:配置对象存储
# config.toml
[storage]
type = "S3"
[storage.s3]
bucket = "greptimedb-data"
region = "us-east-1"
endpoint = "https://s3.amazonaws.com"
access_key_id = "AKIAIOSFODNN7EXAMPLE"
secret_access_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
[storage.cache]
# 分层缓存:内存 + 本地磁盘
memory_cache_size = "4GB"
disk_cache_path = "/var/cache/greptimedb"
disk_cache_size = "100GB"
三、核心特性深度解析
3.1 OpenTelemetry 原生支持
OpenTelemetry(OTel)已成为可观测性事实标准。GreptimeDB 原生支持 OTel 三大信号:
Metrics 接入:
# OTel Collector 配置
exporters:
otlp:
endpoint: "greptimedb:4317"
tls:
insecure: true
service:
pipelines:
metrics:
receivers: [otlp]
exporters: [otlp]
Logs 接入:
# OTel Collector logs pipeline
receivers:
filelog:
include: ["/var/log/*.log"]
exporters:
otlphttp:
endpoint: "http://greptimedb:4318/v1/logs"
service:
pipelines:
logs:
receivers: [filelog]
exporters: [otlphttp]
Traces 接入:
# OTel Collector traces pipeline
exporters:
otlp:
endpoint: "greptimedb:4317"
service:
pipelines:
traces:
receivers: [otlp]
exporters: [otlp]
写入后的自动转换:
OTel 数据写入 GreptimeDB 后,自动转换为宽事件表:
-- 自动生成的表结构
CREATE TABLE otel_metrics (
timestamp TIMESTAMP,
service_name STRING,
metric_name STRING,
metric_value DOUBLE,
attributes MAP<STRING, STRING>,
-- OTel Attributes 自动展开为列
host_name STRING,
region STRING,
version STRING,
...
);
CREATE TABLE otel_logs (
timestamp TIMESTAMP,
trace_id STRING,
span_id STRING,
severity_text STRING,
body STRING,
-- OTel Attributes 自动展开
service_name STRING,
host_name STRING,
...
);
CREATE TABLE otel_traces (
timestamp TIMESTAMP,
trace_id STRING,
span_id STRING,
parent_span_id STRING,
span_name STRING,
duration_ns INT64,
-- OTel Attributes 自动展开
service_name STRING,
http_method STRING,
http_route STRING,
...
);
3.2 SQL + PromQL 双查询引擎
GreptimeDB 同时支持 SQL 和 PromQL,满足不同场景:
SQL 适用场景:
- 跨信号 JOIN 查询
- 复杂聚合分析
- BI 报表生成
- 即席探索
PromQL 适用场景:
- Grafana 仪表盘
- 告警规则
- 传统 Prometheus 用户迁移
示例:PromQL 查询
# QPS 计算
rate(http_requests_total[5m])
# P99 延迟
histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))
# 错误率
sum(rate(http_requests_total{status=~"5.."}[5m]))
/ sum(rate(http_requests_total[5m]))
GreptimeDB 内部转换:
PromQL 查询会被解析并转换为 SQL 逻辑计划:
// src/query/src/promql/parser.rs
pub fn parse_promql_to_sql(query: &str) -> Result<LogicalPlan> {
// 1. 解析 PromQL AST
let ast = parse_promql(query)?;
// 2. 转换为 DataFusion SQL AST
let sql_ast = promql_to_sql(ast)?;
// 3. 生成逻辑计划
let plan = SqlToRel::new(&schema).sql_to_plan(sql_ast)?;
Ok(plan)
}
fn promql_to_sql(ast: PromQlAst) -> Result<Statement> {
match ast {
PromQlAst::Rate { metric, window } => {
// rate(metric[5m]) → SELECT time, value, (value - lag(value) OVER (ORDER BY time)) / 300 FROM metric
Ok(Statement::Query(Box::new(Query {
body: SetExpr::Select(Box::new(Select {
projection: vec![
Expr::Column("time".into()),
Expr::BinaryOp {
left: Box::new(Expr::Column("value".into())),
op: BinaryOperator::Minus,
right: Box::new(Expr::Function(Function {
name: ObjectName(vec![Ident::new("lag")]),
args: vec![FunctionArg::Unnamed(Expr::Column("value".into()))],
})),
},
},
from: vec![TableWithJoins {
relation: TableFactor::Table {
name: ObjectName(vec![Ident::new(&metric)]),
},
}],
..
})),
})))
}
_ => unimplemented!(),
}
}
3.3 Flow 引擎:流处理与物化视图
GreptimeDB 内置 Flow 引擎,支持:
- 连续查询(Continuous Query):实时聚合计算
- 物化视图(Materialized View):预计算结果缓存
- 流式处理(Stream Processing):实时数据转换
示例:实时错误率监控
-- 创建物化视图:每 1 分钟统计错误率
CREATE MATERIALIZED VIEW error_rate_mv
WITH (refresh_interval = '1 minute') AS
SELECT
time_bucket('1 minute', timestamp) AS time_window,
service_name,
endpoint,
SUM(CASE WHEN status_code >= 500 THEN 1 ELSE 0 END) * 1.0 / COUNT(*) AS error_rate
FROM http_requests
GROUP BY time_window, service_name, endpoint;
-- 查询物化视图(毫秒级响应)
SELECT * FROM error_rate_mv
WHERE error_rate > 0.05
ORDER BY time_window DESC;
Flow 引擎架构:
// src/flow/src/engine.rs
pub struct FlowEngine {
scheduler: Scheduler,
executor: Executor,
state_store: Arc<StateStore>,
}
impl FlowEngine {
pub async fn execute_flow(&self, flow: Flow) -> Result<()> {
// 1. 注册调度任务
self.scheduler.register(flow.clone()).await?;
// 2. 启动执行器
tokio::spawn(async move {
loop {
// 等待触发(时间窗口或数据到达)
let trigger = scheduler.wait_for_trigger().await;
// 执行查询
let result = executor.execute(flow.query()).await?;
// 写入物化视图
state_store.write(flow.target_table(), result).await?;
}
});
Ok(())
}
}
3.4 索引体系:全文、倒排、跳过索引
GreptimeDB 支持三种索引,加速不同查询模式:
全文索引(Fulltext Index):
用于文本搜索,如日志关键词检索。
-- 创建全文索引
CREATE FULLTEXT INDEX idx_log_message ON logs(log_message);
-- 全文搜索
SELECT * FROM logs
WHERE MATCH(log_message, 'error timeout')
ORDER BY timestamp DESC;
倒排索引(Inverted Index):
用于标签过滤,如按 service_name、region 筛选。
-- 创建倒排索引
CREATE INVERTED INDEX idx_service ON logs(service_name, region);
-- 倒排索引查询
SELECT * FROM logs
WHERE service_name = 'order-service' AND region = 'us-east-1';
跳过索引(Skipping Index):
用于数值范围过滤,如时间范围查询。
-- 自动创建时间跳过索引
CREATE TABLE metrics (
timestamp TIMESTAMP TIME INDEX,
value DOUBLE,
tag STRING,
);
-- 时间范围查询自动使用跳过索引
SELECT * FROM metrics
WHERE timestamp BETWEEN '2026-07-30 00:00:00' AND '2026-07-31 00:00:00';
索引实现原理:
// src/storage/src/index/mod.rs
pub enum Index {
Fulltext(FulltextIndex),
Inverted(InvertedIndex),
Skipping(SkippingIndex),
}
pub struct FulltextIndex {
tokenizer: Tokenizer,
postings: HashMap<String, RoaringBitmap>, // term → doc_ids
}
impl FulltextIndex {
pub fn search(&self, query: &str) -> RoaringBitmap {
let tokens = self.tokenizer.tokenize(query);
let mut result = RoaringBitmap::new();
for token in tokens {
if let Some(doc_ids) = self.postings.get(&token) {
result |= doc_ids; // OR 操作:包含任一 term 的文档
}
}
result
}
}
pub struct InvertedIndex {
// 标签值 → doc_ids 映射
postings: HashMap<LabelValue, RoaringBitmap>,
}
impl InvertedIndex {
pub fn filter(&self, label: &LabelValue) -> RoaringBitmap {
self.postings.get(label).cloned().unwrap_or_default()
}
}
pub struct SkippingIndex {
// 每个 SST 文件的最小/最大值
min_max: Vec<(Timestamp, Timestamp)>,
}
impl SkippingIndex {
pub fn skip_files(&self, range: &Range<Timestamp>) -> Vec<usize> {
// 返回需要扫描的文件索引
self.min_max
.iter()
.enumerate()
.filter(|(_, (min, max))| range.overlaps(min, max))
.map(|(i, _)| i)
.collect()
}
}
四、性能优化:从写入到查询的极致压榨
4.1 写入优化:三阶段流水线
GreptimeDB 的写入路径分为三个阶段:
阶段一:WAL 持久化
数据先写入 WAL(预写日志),保证崩溃恢复。WAL 使用本地磁盘,顺序写入,吞吐可达 100 MB/s。
阶段二:Memtable 写入
WAL 写入成功后,数据进入 Memtable(内存跳表或 LSM-Tree)。Memtable 支持并发读写,无锁设计。
阶段三:异步刷盘
Memtable 达到阈值后,后台线程将其刷成 SST 文件,上传到对象存储。刷盘过程不阻塞写入。
写入性能实测:
// 压测代码
use greptimedb_client::Client;
#[tokio::main]
async fn main() {
let client = Client::connect("greptimedb:4001").await?;
// 批量写入
let rows: Vec<Row> = (0..100_000).map(|i| Row {
timestamp: Utc::now(),
service_name: format!("service-{}", i % 10),
metric_value: rand::random(),
}).collect();
let start = Instant::now();
client.write_batch("metrics", rows).await?;
let elapsed = start.elapsed();
println!("Written 100k rows in {:?}", elapsed);
// 输出:Written 100k rows in 1.2s (~83k rows/s)
}
单机写入吞吐:
- 简单 metric:100 万行/秒
- 复杂宽事件:50 万行/秒
- 单行 100 字段:10 万行/秒
4.2 查询优化:向量化执行 + 缓存分层
向量化执行:
GreptimeDB 使用 Apache Arrow 的列式内存格式,配合 DataFusion 的向量化执行引擎:
// 向量化过滤示例
use arrow::array::Int64Array;
use arrow::compute::filter;
fn filter_batch(batch: &RecordBatch, predicate: &BooleanArray) -> RecordBatch {
let mut filtered_columns = vec![];
for column in batch.columns() {
let filtered = filter(column, predicate)?;
filtered_columns.push(filtered);
}
RecordBatch::try_new(batch.schema(), filtered_columns)?
}
// 批量处理,一次过滤 1024 行
let batch: RecordBatch = read_from_sst()?; // 1024 行一批
let predicate: BooleanArray = compute_predicate(&batch)?; // 向量化计算
let filtered = filter_batch(&batch, &predicate)?; // 一次过滤整批
缓存分层:
┌────────────────────────────────────────┐
│ Query Request │
└────────────────┬───────────────────────┘
│
┌───────▼────────┐
│ Result Cache │ ← 查询结果缓存(热点查询)
└───────┬────────┘
│ (miss)
┌───────▼────────┐
│ Metadata Cache│ ← 表结构、分区信息
└───────┬────────┘
│ (miss)
┌───────▼────────┐
│ SST Cache │ ← 热点 SST 文件(本地磁盘)
└───────┬────────┘
│ (miss)
┌───────▼────────┐
│ Object Storage│ ← S3/GCS
└────────────────┘
缓存配置:
# config.toml
[query.cache]
# 查询结果缓存
result_cache_size = "1GB"
result_cache_ttl = "5m"
# 元数据缓存
metadata_cache_size = "100MB"
# SST 文件缓存(本地磁盘)
sst_cache_path = "/var/cache/greptimedb/sst"
sst_cache_size = "50GB"
4.3 压缩策略:列级自适应
GreptimeDB 根据列的数据类型和分布,自动选择最优压缩算法:
| 数据类型 | 压缩算法 | 压缩比 |
|---|---|---|
| 时间戳 | Delta + RLE | 10:1 |
| 状态码 | RLE + BitPacking | 20:1 |
| 字符串(低基数) | 字典编码 | 8:1 |
| 字符串(高基数) | LZ4 / Zstd | 3:1 |
| 数值(连续) | Delta + RLE | 15:1 |
| 数值(随机) | BitPacking | 4:1 |
自适应压缩选择:
// src/storage/src/compaction/compressor.rs
pub fn choose_compression(data_type: DataType, cardinality: usize) -> Compression {
match data_type {
DataType::Timestamp => Compression::DeltaRle,
DataType::Int64 if cardinality < 100 => Compression::Dict,
DataType::Int64 => Compression::BitPacking,
DataType::String if cardinality < 1000 => Compression::Dict,
DataType::String => Compression::Zstd,
_ => Compression::None,
}
}
五、实战案例:从 Prometheus + Loki 迁移到 GreptimeDB
5.1 迁移动机
某客户生产环境:
- Prometheus:3 节点集群,存储 1 年指标数据,占用 10TB 本地 SSD
- Loki:2 节点集群,存储 30 天日志,占用 5TB 对象存储
- Jaeger:1 节点,存储 7 天链路,占用 500GB
痛点:
- 跨信号查询困难:排查问题需要在三个 UI 之间切换
- 成本高:Prometheus 本地 SSD 成本昂贵
- 运维复杂:三套系统、三套告警配置、三套备份策略
5.2 迁移方案
阶段一:双写迁移
保留原系统,新增 GreptimeDB 作为 OTel Collector 的第二个输出:
# OTel Collector 配置
exporters:
prometheus_remotewrite:
endpoint: "http://prometheus:9090/api/v1/write"
otlp:
endpoint: "greptimedb:4317"
loki:
endpoint: "http://loki:3100/loki/api/v1/push"
service:
pipelines:
metrics:
receivers: [otlp]
exporters: [prometheus_remotewrite, otlp] # 双写
logs:
receivers: [otlp]
exporters: [loki, otlp] # 双写
阶段二:查询切换
Grafana 数据源从 Prometheus/Loki 切换到 GreptimeDB:
# Grafana 数据源配置
apiVersion: 1
datasources:
- name: GreptimeDB
type: greptimedb
access: proxy
url: http://greptimedb:4000
isDefault: true
阶段三:下线旧系统
验证 GreptimeDB 数据完整性后,下线 Prometheus 和 Loki。
5.3 迁移效果
| 指标 | 迁移前 | 迁移后 | 改善 |
|---|---|---|---|
| 存储成本 | $5000/月 | $1500/月 | -70% |
| 查询延迟 | 2-5 秒(跨系统) | 0.5 秒(单系统) | -75% |
| 运维复杂度 | 3 套系统 | 1 套系统 | -66% |
| 跨信号查询 | 不支持 | 支持 | 质的飞跃 |
5.4 完整迁移代码示例
Python 迁移脚本:
import requests
import time
from datetime import datetime, timedelta
# Prometheus API
PROM_URL = "http://prometheus:9090"
# GreptimeDB API
GREP_URL = "http://greptimedb:4000"
def migrate_metrics(start: datetime, end: datetime, step: str = "15s"):
"""迁移 Prometheus 指标到 GreptimeDB"""
query = 'http_requests_total'
# 1. 从 Prometheus 查询
response = requests.get(
f"{PROM_URL}/api/v1/query_range",
params={
"query": query,
"start": start.timestamp(),
"end": end.timestamp(),
"step": step,
}
)
data = response.json()["data"]["result"]
# 2. 转换为 GreptimeDB 写入格式
rows = []
for series in data:
metric = series["metric"]
for value in series["values"]:
timestamp, val = value
rows.append({
"timestamp": datetime.fromtimestamp(timestamp).isoformat(),
"service_name": metric.get("service", "unknown"),
"endpoint": metric.get("endpoint", "unknown"),
"value": float(val),
})
# 3. 写入 GreptimeDB
requests.post(
f"{GREP_URL}/v1/influxdb/write?db=public",
data="\n".join([
f"http_requests,service_name={r['service_name']},endpoint={r['endpoint']} "
f"value={r['value']} {int(datetime.fromisoformat(r['timestamp']).timestamp() * 1e9)}"
for r in rows
])
)
print(f"Migrated {len(rows)} rows")
# 执行迁移
migrate_metrics(
start=datetime.now() - timedelta(days=30),
end=datetime.now()
)
六、能力边界与冷思考
6.1 不适用场景
OLTP 事务处理
GreptimeDB 是列存引擎,不适合高并发点更新、事务场景。这类需求应选择 MySQL、PostgreSQL。
严格 ACID 保证
GreptimeDB 侧重高吞吐写入和查询,不提供严格的 ACID 事务。写入后数据可能在数秒内才对查询可见。
超低延迟点查询
虽然 GreptimeDB 优化了点查询性能,但相比 KV 存储(如 Redis、RocksDB),延迟仍有差距。对延迟要求 < 10ms 的场景,应选择 KV 存储。
6.2 与竞品对比
| 特性 | GreptimeDB | Prometheus + Thanos | VictoriaMetrics | TimescaleDB |
|---|---|---|---|---|
| 数据类型 | Metrics, Logs, Traces | Metrics | Metrics | Metrics, Time Series |
| 查询语言 | SQL + PromQL | PromQL | MetricsQL | SQL |
| 存储 | 对象存储优先 | 本地 + 对象存储 | 本地 | PostgreSQL |
| 跨信号 JOIN | ✅ | ❌ | ❌ | ❌ |
| 流处理 | ✅ Flow 引擎 | ❌ | ❌ | ❌ |
| 开源协议 | Apache 2.0 | Apache 2.0 | Apache 2.0 | Apache 2.0 |
选型建议:
- 选择 GreptimeDB:需要统一存储指标、日志、链路;需要跨信号 JOIN;需要流处理和物化视图
- 选择 Prometheus + Thanos:已有 Prometheus 生态,只需长期存储和全局查询
- 选择 VictoriaMetrics:只需存储指标,追求极致写入性能
- 选择 TimescaleDB:需要 PostgreSQL 生态,支持 GIS、JSONB 等高级特性
6.3 生产部署建议
单机部署(开发/测试):
docker run -d \
--name greptimedb \
-p 4000-4003:4000-4003 \
-v $(pwd)/greptimedb_data:/greptimedb_data \
greptime/greptimedb:latest standalone start
集群部署(生产):
# docker-compose.yml
version: '3.8'
services:
metasrv:
image: greptime/greptimedb:latest
command: metasrv start --bind-addr 0.0.0.0:3002
ports:
- "3002:3002"
datanode1:
image: greptime/greptimedb:latest
command: datanode start --metasrv-addr metasrv:3002
depends_on: [metasrv]
datanode2:
image: greptime/greptimedb:latest
command: datanode start --metasrv-addr metasrv:3002
depends_on: [metasrv]
frontend:
image: greptime/greptimedb:latest
command: frontend start --metasrv-addr metasrv:3002
ports:
- "4000-4003:4000-4003"
depends_on: [metasrv]
资源建议:
| 规模 | Frontend | Datanode | Metasrv | 存储 |
|---|---|---|---|---|
| 小型(<1TB/月) | 2 核 4GB | 4 核 16GB + 100GB SSD | 2 核 4GB | S3 |
| 中型(1-10TB/月) | 4 核 8GB × 2 | 8 核 32GB + 500GB SSD × 3 | 4 核 8GB | S3 |
| 大型(>10TB/月) | 8 核 16GB × 4 | 16 核 64GB + 1TB SSD × 6 | 8 核 16GB | S3 |
七、总结与展望
GreptimeDB 代表了可观测性基础设施的 Observability 2.0 趋势:
- 统一存储:一个引擎存储指标、日志、链路,消灭三套系统的复杂性
- 宽事件模型:所有信号都是带时间戳的事件,支持跨信号 JOIN
- 存算分离:对象存储优先,计算节点无状态,弹性扩展
- Rust 性能:列存引擎 + 向量化查询,单机百万级写入吞吐
- 流处理能力:Flow 引擎支持物化视图、连续查询
未来方向:
- AI 原生集成:支持 OTel GenAI 规范,存储 LLM 推理日志、Token 使用量
- 更智能的索引:自动识别热点查询,预建索引
- 边缘-云协同:GreptimeDB 已支持 ARM 和 RISC-V,可在边缘设备运行,云端聚合
一句话总结:
GreptimeDB 不是「更好的 Prometheus」,而是「可观测性数据的统一数据库」——当你的指标、日志、链路都存在一张宽事件表里,查询再也不用跨系统了。