编程 GreptimeDB 深度实战:当可观测性告别「三件套」——从宽事件统一引擎到存算分离、Flow 流处理与 PB 级日检索亚秒的生产级完全指南(2026)

2026-06-17 10:58:30 +0800 CST views 6

GreptimeDB 深度实战:当可观测性告别「三件套」——从宽事件统一引擎到存算分离、Flow 流处理与 PB 级日检索亚秒的生产级完全指南(2026)

一、背景:可观测性的「三座大山」与统一引擎的必然

如果你做过生产级可观测性平台,一定对这套架构不陌生:

  • Prometheus / Thanos / Mimir 负责指标
  • Loki / Elasticsearch 负责日志
  • Jaeger / Tempo 负责链路追踪

三套系统意味着三套存储层、三套查询语言(PromQL / LogQL / TraceQL)、三套采集 Agent、三套告警配置、三套 Dashboard。更致命的是,当故障发生时,你需要在三个系统之间跳转——先在 Grafana 看指标飙高,切到 Loki 搜日志,再到 Jaeger 追链路,手动在脑海中关联时间线。这个过程,5 分钟算快,30 分钟不稀奇。

Observability 2.0 的核心洞察很简单:指标、日志、链路,本质上都是带时间戳的事件(Wide Events)。既然都是事件,为什么不用一个引擎来存、一个查询语言来查?

GreptimeDB 就是这个思路的工程实现。它用 Rust 写的列式引擎,把三种信号统一为宽事件模型,以对象存储(S3 / GCS / Azure Blob)为主存储,支持 SQL + PromQL 双查询语言,内置 Flow 流处理引擎做指标派生。不是「三个数据库拼在一起」,而是「一个引擎从头设计」。

本文会从架构原理、存储引擎、查询引擎、Flow 流处理、部署实战、性能调优六个维度,把 GreptimeDB 彻底拆开,让你读完就能在生产环境落地。


二、核心概念:宽事件模型与 Observability 2.0

2.1 传统可观测性的根本问题

传统三件套的问题不是「不好用」,而是数据模型不统一

信号类型传统存储数据模型查询语言
MetricsPrometheus时间序列(name + labels → value)PromQL
LogsLoki / ES半结构化文本LogQL / Query DSL
TracesJaeger / TempoSpan 树TraceQL

这导致三个根本问题:

  1. 跨信号关联需要人工对齐:指标异常 → 搜日志 → 追链路,每一步都是手动切换
  2. 预聚合丢细节:Prometheus 的 counter / histogram 在采集时就聚合了,原始事件已不可追溯
  3. 存储成本线性爆炸:三套系统各管各的存储,冷热分层策略不同,运维复杂度 3x

2.2 宽事件模型:万物皆 Event

GreptimeDB 的核心数据模型是 Wide Event——每一行就是一个带时间戳的事件,列可以任意多:

CREATE TABLE http_requests (
    ts TIMESTAMP TIME INDEX,
    service STRING,
    endpoint STRING,
    status_code INT,
    latency_ms DOUBLE,
    trace_id STRING,
    span_id STRING,
    request_body STRING,
    user_id STRING,
    region STRING,
    -- ... 可以有数百个字段
    PRIMARY KEY (service, endpoint)
) ENGINE=metric WITH (
    append_mode = 'true',
    merge_mode = 'last_row'
);

一个 HTTP 请求事件,同时包含:

  • 指标信号:latency_ms、status_code 可以聚合为 P99 延迟、错误率
  • 日志信号:request_body 是日志内容
  • 链路信号:trace_id、span_id 关联到 Trace

一条 SQL 就能跨信号关联:

-- 找出延迟超过 500ms 的请求,同时查看对应的 trace 信息和请求体
SELECT
    ts, service, endpoint, latency_ms,
    trace_id, span_id, request_body
FROM http_requests
WHERE latency_ms > 500
  AND ts > NOW() - INTERVAL '1 hour'
ORDER BY latency_ms DESC
LIMIT 100;

这在传统架构里需要三条查询、三个系统。

2.3 为什么是列存而不是行存?

宽事件表每行可能有 200+ 个字段,但单次查询通常只涉及 5-10 个。列存引擎只读取查询涉及的列,I/O 量可以降一个数量级。更重要的是,列存对同类数据的压缩效率远高于行存——比如 status_code 列全是 200/404/500,用字典编码+RLE 可以压到极小。

GreptimeDB 的列存基于 Apache Parquet 格式,配合 Rust 的零拷贝反序列化,查询路径几乎没有不必要的内存分配。


三、架构分析:存算分离与四组件分布式模型

3.1 整体架构

GreptimeDB 有两种运行模式:

  • Standalone:单进程,适合开发和小规模部署
  • Distributed:四组件独立扩缩容,适合生产

