Skip to content

GreptimeDB 写入协议选型指南:实测 7 种协议,性能差 37 倍

在相同条件下对比 GreptimeDB 7 种写入协议的 benchmark 结果,基于 v1.0 GA 实测,batch=1000 下吞吐从 7.2 万到 268 万 rows/sec(batch=2000 峰值 334 万),附选型建议。
GreptimeDB 写入协议选型指南:实测 7 种协议,性能差 37 倍
本页内容

不同写入协议的性能差距可达 37 倍。这篇文章用 benchmark 数据帮你选对。

2026-04-21 更新:下文数据基于 GreptimeDB v1.0 GA 重新测试。和第一版相比有几处变化。OTLP 路径启用了 greptime_identity pipeline,attributes 被展开为独立列,存储布局与其他协议一致。v1.0 将 Flat SST / BulkMemtable 设为默认格式后,Bulk 路径对 Arrow schema 的严格校验暴露了 Go ingester 在 timestamp 类型上的不匹配,已在 greptimedb-ingester-go v0.7.2 修复。PostgreSQL 启用了 prepared statement cache。版本背景见 v1.0 GA 发布博客

GreptimeDB 支持十几种写入协议,社区里被问得最多的问题就是:该用哪个?

能找到的零散数据不少,但测试条件各不相同,没法直接对比。所以我做了一个开源 benchmark 工具 greptimedb-ingestion-benchmark,把几种最常用的协议放在完全相同的条件下跑了一遍。这篇文章分享结果和选型建议。

测了哪些协议

从众多写入方式中挑了三类:

GreptimeDB gRPC 协议,通过官方 SDK 使用,有三种写入模式:

写入模式说明
gRPC SDK (Unary)每批次一次 RPC 调用,最简单
gRPC Stream双向流复用连接,适合高频、持续高吞吐
gRPC Bulk (Arrow)Arrow Flight DoPut 列式传输,吞吐最高

开放标准协议:InfluxDB Line Protocol(HTTP 文本格式)和 OTLP Logs(HTTP + Protobuf)。

SQL 协议:MySQL INSERT 和 PostgreSQL INSERT。

这里 OTLP 测的是 Logs 而不是 Metrics。原因是 GreptimeDB 的 OTLP 数据模型中,Metrics 会把每个 metric name 映射为独立的表。本次 benchmark 有 5 个指标字段,Metrics 模型会拆成 5 张表,没法和其他协议公平对比。Logs 模型把所有字段写入同一张表,测试条件一致。本轮 OTLP Logs 路径启用了 greptime_identity pipeline,将 log attributes 展开为独立列,而不是统一写入单个 JSON 列,存储布局与其他协议一致。

关于 Schemaless(自动建表):gRPC SDK、gRPC Stream、InfluxDB LP、OTLP 都支持写入时自动建表[1],新字段直接写,GreptimeDB 自动加列。SQL INSERT 和 gRPC Bulk (Arrow) 需要提前建表。SQL 是因为 INSERT INTO 依赖已有表结构,Arrow Bulk 是因为 DoPut 接口需要目标表已存在来做列映射。数据结构不固定的场景(IoT 设备字段变化、LLM 对话数据等),优先选支持 Schemaless 的协议。

测试条件

1000 万行,100 万个时间序列(1000 台主机 × 5 region × 10 datacenter × 20 service),每行 5 个 float64 指标字段,固定随机种子(seed=42)。每种协议写入独立表,5 并发 worker,SDK 全部默认配置。

测试环境:MacBook Pro 14-inch(M4 Max, 48 GB),GreptimeDB v1.0 GA standalone 模式,greptimedb-ingester-go v0.7.2。这是单机测试,目的是比较协议间的相对差异,不是测绝对吞吐上限。生产环境分布式集群的绝对数值会更高,但协议间的排序一致。完整方法论见仓库 README

结果

batch=1000,100 万 series

100 万时间序列接近真实生产环境的基数,batch=1000 是大多数场景的合理默认值。

协议吞吐 (rows/sec)耗时P50 延迟P99 延迟
gRPC Bulk (Arrow)2,678,8393.7 秒1.54 ms8.80 ms
gRPC Stream1,562,1346.4 秒2.88 ms10.77 ms
gRPC SDK1,174,2218.5 秒4.15 ms10.80 ms
InfluxDB LP889,05111.3 秒5.42 ms13.06 ms
OTLP Logs (HTTP)621,36716.1 秒7.79 ms16.38 ms
PostgreSQL INSERT73,760135.6 秒66.58 ms101.58 ms
MySQL INSERT72,103138.7 秒67.65 ms119.37 ms

