欢迎参与 8 月 1 日中午 11 点的线上分享,了解 GreptimeDB 联合处理指标和日志的最新方案! 👉🏻 点击加入

Skip to content
On this page
性能报告
2025-8-7

超越 Loki!GreptimeDB 日志场景性能报告发布

GreptimeDB 在日志写入场景超越 Loki:写入吞吐达 1.5 倍,关键词查询快 40-500 倍,存储占用节省 50%,且在对象存储环境下性能无衰减。

测试结论

  • GreptimeDB 在日志写入场景中表现出色,写入吞吐能力约为 Loki 的 1.5 倍;在使用低成本的对象存储时,GreptimeDB 写入性能依然保持较高水平,未出现明显下降;

  • 借助全文索引和查询缓存,GreptimeDB 在关键词搜索和聚合等典型查询场景中,查询速度较 Loki 快 40 到 80 倍。对于热点查询,性能提升更是达到 500 倍以上

  • GreptimeDB 采用高效的列式存储和压缩算法,数据压缩率显著优于 Loki,存储占用仅为 Loki 的约二分之一

测试场景

测试数据

我们选取了线上环境中过去 3 个月的 Etcd 集群监控日志作为数据源,尽可能地还原实际的日志分析场景。每条日志包含时间戳、日志级别、Pod 信息、IP 地址、原始消息等字段,原始日志数据如下:

sql
{"pod_name":"etcd-1","container_name":"etcd","pod_ip":"10.0.169.66","pod_labels":"{\"app.kubernetes.io/component\":\"etcd\",\"app.kubernetes.io/instance\":\"etcd\",\"app.kubernetes.io/managed-by\":\"Helm\",\"app.kubernetes.io/name\":\"etcd\",\"apps.kubernetes.io/pod-index\":\"1\",\"controller-revision-hash\":\"etcd-7dc48bf797\",\"helm.sh/chart\":\"etcd-9.0.0\",\"statefulset.kubernetes.io/pod-name\":\"etcd-1\"}","message":"{\"level\":\"debug\",\"ts\":\"2025-06-17T14:41:34.06945Z\",\"caller\":\"etcdserver/server.go:2231\",\"msg\":\"applyEntryNormal\",\"raftReq\":\"header:<ID:15307056038004875511 > alarm:<> \"}","timestamp":"2025-06-17T14:41:34.069544309"}

结构如下:

字段名含义
pod_name所属 Pod 的名称(etcd-1),这是一个 StatefulSet 的第 2 个副本(索引为 1)
container_name日志来源的容器名称,这里是 Etcd
pod_ipPod 的内部 IP 地址:10.0.169.66
pod_labels与这个 Pod 相关的标签
timestamp日志时间戳:2025-06-17T14:41:34.069544309Z(UTC 时间)
message日志的核心内容,里面嵌套了 Etcd 本身的日志结构:level(等级),ts(内部时间),caller(调用源代码位置),msg(日志内容)和 raftReq(Raft 协议请求详情)

我们使用 Vector 这个开源可观测数据 Pipeline 来解析并写入上面的数据。整体测试的流程如图所示:

(图 1:测试流程图)
(图 1:测试流程图)

软硬件说明

硬件平台

服务器型号操作系统
阿里云 12 核(vCPU)24 GiB ecs.c9i.3xlargeUbuntu24.04 LTS

其中数据库资源限制为 8 核 CPU 和 16GB 内存,剩余资源分配给 Vector 和监控组件。

软件版本

数据库版本
GreptimeDB0.15.3
Loki3.5.2

读写性能测试

写入表现

数据库TPS
GreptimeDB121k rows/s
GreptimeDB on OSS102k rows/s
Loki78k rows/s

通过对比测试写入性能,我们可以得出以下结论:

  • GreptimeDB 在写入性能上表现优异:GreptimeDB 在写入测试中达到了 121k rows/s 的吞吐量,表现非常出色。在使用 OSS 对象存储作为存储底座时,GreptimeDB 写入性能依然维持在较高水平,达到 102k rows/s。

  • Loki 的写入吞吐量较低:相比之下,Loki 的写入性能为 78k rows/s,明显低于 GreptimeDB。

总体来看,GreptimeDB 在写入场景中展现了更高的吞吐量,使用对象存储的情况下,依然保持了出色的写入性能。

查询表现