分布式模式的核心组件:

                    ┌─────────────────┐
                    │    Frontend     │ ← 无状态,水平扩展
                    │  (协议适配层)    │
                    └────────┬────────┘
                             │
              ┌──────────────┼──────────────┐
              │              │              │
      ┌───────▼──────┐ ┌────▼─────┐ ┌──────▼──────┐
      │   Datanode   │ │ Metasrv  │ │  Flownode   │
      │ (存储+计算)   │ │(元数据+路由)│ │ (流处理+MV) │
      └───────┬──────┘ └──────────┘ └─────────────┘
              │
      ┌───────▼──────┐
      │ Object Store │ ← S3/GCS/Azure Blob
      │  (主存储)     │
      └──────────────┘

3.2 Frontend:协议适配与查询路由

Frontend 是无状态层,负责:

  1. 协议适配:同时支持 OpenTelemetry gRPC、Prometheus Remote Write、MySQL 协议、PostgreSQL 协议、InfluxDB Line Protocol、Elasticsearch API、Loki API
  2. 查询解析与优化:把 SQL / PromQL 解析为逻辑计划,通过 DataFusion 优化器生成物理计划
  3. 分布式查询调度:把查询拆分到对应 Datanode,聚合结果返回

关键设计:Frontend 完全无状态,可以随意扩缩容。高并发场景下(比如 AI Agent 批量查询),加 Frontend 实例就行。

# Frontend 配置示例
frontend:
  http_addr: "0.0.0.0:4000"
  grpc_addr: "0.0.0.0:4001"
  mysql_addr: "0.0.0.0:4002"
  postgres_addr: "0.0.0.0:4003"
  metasrv_addr: "127.0.0.1:3002"
  
  # 连接池配置
  query_timeout: "30s"
  max_concurrent_queries: 1024

3.3 Datanode:存储引擎的核心

Datanode 是 GreptimeDB 的存储和计算核心,每个 Datanode 管理若干 Region(数据分片)。

写入路径

写入请求 → WAL(预写日志)→ Memtable(内存表)→ Flush → SST(Parquet 文件)→ Object Store

关键设计点

  1. WAL 保证持久性:写入先记 WAL,确认后才返回成功。WAL 存储可配置为本地磁盘或对象存储
  2. Memtable 是行存+列存的混合体:写入时按行追加,查询时按列读取
  3. Flush 是异步的:Memtable 达到阈值后异步刷盘,不阻塞写入
  4. Compaction 是多层合并:类似 LSM-Tree,小文件合并为大文件,优化查询性能
// Datanode 的核心写入逻辑(简化版)
pub async fn write_region(
    region_id: RegionId,
    rows: Rows,
) -> Result<WriteResponse> {
    // 1. 写入 WAL
    let wal_entry = wal.append(region_id, &rows).await?;
    
    // 2. 写入 Memtable
    memtable.write(region_id, rows)?;
    
    // 3. 检查是否需要 Flush
    if memtable.should_flush() {
        tokio::spawn(async move {
            flush_region(region_id).await;
        });
    }
    
    Ok(WriteResponse { 
        success: true, 
        last_entry_id: wal_entry.id 
    })
}

3.4 Metasrv:大脑与调度器

Metasrv 管理集群的元数据,职责包括:

  1. Region 路由:哪个 Region 在哪个 Datanode 上?Frontend 通过 Metasrv 查询路由
  2. 自动重分区:当某个 Region 写入量过大,自动拆分为多个 Region
  3. Autopilot:自动调优——根据负载模式调整 Flush 频率、Compaction 策略
  4. 安全与权限:用户认证、RBAC

Metasrv 底层使用可插拔的 KV 存储(etcd 或 RDS),保证元数据的一致性。

3.5 Flownode:流计算引擎

Flownode 是可选组件,负责持续流计算和物化视图。这是 GreptimeDB 区别于传统时序数据库的核心能力——不仅存数据,还能在数据入库时实时计算派生指标。

原始事件 → Flownode → 派生指标
  (高基数)              (低基数,预聚合)

典型场景:每秒产生 10 万条 HTTP 请求事件(高基数),Flownode 实时聚合为每分钟 P99 延迟、错误率等指标(低基数),供 Dashboard 直接查询。


四、代码实战:从部署到全链路可观测

4.1 快速部署:单机模式

# Docker 一键启动
docker run -p 127.0.0.1:4000-4003:4000-4003 \
  -v "$(pwd)/greptimedb_data:/greptimedb_data" \
  --name greptime --rm \
  greptime/greptimedb:latest standalone start \
  --http-addr 0.0.0.0:4000 \
  --rpc-bind-addr 0.0.0.0:4001 \
  --mysql-addr 0.0.0.0:4002 \
  --postgres-addr 0.0.0.0:4003

端口说明:

  • 4000:HTTP API + Dashboard
  • 4001:gRPC(OTel、内部通信)
  • 4002:MySQL 协议
  • 4003:PostgreSQL 协议

4.2 分布式部署:Kubernetes + Helm

