测试结论
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 来解析并写入上面的数据。整体测试的流程如图所示:

软硬件说明
硬件平台
服务器型号 | 操作系统 |
---|---|
阿里云 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 查询:用于查找某个字段的所有不重复值,比如有哪些服务名或主机名。
关键词搜索

图表横轴为查询的时间范围,纵轴为查询耗时(毫秒,越低越好);
Loki 查询 30d、60d、90d 时间范围的查询时超时,图中留空。
按分钟聚合

Top error/warn pods 查询

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}'
AND timestamp >= '${START_TIME}'
AND timestamp <= '${END_TIME}'
ORDER BY
timestamp DESC
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'::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
"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' 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
"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 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
软件配置
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。