在这次查询性能测试中,我们挑选了几种常见的查询类型,覆盖了日志分析中典型的使用场景:

  • 关键词搜索:支持在日志中按关键词查找,同时结合时间范围和分页,用于快速定位特定信息;
  • 按分钟聚合:统计每分钟内包含某关键词的日志数量,并按 Pod 维度分组,可用于查看一段时间内的变化趋势;
  • Top error/warn pod 查询:统计产生 Warn 或 Error 日志最多的 Pod,帮助快速找到可能出问题的节点;
  • Distinct 查询:用于查找某个字段的所有不重复值,比如有哪些服务名或主机名。

关键词搜索

(图 2:关键词搜索)
(图 2:关键词搜索)

图表横轴为查询的时间范围,纵轴为查询耗时(毫秒,越低越好);

Loki 查询 30d、60d、90d 时间范围的查询时超时,图中留空。

按分钟聚合

(图 3:按分钟聚合)
(图 3:按分钟聚合)

Top error/warn pods 查询

(图 4:Top error/warn pods 查询)
(图 4:Top error/warn pods 查询)

Distinct 查询

(图 5:Distinct 查询)
(图 5:Distinct 查询)

通过上述数据对比可以发现:

  • 借助全文索引,GreptimeDB 在关键词搜索和分钟级聚合等典型场景中展现出显著的查询优势。与 Loki 相比,GreptimeDB 的查询速度提升可达 40 到 80 倍

  • 对于部分热点查询场景,由于 GreptimeDB 内置的查询结果缓存机制,性能差距甚至超过 500 倍,大幅减少了重复查询的响应时间;

  • 在 Distinct 查询场景中,Loki 表现更佳。为提升此类查询效率,GreptimeDB 可结合 Flow 功能对 Distinct 值进行预计算优化。

资源占用及压缩率

数据库CPU AvgMemory AvgMemory Max
GreptimeDB35%1.9GB2.21GB
GreptimeDB on OSS30%1.75GB2.24GB
Loki33.75%1.4GB2.12GB
  • CPU 使用率:GreptimeDB 的 CPU 平均使用率约为 30%~35%,与 Loki 的 33.75% 相当,表现较为接近。

  • 内存使用:GreptimeDB 在内存平均使用上略高于 Loki,约为 1.75GB 至 1.9GB,而 Loki 约为 1.4GB。内存峰值方面,GreptimeDB 和 Loki 均在 2.1GB 左右,差异不大。

总体来看,GreptimeDB 在保持更高写入性能的同时,资源消耗与 Loki 相当。

压缩率

原始数据文件(NDJSON)大小约为 83GB。在所有数据写入完毕后,我们统计各个数据库产品的持久化目录大小,可以计算得到以下压缩率:

数据库数据大小(GB)压缩比
GreptimeDB3.033%
GreptimeDB on OSS2.813%
Loki6.598%

GreptimeDB 在存储日志数据时表现出较高的压缩效率,数据占用仅约原始数据的 3%,压缩后数据大小约为 3.03 GB;相比之下,Loki 的数据压缩比为约 8%,数据大小为 6.59 GB,存储占用明显高于 GreptimeDB。

在对存储效率和成本敏感的场景中,GreptimeDB 更具优势

附录

查询语句

Range Match

SQL

sql
SELECT
  *
FROM
  ${TABLE_NAME}
WHERE
  message @@ '${KEYWORD}'
  AND timestamp >= '${START_TIME}'
  AND timestamp <= '${END_TIME}'
ORDER BY
  timestamp DESC
OFFSET ${OFFSET}
LIMIT ${LIMIT}

LogQL

bash
"http://${DB_HOST}:${DB_PORT}/loki/api/v1/query_range" \
    -d 'query={level="info"} |= `${QUERY}`' \
    -d "limit=${LIMIT}" \
    -d "direction=backward" \
    -d "start=${START_TIME}" \
    -d "end=${END_TIME}"

Minute bucket with match

SQL

sql
SELECT
  date_bin('1m'::interval, timestamp) AS minute_bucket,
  pod_name,
  pod_ip,
  COUNT(*) AS log_count
FROM ${TABLE_NAME}
WHERE 
  message @@ '${KEYWORD}'
  AND timestamp >= '${START_TIME}'
  AND timestamp <= '${END_TIME}'
GROUP BY minute_bucket, pod_name, pod_ip
ORDER BY minute_bucket ASC, pod_ip ASC;

LogQL