生产环境必须分布式部署。用 Helm 是最省心的方式:

# 添加 Helm 仓库
helm repo add greptime https://greptimeteam.github.io/helm-charts/
helm repo update

# 安装(含 etcd)
helm install greptimedb greptime/greptimedb-cluster \
  --namespace greptimedb --create-namespace \
  --set meta.etcd.endpoints='{etcd.etcd-cluster:2379}' \
  --set storage.s3.bucket=your-bucket \
  --set storage.s3.root=/greptimedb \
  --set storage.s3.accessKeyId=YOUR_AK \
  --set storage.s3.secretAccessKey=YOUR_SK \
  --set storage.s3.region=us-east-1

关键配置——自定义 values.yaml:

# values.yaml - 生产级配置
frontend:
  replicas: 3
  resources:
    requests:
      cpu: "2"
      memory: "4Gi"
    limits:
      cpu: "4"
      memory: "8Gi"
  service:
    type: LoadBalancer

datanode:
  replicas: 5
  resources:
    requests:
      cpu: "4"
      memory: "16Gi"
    limits:
      cpu: "8"
      memory: "32Gi"
  storage:
    wal:
      type: "local"
      local:
        path: "/greptimedb/wal"
        size: "100Gi"
    data:
      type: "s3"
      s3:
        bucket: "your-bucket"
        root: "/greptimedb/data"

metasrv:
  replicas: 3
  resources:
    requests:
      cpu: "1"
      memory: "2Gi"
  etcd:
    endpoints:
      - "etcd-0.etcd:2379"
      - "etcd-1.etcd:2379"
      - "etcd-2.etcd:2379"

flownode:
  replicas: 2
  resources:
    requests:
      cpu: "2"
      memory: "8Gi"

4.3 OpenTelemetry 采集接入

GreptimeDB 原生支持 OpenTelemetry 协议,配置 OTel Collector 直接写入:

# otel-collector-config.yaml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: "0.0.0.0:4317"
      http:
        endpoint: "0.0.0.0:4318"

processors:
  batch:
    timeout: "5s"
    send_batch_size: 8192

exporters:
  otlphttp/greptime:
    endpoint: "http://greptime-frontend:4000/otlp"
    headers:
      "X-Greptime-DB-Name": "public"
      # 如果开启了认证
      # "Authorization": "Bearer your-token"

service:
  pipelines:
    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlphttp/greptime]
    logs:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlphttp/greptime]
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlphttp/greptime]

部署 OTel Collector:

kubectl apply -f otel-collector-config.yaml

kubectl run otel-collector --image=otel/opentelemetry-collector-contrib:latest \
  --configmap=otel-collector-config

4.4 Prometheus 迁移:无缝替换 Thanos/Mimir

已有 Prometheus 的场景,可以先用 Remote Write 双写,逐步迁移:

# prometheus.yml
remote_write:
  - url: "http://greptime-frontend:4000/v1/prometheus/write"
    # 同时保留原有 Thanos 写入
  - url: "http://thanos-receive:10908/api/v1/receive"

GreptimeDB 完全兼容 Prometheus Remote Write 协议,你的 PromQL 查询也直接可用:

# PromQL 查询通过 HTTP API
curl "http://greptime-frontend:4000/v1/prometheus/query?query=http_requests_total{service='api-gateway'}"

4.5 数据建模实战:一个完整的可观测场景

以微服务 API 网关为例,建模一个统一的宽事件表:

-- 创建数据库
CREATE DATABASE IF NOT EXISTS observability;

-- API 请求宽事件表
CREATE TABLE observability.api_events (
    -- 时间列(必须)
    ts TIMESTAMP TIME INDEX,

    -- 上下文字段
    service STRING,
    endpoint STRING,
    method STRING,
    status_code INT,
    latency_ms DOUBLE,

    -- 链路关联
    trace_id STRING,
    span_id STRING,
    parent_span_id STRING,

    -- 日志内容
    request_headers JSON,
    request_body STRING,
    response_body STRING,
    error_message STRING,

    -- 业务字段
    user_id STRING,
    tenant_id STRING,
    region STRING,
    az STRING,

    -- 基础设施字段
    pod_name STRING,
    node_name STRING,
    container_id STRING,

    PRIMARY KEY (service, endpoint, region)
) ENGINE=metric WITH (
    append_mode = 'true'
);

为什么用 JSON 类型存 headers? GreptimeDB 原生支持 JSON 类型,可以用 ->> 运算符直接查询 JSON 字段,不需要提前展开所有 header 键:

-- 查询特定 User-Agent 的请求
SELECT ts, service, endpoint, latency_ms,
       request_headers->>'user-agent' AS user_agent
FROM observability.api_events
WHERE request_headers->>'user-agent' LIKE '%curl%'
  AND ts > NOW() - INTERVAL '30 minutes';