协议吞吐对比

gRPC 三种模式在 117-268 万/秒这个区间。HTTP 协议里 InfluxDB LP 约 89 万/秒,OTLP Logs 约 62 万/秒,掉一档。SQL 在 7.2-7.4 万/秒。最快和最慢差了大约 37 倍。

OTLP 吞吐 62 万落在 InfluxDB LP 之下,这符合预期:两者同样走 HTTP,每批次一次完整的请求-响应,但 OTLP 路径还需要在服务端执行 greptime_identity pipeline,把 attributes 展开为独立列,这部分 CPU 开销是它落后的主要原因。代价换来的是与其他协议一致的存储布局;下面"时间序列基数的影响"一节还会看到,这条路径对 cardinality 几乎不敏感。

SQL 的结果需要多说一句:本次测试的连接池大小和并发数都只有 5,这是 benchmark 统一的配置。实际使用中,通过增大连接池和并发数可以提升 SQL 写入吞吐,但这不在本次测试的覆盖范围内。这里的数据反映的是相同并发条件下各协议的相对表现。

低基数参考:batch=1000,10 万 series

时间序列较少的场景(几百台主机的监控之类),大多数协议基本持平,但有一个例外:

协议吞吐 (rows/sec)P50 延迟P99 延迟
gRPC Bulk (Arrow)1,950,5171.37 ms12.92 ms
gRPC Stream1,653,2252.64 ms11.47 ms
gRPC SDK1,228,2983.96 ms9.87 ms
InfluxDB LP945,9124.99 ms15.26 ms
OTLP Logs (HTTP)616,8487.46 ms18.16 ms
PostgreSQL INSERT74,21865.90 ms106.31 ms
MySQL INSERT73,81366.12 ms107.61 ms

gRPC Bulk 在 10 万 series 下反而比 100 万 series 更慢,与其他协议趋势相反,并非笔误。原因在下面"时间序列基数的影响"一节展开。

批次大小的影响

4 种批次大小(50 / 200 / 1000 / 2000),100 万 series:

协议batch=50batch=200batch=1000batch=2000
gRPC Bulk (Arrow)806,4951,524,3162,678,8393,335,918
gRPC Stream641,4851,076,5551,562,1341,759,202
gRPC SDK552,341950,1971,174,2211,415,292
InfluxDB LP507,072761,088889,0511,082,739
OTLP Logs (HTTP)425,849564,228621,367683,222
MySQL INSERT66,78867,69272,10373,534
PostgreSQL INSERT66,49068,17673,76073,617

批次大小对吞吐的影响

gRPC 系列持续受益于更大的批次。Bulk 从 batch=50 的 80.6 万一路涨到 batch=2000 的 334 万,超过 4 倍。Stream 约 2.7 倍(64 万 → 176 万),SDK 2.6 倍。延迟代价也在可接受范围 —— 即使 batch=2000,gRPC Bulk P99 也只在十几毫秒(13.78 ms)。

InfluxDB LP 的批次收益优于 OTLP(50.7 万 → 108 万 vs 42.6 万 → 68.3 万)。OTLP 走 protobuf 再加 pipeline 处理,比 InfluxDB 纯文本 line protocol 路径重。

SQL 几乎没变。MySQL 从 6.7 万到 7.4 万,PostgreSQL 从 6.6 万到 7.4 万。吞吐被 INSERT ... VALUES (...) 的逐行执行模型卡住了,batch 再大也只是把延迟摊到一条语句里(P99 从 7 ms 飙到 200 ms+)。

时间序列基数的影响

先交代一个 v1.0 的前提,本节观察都建立在这之上:v1.0 起,Flat SST / BulkMemtable 是默认存储格式(详见 v1.0 GA 发布博客)。benchmark 中所有协议的写入都经过新的 BulkMemtable,下面的 cardinality 对比能直接反映出这个变化的影响。

10 万 vs 100 万 series,batch=1000:

协议10 万 series100 万 series差异
gRPC Bulk (Arrow)1,950,5172,678,839+37.3%
gRPC Stream1,653,2251,562,134−5.5%
gRPC SDK1,228,2981,174,221−4.4%
InfluxDB LP945,912889,051−6.0%
OTLP Logs (HTTP)616,848621,367+0.7%
PostgreSQL INSERT74,21873,760−0.6%
MySQL INSERT73,81372,103−2.3%