bash
"http://${DB_HOST}:${DB_PORT}/loki/api/v1/query_range" \
    -d 'count_over_time({level="info"} |= `${QUERY}` [1m])' \
    -d "direction=backward" \
    -d "start=${START_TIME}" \
    -d "end=${END_TIME}"

Top error warn pods

SQL

sql
SELECT pod_name, pod_ip, COUNT(*) AS cnt
FROM ${TABLE_NAME}
WHERE 
(level = 'warn' OR level = 'error')
AND timestamp >= '${START_TIME}' 
AND timestamp <= '${END_TIME}'
GROUP BY pod_name, pod_ip
ORDER BY cnt DESC, pod_ip DESC
LIMIT ${LIMIT};

LogQL

bash
"http://${DB_HOST}:${DB_PORT}/loki/api/v1/query" \
    -d 'query=topk(${LIMIT}, sum by(pod_ip, pod_name) (count_over_time({level=~"warn|error"}[${RATE_INTERVAL}])))' \
    -d "start=${START_TIME}" \
    -d "end=${END_TIME}"

Distinct

SQL

sql
SELECT
  DISTINCT(${QUERY})
FROM
  ${TABLE_NAME}
WHERE
  timestamp >= '${START_TIME}'
  AND timestamp <= '${END_TIME}'
ORDER BY ${QUERY}
LIMIT ${LIMIT};

LogQL

bash
"http://${DB_HOST}:${DB_PORT}/loki/api/v1/label/${QUERY}/values" \
    -d "start=${START_TIME}" \
    -d "end=${END_TIME}"

配置

GreptimeDB

Vector 配置
c++
[sources.logfile]
type = "file"
include = ["/data/etcd_logs/output.json"]

[transforms.parse_log]
type = "remap"
inputs = ["logfile"]
source = '''
  . = parse_json!(.message)

  .parsed_message, err = parse_json(.message)
  if is_object(.parsed_message) {
    .level = .parsed_message.level
  }
  del(.parsed_message)
  if exists(.timestamp) {
    .timestamp = parse_timestamp!(.timestamp, "%Y-%m-%dT%H:%M:%S.%f")
  }

  .pod_name = .pod_name
  .container_name = .container_name
  .pod_ip = .pod_ip
  .message_id = .message_id

  .pod_labels, err = parse_json(.pod_labels)
  if is_object(.pod_labels) {
    ."app.kubernetes.io/component" = .pod_labels."app.kubernetes.io/component"
    ."app.kubernetes.io/instance" = .pod_labels."app.kubernetes.io/instance"
    ."app.kubernetes.io/managed-by" = .pod_labels."app.kubernetes.io/managed-by"
    ."app.kubernetes.io/name" = .pod_labels."app.kubernetes.io/name"
    ."apps.kubernetes.io/pod-index" = .pod_labels."apps.kubernetes.io/pod-index"
    ."controller-revision-hash" = .pod_labels."controller-revision-hash"
    ."helm.sh/chart" = .pod_labels."helm.sh/chart"
    ."statefulset.kubernetes.io/pod-name" = .pod_labels."statefulset.kubernetes.io/pod-name"
  }
  del(.pod_labels)
'''

[sinks.greptime_logs]
type = "greptimedb_logs"
inputs = ["parse_log"]
compression = "gzip"
dbname = "public"
endpoint = "http://greptimedb:4000"
pipeline_name = "greptime_identity"
extra_params = { "custom_time_index" = "timestamp;datestr;%Y-%m-%dT%H:%M:%S%.9f%#z" }
table = "demo_logs"
batch.max_events = 1000

[sources.vector_metrics]
type = "internal_metrics"

[sinks.prometheus_exporter]
type = "prometheus_exporter"
inputs = ["vector_metrics"]
address = "0.0.0.0:9598"
建表语句
sql
CREATE TABLE IF NOT EXISTS `demo_logs` (
  `message` STRING NULL FULLTEXT INDEX,
  `level` STRING NULL SKIPPING INDEX,
  `target` STRING NULL SKIPPING INDEX,
  `pod_name` STRING NULL SKIPPING INDEX,
  `container_name` STRING NULL SKIPPING INDEX,
  `pod_ip` STRING NULL SKIPPING INDEX,
  `app.kubernetes.io/component` STRING NULL SKIPPING INDEX,
  `app.kubernetes.io/instance` STRING NULL SKIPPING INDEX,
  `app.kubernetes.io/managed-by` STRING NULL SKIPPING INDEX,
  `app.kubernetes.io/name` STRING NULL SKIPPING INDEX,
  `apps.kubernetes.io/pod-index` STRING NULL SKIPPING INDEX,
  `controller-revision-hash` STRING NULL SKIPPING INDEX,
  `helm.sh/chart` STRING NULL SKIPPING INDEX,
  `message_id` STRING NULL SKIPPING INDEX,
  `timestamp` TIMESTAMP(9) NOT NULL,
  TIME INDEX (`timestamp`)
)
ENGINE=mito
WITH(
  skip_wal = 'true',
  append_mode = 'true'
);