4.6 索引策略:全文索引 + 倒排索引 + 跳跃索引

GreptimeDB 支持三种索引,针对不同查询模式:

-- 为 api_events 添加索引
ALTER TABLE observability.api_events ADD FULLTEXT INDEX idx_error_msg (error_message);
ALTER TABLE observability.api_events ADD INVERTED INDEX idx_trace (trace_id);
ALTER TABLE observability.api_events ADD SKIPPING INDEX idx_status (status_code);

索引选择指南

索引类型适用场景示例
Fulltext全文搜索,模糊匹配error_message LIKE '%timeout%'
Inverted精确匹配,高基数字段WHERE trace_id = 'abc123'
Skipping范围查询,低基数字段WHERE status_code = 500

五、Flow 引擎:实时流处理与物化视图

5.1 Flow Task 的核心概念

Flow 是 GreptimeDB 内置的流计算引擎,核心能力是在数据写入时实时触发计算,将结果写入目标表。本质是一个持续查询

源表(原始事件)→ Flow Task → 目标表(派生指标)

与 Kafka + Flink 的区别:不需要额外的消息队列和计算集群,Flow 运行在 GreptimeDB 内部的 Flownode 上,数据零拷贝。

5.2 实战:从原始事件派生 P99 延迟指标

-- 创建目标表:每分钟 P99 延迟
CREATE TABLE observability.latency_p99 (
    ts TIMESTAMP TIME INDEX,
    service STRING,
    endpoint STRING,
    p99_latency DOUBLE,
    request_count BIGINT,
    PRIMARY KEY (service, endpoint)
) ENGINE=metric;

-- 创建 Flow Task
CREATE FLOW calculate_p99
SINK TO observability.latency_p99
AS
SELECT
    date_bin(INTERVAL '1 minute', ts) AS ts,
    service,
    endpoint,
    approx_percentile_cont(latency_ms, 0.99) AS p99_latency,
    count(*) AS request_count
FROM observability.api_events
GROUP BY
    date_bin(INTERVAL '1 minute', ts),
    service,
    endpoint;

approx_percentile_cont 是 GreptimeDB 的高性能近似分位数函数,基于 T-Digest 算法,精度 99%+,内存占用远小于精确排序。

5.3 多级聚合:秒级 → 分钟级 → 小时级

实际生产中,Dashboard 通常需要不同时间粒度的指标。可以链式 Flow:

-- 分钟级错误率
CREATE TABLE observability.error_rate_minute (
    ts TIMESTAMP TIME INDEX,
    service STRING,
    error_rate DOUBLE,
    total_requests BIGINT,
    error_requests BIGINT,
    PRIMARY KEY (service)
) ENGINE=metric;

CREATE FLOW calc_error_rate_min
SINK TO observability.error_rate_minute
AS
SELECT
    date_bin(INTERVAL '1 minute', ts) AS ts,
    service,
    cast(count_if(status_code >= 500) AS DOUBLE) / count(*) AS error_rate,
    count(*) AS total_requests,
    count_if(status_code >= 500) AS error_requests
FROM observability.api_events
GROUP BY
    date_bin(INTERVAL '1 minute', ts),
    service;

-- 小时级错误率(从分钟级派生,而非原始事件)
CREATE TABLE observability.error_rate_hour (
    ts TIMESTAMP TIME INDEX,
    service STRING,
    error_rate DOUBLE,
    avg_requests_per_minute DOUBLE,
    PRIMARY KEY (service)
) ENGINE=metric;

CREATE FLOW calc_error_rate_hour
SINK TO observability.error_rate_hour
AS
SELECT
    date_bin(INTERVAL '1 hour', ts) AS ts,
    service,
    avg(error_rate) AS error_rate,
    avg(total_requests) AS avg_requests_per_minute
FROM observability.error_rate_minute
GROUP BY
    date_bin(INTERVAL '1 hour', ts),
    service;

为什么从分钟级派生而非原始事件? 原始事件基数太高(每秒 10 万条),小时级聚合直接读原始事件会导致计算量过大。从分钟级派生,计算量降 60 倍,且结果一致(加权平均)。

5.4 Flow 的容错与 Exactly-Once 语义

Flow Task 基于 WAL 实现容错:

  1. 每个 Flow Task 维护自己的消费位点(类似 Kafka consumer offset)
  2. Flownode 故障后重启,从上次位点继续消费
  3. 结果写入目标表时使用幂等写入(基于时间窗口 + 分组键去重),保证 Exactly-Once
Flownode 重启 → 从 WAL 位点恢复 → 重新计算窗口内数据 → 幂等写入目标表

六、存算分离与对象存储架构

6.1 为什么对象存储是主存储?

传统时序数据库(InfluxDB、Prometheus)依赖本地磁盘,带来三个问题:

  1. 容量上限:单机磁盘有上限,扩容必须加机器
  2. 成本高昂:SSD 按 TB 计价是 S3 的 10-20 倍
  3. 运维复杂:数据迁移、副本管理、故障恢复全靠手动

