暂无图片
暂无图片
1
暂无图片
暂无图片
暂无图片

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

GreptimeDB 2025-08-08
316

测试结论

  • GreptimeDB 在日志写入场景中表现出色,写入吞吐能力约为 Loki 的 1.5 倍;在使用低成本的对象存储时,GreptimeDB 写入性能依然保持较高水平,未出现明显下降;
  • 借助全文索引和查询缓存,GreptimeDB 在关键词搜索和聚合等典型查询场景中,查询速度较 Loki 快 40 到 80 倍。对于热点查询,性能提升更是高达 500 倍以上
  • GreptimeDB 采用高效的列式存储和压缩算法,数据压缩率显著优于 Loki,而存储占用约为 Loki 的二分之一

测试场景

测试数据

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

{"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_ip
Pod 的内部 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:测试流程图)

软硬件说明

硬件平台

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

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

软件版本

数据库
版本
GreptimeDB
0.15.3
Loki
3.5.2

读写性能测试

写入表现

数据库
TPS
GreptimeDB
121k rows/s
GreptimeDB on OSS
102k rows/s
Loki
78k 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:关键词搜索)
  • 图表横轴为查询的时间范围,纵轴为查询耗时(毫秒,越低越好);
  • Loki 查询 30d、60d、90d 时间范围的查询时超时,图中留空。

按分钟聚合

(图 3:按分钟聚合)

Top error/warn pods 查询

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

Distinct 查询

(图 5:Distinct 查询)

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

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

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

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

资源占用及压缩率

数据库
CPU Avg
Memory Avg
Memory Max
GreptimeDB
35%
1.9GB
2.21GB
GreptimeDB on OSS
30%
1.75GB
2.24GB
Loki
33.75%
1.4GB
2.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)
压缩比
GreptimeDB
3.03
3%
GreptimeDB on OSS
2.81
3%
Loki
6.59
8%

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


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

附录

查询语句

Range Match

SQL

SELECT
  *
FROM
  ${TABLE_NAME}
WHERE
  message @@ '${KEYWORD}'
ANDtimestamp >= '${START_TIME}'
ANDtimestamp <= '${END_TIME}'
ORDERBY
timestampDESC
OFFSET ${OFFSET}
LIMIT ${LIMIT}

LogQL

"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

SELECT
  date_bin('1m'::intervaltimestampAS minute_bucket,
  pod_name,
  pod_ip,
COUNT(*) AS log_count
FROM ${TABLE_NAME}
WHERE
  message @@ '${KEYWORD}'
ANDtimestamp >= '${START_TIME}'
ANDtimestamp <= '${END_TIME}'
GROUPBY minute_bucket, pod_name, pod_ip
ORDERBY minute_bucket ASC, pod_ip ASC;

LogQL

"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

SELECT pod_name, pod_ip, COUNT(*) AS cnt
FROM ${TABLE_NAME}
WHERE
(level = 'warn'ORlevel = 'error')
ANDtimestamp >= '${START_TIME}'
ANDtimestamp <= '${END_TIME}'
GROUPBY pod_name, pod_ip
ORDERBY cnt DESC, pod_ip DESC
LIMIT ${LIMIT};

LogQL

"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

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

LogQL

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

配置

GreptimeDB

Vector 配置

[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"

建表语句

CREATE TABLEIFNOTEXISTS`demo_logs` (
`message`STRINGNULL FULLTEXT INDEX,
`level`STRINGNULL SKIPPING INDEX,
`target`STRINGNULL SKIPPING INDEX,
`pod_name`STRINGNULL SKIPPING INDEX,
`container_name`STRINGNULL SKIPPING INDEX,
`pod_ip`STRINGNULL SKIPPING INDEX,
`app.kubernetes.io/component`STRINGNULL SKIPPING INDEX,
`app.kubernetes.io/instance`STRINGNULL SKIPPING INDEX,
`app.kubernetes.io/managed-by`STRINGNULL SKIPPING INDEX,
`app.kubernetes.io/name`STRINGNULL SKIPPING INDEX,
`apps.kubernetes.io/pod-index`STRINGNULL SKIPPING INDEX,
`controller-revision-hash`STRINGNULL SKIPPING INDEX,
`helm.sh/chart`STRINGNULL SKIPPING INDEX,
`message_id`STRINGNULL SKIPPING INDEX,
`timestamp`TIMESTAMP(9NOTNULL,
TIMEINDEX (`timestamp`)
)
ENGINE=mito
WITH(
  skip_wal = 'true',
  append_mode = 'true'
);

Loki

软件配置

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 配置

[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

Greptime 格睿科技专注于打造新一代可观测数据库,服务开发者与企业用户,覆盖从从边缘设备到云端企业级部署的多样化需求。

  • GreptimeDB 开源版:开源、云原生,统一处理指标、日志和追踪数据,适合中小规模 IoT,个人项目与可观测性场景;
  • GreptimeDB 企业版:面向关键业务,提供更高性能、高安全性、高可用性和智能化运维服务;
  • GreptimeCloud 云服务:全托管云服务,零运维体验“企业级”可观测数据库,弹性扩展,按需付费。

欢迎加入开源社区参与贡献与交流!推荐从带有 good first issue
 标签的任务入手,一起共建可观测未来。


⭐ Star us on GitHub:https://github.com/GreptimeTeam/greptimedb 

📚 官网:https://greptime.cn/ 

📖 文档:https://docs.greptime.cn/ 

🌍 Twitter:https://twitter.com/Greptime 

💬 Slack:https://greptime.com/slack 

💼 LinkedIn:https://www.linkedin.com/company/greptime/


往期精彩文章:

点击「阅读原文」,立即体验 GreptimeDB!

最后修改时间:2025-08-11 18:05:13
文章转载自GreptimeDB,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论