Loki

软件配置
yaml
auth_enabled: false

server:
  http_listen_port: 3100
  grpc_listen_port: 9096

common:
  instance_addr: 0.0.0.0
  path_prefix: /tmp/loki
  storage:
    filesystem:
      chunks_directory: /tmp/loki/chunks
      rules_directory: /tmp/loki/rules
  replication_factor: 1
  ring:
    kvstore:
      store: inmemory

query_range:
  results_cache:
    cache:
      embedded_cache:
        enabled: true
        max_size_mb: 100

schema_config:
  configs:
    - from: 2020-10-24
      store: tsdb
      object_store: filesystem
      schema: v13
      index:
        prefix: index_
        period: 24h

ruler:
  alertmanager_url: http://localhost:9093

analytics:
  reporting_enabled: false

ingester:
  max_chunk_age: 4800h

limits_config:
  reject_old_samples: true
  retention_period: 365d
  max_query_lookback: 365d
  max_query_length: 0h

  ingestion_rate_mb: 10240
  ingestion_burst_size_mb: 10240
  max_streams_per_user: 10000000
  max_global_streams_per_user: 10000000

  per_stream_rate_limit: 10240M
  per_stream_rate_limit_burst: 10240M
  cardinality_limit: 20000000
Vector 配置
c++
[sources.logfile]
type = "file"
include = ["/data/etcd_logs/output.json"]

[transforms.parse_log]
type = "remap"
inputs = ["logfile"]
source = '''
  . = parse_json!(.message)

  .parsed_message, err = parse_json(.message)
  if is_object(.parsed_message) {
    .level = .parsed_message.level
  }
  del(.parsed_message)
  if exists(.timestamp) {
    .timestamp = parse_timestamp!(.timestamp, "%Y-%m-%dT%H:%M:%S.%f")
  }

  .pod_name = .pod_name
  .container_name = .container_name
  .pod_ip = .pod_ip

  .pod_labels, err = parse_json(.pod_labels)
  if is_object(.pod_labels) {
    ."app.kubernetes.io/component" = .pod_labels."app.kubernetes.io/component"
    ."app.kubernetes.io/instance" = .pod_labels."app.kubernetes.io/instance"
    ."app.kubernetes.io/managed-by" = .pod_labels."app.kubernetes.io/managed-by"
    ."app.kubernetes.io/name" = .pod_labels."app.kubernetes.io/name"
    ."apps.kubernetes.io/pod-index" = .pod_labels."apps.kubernetes.io/pod-index"
    ."controller-revision-hash" = .pod_labels."controller-revision-hash"
    ."helm.sh/chart" = .pod_labels."helm.sh/chart"
    ."statefulset.kubernetes.io/pod-name" = .pod_labels."statefulset.kubernetes.io/pod-name"
  }
  del(.pod_labels)
'''

[sinks.loki]
type = "loki"
inputs = [ "parse_log" ]
compression = "gzip"
endpoint = "http://loki:3100"
out_of_order_action = "accept"
path = "/loki/api/v1/push"
batch.max_events = 1000
encoding.codec = "json"
healthcheck = false
remove_timestamp = false

[sinks.loki.labels]
source = "vector"
level = "{{level}}"
pod_name = "{{pod_name}}"
container_name = "{{container_name}}"
pod_ip = "{{pod_ip}}"

[sources.vector_metrics]
type = "internal_metrics"

[sinks.prometheus_exporter]
type = "prometheus_exporter"
inputs = ["vector_metrics"]
address = "0.0.0.0:9598"

监控数据

Loki 写入

Loki 在写入过程中,磁盘的峰值占用 60GiB(Disk Usage 黄色线段)

GreptimeDB 写入

GreptimeDB OSS 写入

查询

其中黄色线条为为 Loki 资源占用,绿色线条为 GreptimeDB。

加入我们的社区

获取 Greptime 最新更新,并与其他用户讨论。