GreptimeDB 把 S3 作为主存储,本地只做缓存:

写入 → WAL(本地)→ Memtable(内存)→ Flush → Parquet(S3)
查询 → 本地缓存命中?→ 返回
                          ↓ 未命中
                      从 S3 读取 → 放入缓存 → 返回

6.2 分层缓存架构

┌─────────────────────────────────┐
│         Memory Cache            │  ← 最热数据,纳秒级访问
│    (Memtable + 查询结果缓存)     │
├─────────────────────────────────┤
│         Local Disk Cache        │  ← 温数据,微秒级访问
│    (Parquet 文件本地副本)         │
├─────────────────────────────────┤
│         Object Storage (S3)     │  ← 全量数据,毫秒级访问
│    (Parquet 文件 + 元数据)        │
└─────────────────────────────────┘

缓存配置:

# greptimedb.toml - 缓存配置
[datanode]
[datanode.storage]
# 内存缓存大小
mem_cache_size = "4GiB"
# 本地磁盘缓存路径和大小
local_cache_path = "/data/greptimedb/cache"
local_cache_size = "100GiB"
# S3 配置
storage = "S3"
[datanode.storage.s3]
bucket = "your-bucket"
root = "/greptimedb"
region = "us-east-1"

6.3 成本对比:GreptimeDB vs 传统方案

以一个典型的中大规模场景为例:

  • 数据量:10TB/天(指标 + 日志 + Trace)
  • 保留期:30 天热数据 + 90 天冷数据
  • 查询 QPS:1000
方案月存储成本月计算成本月总成本
Prometheus + Loki + ES$12,000 (SSD)$6,000$18,000
Thanos + Loki + ES$4,000 (S3+SSD)$8,000$12,000
GreptimeDB$800 (S3为主)$4,000$4,800

存储成本降 50 倍不是营销噱头——S3 Standard 是 $0.023/GB/月,SSD 是 $0.10-0.20/GB/月,再加上三套系统的冗余存储(同一份数据存三次),差距就是这样拉开的。


七、查询引擎:SQL + PromQL 双语言实战

7.1 SQL 查询:关系型能力

GreptimeDB 的 SQL 引擎基于 Apache DataFusion,支持标准 SQL 语法:

-- 跨信号关联查询:找出延迟高的请求对应的错误日志和 Trace
WITH slow_requests AS (
    SELECT trace_id, service, endpoint, latency_ms
    FROM observability.api_events
    WHERE latency_ms > 1000
      AND ts > NOW() - INTERVAL '10 minutes'
)
SELECT
    s.ts, s.service, s.endpoint, s.latency_ms,
    s.error_message,
    s.trace_id
FROM observability.api_events s
JOIN slow_requests sr ON s.trace_id = sr.trace_id
WHERE s.error_message IS NOT NULL
ORDER BY s.latency_ms DESC
LIMIT 50;

7.2 PromQL 查询:兼容 Prometheus

对于已有 Grafana Dashboard 的团队,PromQL 兼容意味着零迁移成本:

# HTTP API 查询
curl -G "http://greptime-frontend:4000/v1/prometheus/query" \
  --data-urlencode 'query=histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (le, service))'

# Range query
curl -G "http://greptime-frontend:4000/v1/prometheus/query_range" \
  --data-urlencode 'query=http_requests_total{service="api-gateway"}' \
  --data-urlencode 'start=2026-06-17T00:00:00Z' \
  --data-urlencode 'end=2026-06-17T10:00:00Z' \
  --data-urlencode 'step=1m'

7.3 SQL 与 PromQL 混合查询

GreptimeDB 独有的能力——在 SQL 中调用 PromQL 函数:

-- 用 SQL 的灵活性 + PromQL 的时序函数
SELECT
    service,
    promql_quantile(0.99, latency_ms) AS p99,
    promql_rate(count) AS rps
FROM observability.api_events
WHERE ts > NOW() - INTERVAL '1 hour'
GROUP BY service;

八、性能优化:从写入到查询的全面调优

8.1 写入优化

批量写入:单条写入的 RPC 开销是批量写入的 100 倍。GreptimeDB 的写入吞吐在 batch size 8192 时达到峰值:

# Python 批量写入示例(使用 gRPC SDK)
import greptime

client = greptime.GreptimeDBClient(
    host="greptime-frontend",
    port=4001,
    database="observability"
)

# 批量构造行数据
rows = []
for event in events:
    rows.append({
        "ts": event.timestamp,
        "service": event.service,
        "endpoint": event.endpoint,
        "latency_ms": event.latency,
        # ...
    })

# 一次写入,批量提交
client.insert("api_events", rows)

Region 分区策略:默认按 Primary Key 哈希分区。如果某个 service 的写入量远大于其他,可以手动调整分区数:

