编程 GreptimeDB 深度拆解:从「三套系统」到 Observability 2.0 —— Rust 列存引擎、存算分离架构与宽事件统一查询实战

2026-07-31 07:44:49 +0800 CST views 18

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 指标、日志、链路

传统架构下,跨信号查询需要:

  1. 在 Prometheus 中找到异常时间段的 service_nameendpoint
  2. 在 Loki 中搜索对应时间段的日志
  3. 在 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)│
                    └────────────────┘

四大组件职责:

  1. Frontend:无状态入口,支持 OTel、Prometheus Remote Write、MySQL、PostgreSQL、gRPC、InfluxDB、Loki 等协议。内置分布式查询引擎,处理 SQL 和 PromQL 查询。

  2. Datanode:存储引擎节点,负责数据持久化。包含 WAL(预写日志)、Memtable(内存表)、SST(Sorted String Table)、Cache(缓存)、Compaction(压缩合并)和索引(全文索引、倒排索引、跳过索引)。

  3. Metasrv:元数据中心,管理表结构、分区路由、自动重分区、安全策略。底层可插拔 KV 存储(etcd 或 RDS)。

  4. Flownode(可选):流式计算节点,支持 Flow 引擎、连续查询和物化视图。

2.2 为什么选择 Rust?

GreptimeDB 用 Rust 编写,核心优势:

内存安全,零成本抽象
Rust 的所有权系统在编译期杜绝了空指针、悬垂指针、数据竞争等内存安全问题,而运行时性能接近 C/C++。对于数据库这种对性能极其敏感的基础设施,Rust 是理想选择。

向量化查询引擎
GreptimeDB 基于 Apache ArrowDataFusion 构建向量化查询引擎。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 列存引擎:高效压缩稀疏列

宽事件数据每行可能有数百个字段,但大部分请求只使用其中一小部分。例如:

  • 普通请求只有 methodpathstatus_codeduration
  • 特殊请求才有 user_idsession_iderror_stack

列存引擎优势:

  1. 高效压缩:每列数据类型相同,压缩率远超行存。例如 status_code 列大量重复的 200,使用 RLE(Run-Length Encoding)压缩后体积缩小 10 倍。

  2. 稀疏列友好:未查询的列完全不需要加载。查询 SELECT status_code FROM logs,只读 status_code 列,不关心其他几百列。

  3. 向量化计算:列式数据天然适合 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,可独立扩展

存算分离的核心优势:

  1. 弹性扩展:流量高峰期加 Frontend 节点,存储在 S3 上自然增长,无需 resharding
  2. 成本降低:对象存储成本远低于本地 SSD(最高可降本 50 倍
  3. 运维简化:无状态节点可随时扩缩容,故障自动恢复

写入路径:

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_nameregion 筛选。

-- 创建倒排索引
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 + RLE10:1
状态码RLE + BitPacking20:1
字符串(低基数)字典编码8:1
字符串(高基数)LZ4 / Zstd3:1
数值(连续)Delta + RLE15:1
数值(随机)BitPacking4: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

痛点:

  1. 跨信号查询困难:排查问题需要在三个 UI 之间切换
  2. 成本高:Prometheus 本地 SSD 成本昂贵
  3. 运维复杂:三套系统、三套告警配置、三套备份策略

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 与竞品对比

特性GreptimeDBPrometheus + ThanosVictoriaMetricsTimescaleDB
数据类型Metrics, Logs, TracesMetricsMetricsMetrics, Time Series
查询语言SQL + PromQLPromQLMetricsQLSQL
存储对象存储优先本地 + 对象存储本地PostgreSQL
跨信号 JOIN
流处理✅ Flow 引擎
开源协议Apache 2.0Apache 2.0Apache 2.0Apache 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]

资源建议:

规模FrontendDatanodeMetasrv存储
小型(<1TB/月)2 核 4GB4 核 16GB + 100GB SSD2 核 4GBS3
中型(1-10TB/月)4 核 8GB × 28 核 32GB + 500GB SSD × 34 核 8GBS3
大型(>10TB/月)8 核 16GB × 416 核 64GB + 1TB SSD × 68 核 16GBS3

七、总结与展望

GreptimeDB 代表了可观测性基础设施的 Observability 2.0 趋势

  1. 统一存储:一个引擎存储指标、日志、链路,消灭三套系统的复杂性
  2. 宽事件模型:所有信号都是带时间戳的事件,支持跨信号 JOIN
  3. 存算分离:对象存储优先,计算节点无状态,弹性扩展
  4. Rust 性能:列存引擎 + 向量化查询,单机百万级写入吞吐
  5. 流处理能力:Flow 引擎支持物化视图、连续查询

未来方向:

  • AI 原生集成:支持 OTel GenAI 规范,存储 LLM 推理日志、Token 使用量
  • 更智能的索引:自动识别热点查询,预建索引
  • 边缘-云协同:GreptimeDB 已支持 ARM 和 RISC-V,可在边缘设备运行,云端聚合

一句话总结:

GreptimeDB 不是「更好的 Prometheus」,而是「可观测性数据的统一数据库」——当你的指标、日志、链路都存在一张宽事件表里,查询再也不用跨系统了。


参考资料

推荐文章

网站日志分析脚本
2024-11-19 03:48:35 +0800 CST
robots.txt 的写法及用法
2024-11-19 01:44:21 +0800 CST
全新 Nginx 在线管理平台
2024-11-19 04:18:33 +0800 CST
HTML + CSS 实现微信钱包界面
2024-11-18 14:59:25 +0800 CST
Vue3中如何处理权限控制?
2024-11-18 05:36:30 +0800 CST
如何实现生产环境代码加密
2024-11-18 14:19:35 +0800 CST
程序员茄子在线接单