时间序列基数的影响

两点值得关注。其一,除 gRPC Bulk 外,其他协议基本持平,差异都在 ±6% 以内;其二,gRPC Bulk 不仅不持平,方向还反了——100 万 series 比 10 万 series 快 37.3%。多次重复运行结果稳定,不是单次偶然。

差距真正在哪

10M rows、batch=2000 下 dump information_schema.region_statistics 和 mito flush metrics,结果相当清楚:

指标10 万 series100 万 series
吞吐 rows/sec2,159K2,933K(+36%)
wall clock4.63 s3.41 s(Δ = −1.22 s)
region 数11
flush 次数(reason="EngineFull"33
SST 输出大小491 MB493 MB
flush_elapsed_sum{type="total"}2.31 s1.02 s
flush_memtables sum2.28 s0.99 s
write_batch(parquet IO)sum2.23 s2.01 s

几个事实可以直接从上面的表格中读出。两次运行的 region 数、flush 次数、SST 输出大小均相同,差距不来自触发更多 flush 或写入更多字节。write_batch(parquet IO 阶段)两者也基本持平,瓶颈同样不在 IO。真正有显著差异的只有 flush_memtables:10 万的耗时是 100 万的 2.3 倍,1.29 s 的差值基本对应 1.22 s 的 wall clock 差距。cardinality 的反转,本质上是 BulkMemtable 单次 flush 耗时的反转。

对照实验:没有 flush 就没有差距

如果 flush 是差距的来源,那把总写入量缩到不会触发 flush,差距应该消失。实测也确实如此:

场景10 万100 万flush 次数
total=1M rows(不触发 flush)5,138K / 4,981K rows/sec5,425K / 3,537K rows/sec0
total=10M rows(触发 3 次 flush)2,186K rows/sec2,929K rows/sec3

没有 flush 时,两种 cardinality 分不出高低(其中一次 10 万 series 还略领先)。一旦 flush 开始触发,差距就出现了。

5000 万行规模:同样的模式

为了确认这不是 1000 万行规模下的 artifact,我们在同一台机器、同一配置下,把数据量放到 5000 万行又跑了一遍:

batch10 万 rows/sec100 万 rows/sec100 万 领先10 万 flush 次数 / 总耗时100 万 flush 次数 / 总耗时单次 flush 比值
10001,604K2,235K+39%12 / 5.80 s12 / 3.85 s1.5×
100003,171K4,090K+29%12 / 14.86 s12 / 10.22 s1.45×

两种 cardinality 下 flush 次数相同(12 次),整体形状一致,差距依然集中在单次 flush 的耗时上,10 万的 flush 比 100 万慢 ~50%。差距从 +39%(batch=1000)收窄到 +29%(batch=10000),但没有消失。1000 万和 5000 万两个规模的结论一致,不是小数据量下的噪声。

为什么只有 gRPC Bulk 看到

其他协议在写入侧都有 per-row 开销:partition 路由、行转列、schema 校验,这部分成本随 cardinality 线性增长。它大致抵消了 memtable flush 侧在高 cardinality 下的加速,所以 Stream、SDK、InfluxDB、OTLP、SQL 的波动都只在几个百分点。Bulk 把 per-row 开销压到接近零(整批 Arrow RecordBatch 直接 append 成 part),flush 侧的 cardinality 效应才显露出来。在这个量级下,它恰好对用户有利。

为什么差距这么大

gRPC 快在编码效率。Protocol Buffers 二进制格式,体积小,解析快。三种模式的区别在于连接模型:SDK 每批次独立 RPC;Stream 用双向流复用连接,省掉连接协商开销,吞吐比 Unary 高约 20-30%;Bulk 走 Arrow Flight 协议[2]做列式传输,GreptimeDB 内部也用 Arrow 做内存格式,写入接近 zero-copy,所以能到 268 万/秒(batch=2000 下到 334 万/秒)。代价是需要提前建表。

InfluxDB LP 和 OTLP 都走 HTTP,每个批次一个完整的请求-响应周期,这是它们的天花板。InfluxDB LP 虽然是文本格式,但路径本身比较薄;OTLP 还要多跑一道 greptime_identity pipeline,所以即使两者都是 HTTP + 批量模式,OTLP 这次还是落在 InfluxDB LP 下面。

SQL 慢有两层原因。第一是处理路径长:客户端拼 INSERT INTO ... VALUES (...) 文本,服务端解析 SQL,逐行做类型转换,再写入,每一步都有开销,文本体积也远大于二进制格式。第二是并发模型的限制:MySQL 和 PostgreSQL 协议都是同步连接模型,一个连接同一时间只能处理一条语句,并发能力受连接数限制,跟 gRPC 的异步流式模型差距很大。这不是 GreptimeDB 特有的问题,任何通过 SQL 协议写入的时序数据库 (Time Series Database) 都面临同样的开销。

怎么选

大多数场景用 gRPC SDK。 约 117 万 rows/sec,代码简单,支持 Schemaless,我们的官方 SDK 覆盖 Go、Java、Rust、Erlang、.NET。没有特殊需求的话,这是最稳妥的起点。如果技术栈是 JS/TS(目前没有 gRPC 的 JS 客户端),用 InfluxDB LP 或 OTLP 替代,它们有成熟的 JS 库,性能也在几十万到百万级。

批量导入用 gRPC Bulk。 数据迁移、历史回填、ETL 场景。batch=1000 时约 268 万 rows/sec,batch=2000 时超过 334 万 rows/sec,1000 万行 4 秒以内写完。需要提前建好表。Erlang SDK 暂不支持这个模式。

高频或持续高吞吐用 gRPC Stream。 IoT 网关、监控采集器这种长时间不间断写入的场景,或者写入频率很高、每次数据量不大但频次密集的场景。双向流复用连接避免了反复建连的开销,吞吐约 156 万 rows/sec,支持 Schemaless。

已有 InfluxDB 生态就用 InfluxDB Line Protocol。 在跑 Telegraf,或者应用已经输出 Line Protocol?直接接 GreptimeDB 的兼容接口,约 89 万 rows/sec,迁移成本接近零。

已有 OTel 生态就用 OTLP。 在用 OpenTelemetry Collector 或 OTel SDK?OTLP 约 62 万 rows/sec,支持 Schemaless。吞吐低于 InfluxDB LP 是因为服务端要跑 greptime_identity pipeline 把 attributes 展开成独立列 —— 这个代价换来的是与其他协议一致的存储布局。Metrics 和 Logs 的数据模型不同[3]:Metrics 按 metric name 分表,适合 Prometheus 式监控;Logs 写入统一的日志表,适合结构灵活的数据。根据实际数据模型选。

开发调试用 MySQL / PostgreSQL。 写入吞吐低(MySQL/PG 都在 7.2 万 rows/sec 左右),但 mysqlpsql 命令行、DBeaver、各种 ORM 和语言驱动都能直接连。不支持 Schemaless,需先建表。写入慢不代表查询慢,MySQL/PG 协议是 GreptimeDB 查询的主要接口。

速查表

gRPC SDKgRPC StreamgRPC BulkInfluxDB LPOTLPMySQL/PG
吞吐117 万/s156 万/s268 万/s89 万/s62 万/s~7.2 万/s
Schemaless❌ 需建表❌ 需建表
传输格式ProtobufProtobufArrow IPC文本ProtobufSQL 文本
SDK 覆盖Go/Java/Rust/Erlang/.NET同左同左(Erlang 除外)各语言各语言各语言
场景通用默认高频/持续写入批量导入InfluxDB 迁移OTel 生态查询和调试

一句话总结:性能敏感选 gRPC(SDK 起步,需要更高吞吐再上 Stream 或 Bulk),已有生态选对应的兼容协议(InfluxDB LP / OTLP),SQL 用来查询和调试。

自己跑一遍

bash
git clone https://github.com/killme2008/greptimedb-ingestion-benchmark.git
cd greptimedb-ingestion-benchmark
bin/run.sh

脚本自动下载 GreptimeDB、启动、跑完全部协议、输出结果。支持自定义:

bash
bin/run.sh -protocols grpc,grpc_bulk,influxdb -batch-size 500,1000,2000
bin/run.sh -host 10.0.0.1  # 连接远程实例

测试结果和这里的有差异?或者在特定场景下有不同发现?欢迎到 GitHub DiscussionSlack 聊聊。

参考链接


  1. GreptimeDB 写入数据 — 自动建表 ↩︎

  2. Apache Arrow Flight 协议 ↩︎

  3. GreptimeDB OpenTelemetry 数据模型 ↩︎

Stay in the loop

加入我们的社区