-- 查看当前分区分布
SELECT region_id, row_count, disk_size
FROM information_schema.region_stats
WHERE table_name = 'api_events';

-- 如果某个 Region 过大,手动分裂
ALTER TABLE observability.api_events SPLIT REGION 0 AT (service='high-traffic-svc');

8.2 查询优化

时间范围裁剪:GreptimeDB 的元数据记录了每个 Parquet 文件的时间范围,查询时可以跳过不相关的文件。确保查询条件包含时间范围:

-- ✅ 好的查询:有时间范围
SELECT * FROM api_events
WHERE ts > NOW() - INTERVAL '1 hour'
  AND service = 'api-gateway';

-- ❌ 差的查询:无时间范围,全表扫描
SELECT * FROM api_events
WHERE service = 'api-gateway';

合理使用索引

-- 为高频查询字段建索引
ALTER TABLE api_events ADD INVERTED INDEX idx_service (service);
ALTER TABLE api_events ADD INVERTED INDEX idx_trace_id (trace_id);
ALTER TABLE api_events ADD SKIPPING INDEX idx_status_code (status_code);
ALTER TABLE api_events ADD FULLTEXT INDEX idx_error (error_message);

读取副本扩展:Frontend 是无状态的,查询压力大时直接加 Frontend 实例:

# Kubernetes 扩容
kubectl scale deployment greptimedb-frontend --replicas=5 -n greptimedb

8.3 Compaction 调优

Compaction 是 LSM-Tree 架构的核心后台任务,影响查询性能和存储空间:

[datanode.compaction]
# 触发 compaction 的 SST 文件数阈值
max_files = 8
# 最大 compaction 并发数
max_concurrent_tasks = 4
# compaction 的内存预算
max_memory_budget = "2GiB"
# 时间窗口 compaction(按时间分区合并)
time_window = "1h"

8.4 冷热数据分层

对于 30 天热数据 + 90 天冷数据的场景:

-- 设置表的 TTL
ALTER TABLE observability.api_events SET TTL = '120d';

-- 对冷数据使用更激进的压缩
ALTER TABLE observability.api_events SET COMPRESSION = 'zstd(19)' WHERE ts < NOW() - INTERVAL '30 days';

实际操作中,GreptimeDB 的 Compaction 会自动对旧数据使用更高压缩比。你只需要在 S3 层面配置生命周期策略,将超过 90 天的 Parquet 文件从 Standard 转为 Glacier:

// S3 Lifecycle Policy
{
  "Rules": [{
    "ID": "GreptimeDBColdStorage",
    "Status": "Enabled",
    "Transitions": [{
      "Days": 90,
      "StorageClass": "GLACIER"
    }]
  }]
}

九、告警与触发规则

9.1 内置告警规则

GreptimeDB 支持 SQL 风格的告警规则定义:

-- 创建告警规则:错误率超过 5% 触发
CREATE RULE high_error_rate
ON observability.error_rate_minute
WHEN error_rate > 0.05
EVALUATE EVERY INTERVAL '1 minute'
FOR INTERVAL '3 minutes'  -- 持续 3 分钟才触发
ACTION NOTIFY 'webhook', 'https://your-webhook.example.com/alert'
WITH PAYLOAD = json_build_object(
    'alert_name', 'HighErrorRate',
    'service', service,
    'error_rate', error_rate,
    'threshold', 0.05
);

9.2 与 Grafana 集成

安装 GreptimeDB 的 Grafana 数据源插件后,可以直接在 Grafana 中使用 SQL + PromQL 双语言:

# 安装 Grafana 插件
grafana-cli plugins install greptimedb-greptimedb-datasource

配置数据源:

apiVersion: 1
datasources:
  - name: GreptimeDB
    type: greptimedb-greptimedb-datasource
    access: proxy
    url: http://greptime-frontend:4000
    jsonData:
      database: observability

十、生产案例:OceanBase Cloud 的 300TB 日志实践

OceanBase Cloud 是 GreptimeDB 最大的生产用户之一:

  • 规模:80+ 个 GreptimeDB 集群,管理 300TB 日志数据
  • 迁移前:使用 Grafana Loki,存储成本高,大规模查询慢
  • 迁移后:存储成本降 60%+,一天内日志检索亚秒级返回

关键架构决策:

  1. 存算分离:计算节点按流量弹性伸缩,日志写入高峰期自动扩容
  2. 对象存储为主:300TB 数据全量存 S3,本地只做 100GB 缓存
  3. 多租户隔离:每个客户独立 Region,资源隔离 + 配额限制
-- 多租户配置
CREATE TENANT tenant_a WITH (quota = '100GB');
CREATE TENANT tenant_b WITH (quota = '500GB');

-- 按租户分表
CREATE TABLE tenant_a.logs ( ... );
CREATE TABLE tenant_b.logs ( ... );

十一、GreptimeDB vs 竞品:深度对比

11.1 vs Prometheus + Thanos/Mimir

维度GreptimeDBPrometheus + Thanos/Mimir
数据类型指标+日志+Trace仅指标
查询语言SQL + PromQLPromQL
存储原生对象存储本地磁盘 + 对象存储(sidecar)
高基数原生支持需要额外配置
长期存储S3 直接写需 Thanos Sidecar/Receive
运维复杂度一个系统多组件编排

11.2 vs Grafana Loki

维度GreptimeDBGrafana Loki
数据模型结构化宽事件标签+文本
查询语言SQL + LogQL 兼容LogQL
索引全文+倒排+跳跃仅标签索引
大规模查询列存 + 并行扫描全文扫描慢
结构化查询SQL 原生支持需要 LogQL 管道

11.3 vs Elasticsearch

维度GreptimeDBElasticsearch
存储成本S3 为主,低成本SSD 为主,高成本
写入吞吐高(Rust + 列存)中(JVM + 倒排更新)
时序查询原生优化通用查询
资源占用低(无 JVM)高(JVM 堆)

十二、Go/Python/Rust SDK 实战

12.1 Go SDK:高性能写入

package main

import (
    "context"
    "fmt"
    "time"

    "github.com/GreptimeTeam/greptimedb-ingester-go"
    "github.com/GreptimeTeam/greptimedb-ingester-go/table"
    "github.com/GreptimeTeam/greptimedb-ingester-go/table/types"
)

func main() {
    // 创建客户端
    cfg := greptime.NewConfig("greptime-frontend").
        WithPort(4001).
        WithDatabase("observability")

    client, err := greptime.NewClient(cfg)
    if err != nil {
        panic(err)
    }
    defer client.Close()

    // 构造表结构
    tbl, err := table.New("api_events").
        WithTimestampColumnName("ts").
        WithTagColumns("service", "endpoint", "region").
        WithFieldColumns("latency_ms", "status_code", "error_message").
        Build()
    if err != nil {
        panic(err)
    }

    // 批量写入
    for i := 0; i < 10000; i++ {
        tbl.AddRow(
            types.Timestamp(time.Now()),
            types.String("api-gateway"),
            types.String("/api/v1/users"),
            types.String("us-east-1"),
            types.Float64(float64(i%500+10)),
            types.Int32(int32(200+i%5*60)),
            types.String(""),
        )
    }

    // 提交写入
    affected, err := client.Write(context.Background(), tbl)
    if err != nil {
        panic(err)
    }
    fmt.Printf("写入 %d 行\n", affected)
}

12.2 Python SDK:数据分析友好

from greptime import GreptimeDBClient

client = GreptimeDBClient(
    host="greptime-frontend",
    port=4002,  # MySQL 协议
    database="observability"
)

# SQL 查询
result = client.sql("""
    SELECT
        service,
        approx_percentile_cont(latency_ms, 0.99) AS p99,
        avg(latency_ms) AS avg_latency,
        count(*) AS total
    FROM api_events
    WHERE ts > NOW() - INTERVAL '1 hour'
    GROUP BY service
    ORDER BY p99 DESC
""")

for row in result:
    print(f"{row['service']}: P99={row['p99']:.1f}ms, Avg={row['avg_latency']:.1f}ms, Count={row['total']}")

12.3 Rust SDK:零开销写入

use greptimedb_ingester::client::Client;
use greptimedb_ingester::table::TableBuilder;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new("http://greptime-frontend:4000")
        .database("observability")
        .build()?;

    let mut table = TableBuilder::new("api_events")
        .timestamp_column("ts")
        .tag_columns(&["service", "endpoint"])
        .field_columns(&["latency_ms", "status_code"])
        .build();

    // 批量添加行
    for i in 0..10000 {
        table.add_row()
            .timestamp(now())
            .tag("service", "api-gateway")
            .tag("endpoint", "/api/v1/users")
            .field("latency_ms", (i % 500 + 10) as f64)
            .field("status_code", 200 + (i % 5) as i32 * 60);
    }

    let result = client.write(&table).await?;
    println!("写入 {} 行", result.affected_rows);
    Ok(())
}

十三、监控 GreptimeDB 自身

13.1 内置 Metrics

GreptimeDB 暴露了 Prometheus 格式的自身监控指标:

curl http://greptime-frontend:4000/metrics

关键指标:

指标含义告警阈值
greptime_write_rows_total总写入行数-
greptime_write_latency_seconds写入延迟P99 > 500ms
greptime_query_latency_seconds查询延迟P99 > 5s
greptime_region_write_rows单 Region 写入量单 Region > 100k/s
greptime_compaction_pending_tasks待合并任务数> 100
greptime_wal_size_bytesWAL 大小> 10GB

13.2 用 GreptimeDB 监控 GreptimeDB(递归之美)

-- GreptimeDB 监控自身的写入延迟趋势
CREATE FLOW monitor_self_write_latency
SINK TO monitor.greptime_write_latency_p99
AS
SELECT
    date_bin(INTERVAL '1 minute', ts) AS ts,
    'greptimedb' AS cluster,
    approx_percentile_cont(value, 0.99) AS p99_latency
FROM greptime_metrics
WHERE metric_name = 'greptime_write_latency_seconds'
GROUP BY date_bin(INTERVAL '1 minute', ts);

十四、迁移路线图:从三件套到统一引擎

14.1 渐进式迁移(推荐)

不要一次性替换所有系统,按信号逐步迁移:

阶段一:指标先行(1-2 周)

  1. 配置 Prometheus Remote Write 双写到 GreptimeDB
  2. 验证 PromQL 查询结果一致
  3. Grafana 添加 GreptimeDB 数据源,并行对比
  4. 确认无误后,切 PromQL 查询到 GreptimeDB

阶段二:日志迁移(2-4 周)

  1. 配置 OTel Collector 同时写 Loki 和 GreptimeDB
  2. 验证 SQL 查询日志的能力
  3. 对比查询性能(GreptimeDB 的列存 + 索引通常更快)
  4. 切日志查询到 GreptimeDB

阶段三:链路追踪(2-3 周)

  1. 配置 OTel Collector Trace 数据写入 GreptimeDB
  2. 验证 trace_id 关联查询
  3. 替换 Jaeger/Tempo

阶段四:Flow 流处理替换 Recording Rules(1-2 周)

  1. 把 Prometheus Recording Rules 迁移为 Flow Task
  2. 验证派生指标一致性
  3. 下线 Thanos/Mimir 的 Recording Rules

14.2 回滚方案

每个阶段都保留双写,迁移出问题时可以秒级回滚:

# OTel Collector 双写配置
exporters:
  otlphttp/greptime:
    endpoint: "http://greptime-frontend:4000/otlp"
  otlphttp/loki:
    endpoint: "http://loki:3100/otlp"

十五、总结与展望

核心收获

  1. 宽事件模型是 Observability 2.0 的数据基础——一种信号,一张表,一条 SQL
  2. 存算分离 + 对象存储是成本优化的关键——S3 比 SSD 便宜 10-20 倍,且容量无上限
  3. Flow 引擎让流计算内嵌于数据库,省去了 Kafka + Flink 的额外运维
  4. SQL + PromQL 双语言兼顾了分析灵活性和 Grafana 兼容性
  5. Rust + Arrow + DataFusion的技术栈决定了性能上限极高

GreptimeDB 的局限与取舍

  1. 不支持事务:时序/可观测场景不需要 ACID 事务,但如果你想在上面跑 OLTP,不行
  2. 更新代价高:列存 + LSM 架构,更新意味着重写整个 Parquet 文件。追加模式才是正确用法
  3. 学习曲线:SQL 查询可观测数据对习惯了 LogQL/PromQL 的团队需要适应
  4. 生态仍在成长:相比 Prometheus 的生态,GreptimeDB 的 Exporter/Integration 还在补全

2026 展望

根据 GreptimeDB 的 2026 路线图,几个值得期待的方向:

  1. AI Agent 原生查询优化:针对 LLM Agent 的自然语言→SQL 查询路径优化
  2. 更完善的 Autopilot:自动调优 Region 分区、Compaction 策略、缓存大小
  3. 边缘到云的统一:资源受限设备上的轻量 GreptimeDB 实例,数据同步到云端
  4. 更丰富的分析函数:异常检测、预测、根因分析等内嵌算法

GreptimeDB 代表了可观测性基础设施的演进方向——不是在三个系统之间做选择,而是用一个统一的引擎消除选择本身。对于正在被三件套的运维成本和跨系统关联查询折磨的团队,现在就是试水的最佳时机。


相关资源

  • 官方文档:https://docs.greptime.com
  • GitHub 仓库:https://github.com/GreptimeTeam/greptimedb
  • Helm Charts:https://github.com/GreptimeTeam/helm-charts
  • OTel Collector 配置:https://docs.greptime.com/user-guide/ingest-data/for-observability/opentelemetry/

推荐文章

介绍 Vue 3 中的新的 `emits` 选项
2024-11-17 04:45:50 +0800 CST
CSS 奇技淫巧
2024-11-19 08:34:21 +0800 CST
`Blob` 与 `File` 的关系
2025-05-11 23:45:58 +0800 CST
Golang Select 的使用及基本实现
2024-11-18 13:48:21 +0800 CST
PHP 如何输出带微秒的时间
2024-11-18 01:58:41 +0800 CST
PyMySQL - Python中非常有用的库
2024-11-18 14:43:28 +0800 CST
CSS 特效与资源推荐
2024-11-19 00:43:31 +0800 CST
宝塔面板 Nginx 服务管理命令
2024-11-18 17:26:26 +0800 CST
程序员茄子在线接单