@Greptime: 关于Prometheus远程写入,瓶颈并非网络或memtable——而是Region Worker在dec…时持有&mut

X AI KOLs Following 产品

摘要

GreptimeDB v1.0引入了Pending Rows Batcher,这是一个三阶段流水线,将CPU密集型工作从Datanode的关键路径上移开,使Prometheus远程写入吞吐量从120万提升到217万points/sec,并将Datanode的CPU使用率降低20%。

在Prometheus远程写入中,瓶颈并非网络或memtable——而是Region Worker在解码行、编码主键和对齐schema时持有&mut。 GreptimeDB v1.0将这项工作从关键路径上移除,并移至前端(Pending Rows Batcher)。现在Datanode直接将预编码的Arrow IPC摄入BulkMemtable。 1.20M → 2.17M points/sec。Datanode CPU降低20%。 https://greptime.com/blogs/2026-05-26-pending-rows-batcher…
查看原文
查看缓存全文

缓存时间: 2026/06/03 17:54

关于 Prometheus remote write,瓶颈并不在网络或 memtable —— 而是在于 Region Worker 在解码行数据、编码主键和对齐 schema 时持有 &mut 引用。

GreptimeDB v1.0 将这些工作移出临界区,转移到 Frontend(Pending Rows Batcher)。现在 Datanode 直接将预编码的 Arrow IPC 摄入 BulkMemtable。

1.20M → 2.17M 点/秒。Datanode CPU 降低 20%。

https://greptime.com/blogs/2026-05-26-pending-rows-batcher…


Pending Rows Batcher:GreptimeDB 中 Prometheus 数据摄入提速 80%

来源:https://greptime.com/blogs/2026-05-26-pending-rows-batcher GreptimeDB v1.0 在 Prometheus Remote Write [1] 路径上引入了 Pending Rows Batcher。在一个 16 Region 的物理表上,相同负载下,吞吐量从 1.20M 点/秒提升至 2.17M 点/秒,同时 Datanode CPU 使用率下降约 20%。

代价是 Frontend CPU 大约翻倍。我们将原本位于 Datanode 热路径上的工作移到了 Frontend,从而使批处理数据能以列式格式直接流向存储层。本文深入介绍了新的写入路径及其解决的问题。

为什么 Prometheus 数据摄入对时序数据库来说很困难 (https://greptime.com/blogs/2026-05-26-pending-rows-batcher#why-prometheus-ingestion-is-hard-on-a-time-series-database)

在可观测性工作负载中,Prometheus 通过 Remote Write 协议将指标推送到远程存储。每个请求通常只携带几十到几百个数据点,但请求速率非常高。数据库必须跟上持续不断的小型碎片化写入流。

在原始的逐行写入路径上,每个请求独立触发一组固定的操作:

  • 表 schema 解析
  • Schema 对齐:缺失列执行 ALTER TABLE,缺失表执行 CREATE TABLE
  • 行编码和 gRPC 传输
  • 在 Datanode 上进行逐行解码、主键编码、Memtable 写入和 WAL 追加

其中许多是重复的固定开销。连续写入同一张表会重复触发相同的 schema 检查和格式转换。随着 TPS 上升,这些开销主导了吞吐量。

真正的瓶颈:Region Worker 的临界区 (https://greptime.com/blogs/2026-05-26-pending-rows-batcher#the-real-bottleneck-the-region-worker-s-critical-section)

固定开销只是故事的一半。更深的瓶颈位于 Datanode 内部。

在 GreptimeDB 中,对每个 Region 的写入通过专用的 Region Worker 串行化。为了维护写入顺序,Worker 在处理写入时持有 Region 的 &mut 引用,这构成了一个临界区。在该临界区被持有时,其他任务无法接触该 Region。

在逐行写入路径上,临界区内部的工作包括:

  • 行解码与校验
  • 主键编码与排序
  • Schema 对齐与列转换
  • Memtable 写入
  • WAL 追加

编码和排序是 CPU 密集型的,它们显著延长了锁持有时间。在高 TPS 下,Region Worker 成为瓶颈:新请求在排队等待正在执行临界区的请求完成。

减少固定开销并缩短临界区——这就是 Pending Rows Batcher 的目的。

解决方案:三阶段流水线 (https://greptime.com/blogs/2026-05-26-pending-rows-batcher#the-solution-a-three-stage-pipeline)

Pending Rows Batcher 在 Frontend 上将来自多个请求的行数据合并,一次性进行 schema 对齐和行到列的转换,然后通过 BulkInsert RPC 将列式批量数据直接写入 Datanode。该路径包含三个部分:

Pending Rows Batcher 整体数据流 图 1:Pending Rows Batcher 整体数据流

  • Frontend 上的 Batcher 负责批处理和行到列的转换
  • BulkInsert 路径传输列式批量数据
  • Datanode 上的 BulkMemtable[2] 以列式形式接收数据

该流程分为三个阶段:在入口提交并对齐 schema,按物理表积累批量数据,然后将列式批量数据批量写入 Datanode。

三阶段批处理流程 图 2:三阶段批处理流程

其思想是将昂贵的操作——解码、编码、排序、schema 对齐——从 Datanode 的临界区移到 Frontend 上。Region Worker 只需消费预编码的 RecordBatches。

写入路径上的关键点 (https://greptime.com/blogs/2026-05-26-pending-rows-batcher#key-points-along-the-write-path)

提交与 Schema 对齐 (https://greptime.com/blogs/2026-05-26-pending-rows-batcher#submit-and-schema-alignment)

当 Prometheus Remote Write 请求到达时,Batcher 将其转换为 Arrow RecordBatch[3] 并在过程中对齐 schema:

  • 对于已存在的逻辑表,检测并添加缺失的标签列,通过批量 ALTER TABLE 调用完成
  • 对于之前未见的指标表,通过批量 CREATE TABLE 调用自动创建表
  • 所有 DDL 操作按物理表分组并批量执行,避免了每次请求的 DDL 开销

Schema 变更的开销被分摊到整个批次中,而不是逐请求支付。

逻辑表 vs. 物理表 (https://greptime.com/blogs/2026-05-26-pending-rows-batcher#logical-tables-vs-physical-tables)

首先简要介绍 Metric Engine 抽象。

在 Prometheus 工作负载中,每个指标对用户暴露为一张逻辑表——不同的指标名称映射到不同的逻辑表。在内部,多个逻辑表可以映射到同一张物理表,而该物理表负责实际的数据写入、分区和 Region 管理。

Metric Engine:逻辑表映射到单个物理宽表 图 3:Metric Engine 将多个逻辑表映射到一张物理宽表

因此,Batcher 并不是简单地“每个指标一个批次”。它首先在逻辑表级别对齐 schema,然后根据它们映射到的物理表进行合并。共享同一张物理表的多个指标也共享一个 Worker 和刷新节奏,它们最终在发往 Datanode 的途中合并成一个更大的列式批次。

这就是“按物理表分区”这个说法反复出现的原因。逻辑表是用户看到的指标表及其 schema;物理表则决定了数据在底层如何组织和调度以进行批量写入。

Worker 批处理:两个刷新触发器 (https://greptime.com/blogs/2026-05-26-pending-rows-batcher#worker-batching-two-flush-triggers)

每个物理表都有一个专用的后台 Worker。提交给 Worker 的 RecordBatches 持续积累,直到两个触发器之一被触发。总行数达到 max_batch_rows(默认 100,000)时 Worker 立即刷新;或者自上次刷新以来 pending_rows_flush_interval 时间已过,Worker 按定时器刷新。闲置超过 3× 刷新间隔的 Worker 会自动关闭以释放资源。

定时刷新限制了延迟下限;满批次刷新则最大化批次大小。两者一起在低延迟和大批次之间取得平衡。

列式批量写入 (https://greptime.com/blogs/2026-05-26-pending-rows-batcher#columnar-bulk-write)

刷新时,Batcher 合并批次中所有逻辑表的 RecordBatches,编码为 Arrow IPC,然后通过单个 BulkInsert RPC 写入目标 Region。与原始的逐行 Insert 相比,这减少了网络往返次数,让 Datanode 可以直接处理列式数据而无需进行行到列的转换,并且支持基于分区的拆分,从而实现对不同 Region 的并行写入。

临界区如何变短 (https://greptime.com/blogs/2026-05-26-pending-rows-batcher#how-the-critical-section-gets-shorter)

映射回临界区瓶颈,路径上的分工如下:

  • Frontend(临界区外):行到列转换、schema 对齐、主键编码、分区拆分。这些 CPU 密集型操作在 Batcher 内部异步执行,不会占用任何 Region Worker。
  • Datanode(临界区内):当 Region Worker 收到 BulkInsert 请求时,它直接将已经编码的 Arrow IPC RecordBatch 推入 Region。无需解码、无需排序、无需逐行处理。

临界区内的工作从“解码 + 主键编码 + 排序 + Memtable 写入 + WAL”缩减为“摄入预编码的 RecordBatch”。锁持有时间急剧下降,Region Worker 的吞吐量随之提升。

端到端列式化:BulkMemtable 的作用 (https://greptime.com/blogs/2026-05-26-pending-rows-batcher#columnar-end-to-end-the-role-of-bulkmemtable)

一旦 Frontend 将行数据转换为 Arrow RecordBatch,接下来的问题是这些列式数据在 Datanode 端如何处理。

如果 Datanode 仍然使用原始的基于行的 Memtable(TimeSeriesMemtable),那么列式批次会被重新拆解成逐行的 Mutations,逐个主键编码,然后逐行插入。Batcher 通过合并获得的任何优势都会被抵消。网络虽然节省了几次往返,但存储层又会退回到逐行处理。

原始基于行的路径 vs. Batcher + BulkMemtable 列式路径 图 4:原始基于行的路径 vs. Batcher + BulkMemtable 列式路径

同样在 GreptimeDB v1.0 中引入的 BulkMemtable 弥补了这一差距。它是一个专门为列式批量写入构建的 Memtable:只接受 RecordBatch 输入,内部以列式布局存储数据,并在扫描时暴露 RecordBatch 迭代器。

有了 BulkMemtable,完整的写入路径如下:

Prometheus Remote Write → Batcher(行到列 + 积累) → BulkInsert RPC(Arrow IPC) → BulkMemtable(列式存储) → 刷新(列式 Parquet) → 查询(列式扫描)

在 Batcher 完成单次“行→列”转换后,数据再也不会回到行格式。这带来了:

  • 无格式转换开销:BulkMemtable 直接接收 RecordBatches,无需拆解成逐行结构
  • 零拷贝刷新:已经编码为内存 Parquet 字节的数据直接进入 SST 文件
  • 向量化查询:扫描时发出的 RecordBatches 可以被 DataFusion 直接消费

BulkMemtable 的内部设计——分区层次结构、合并策略、内存 Parquet 编码——本身是一个深入的话题。我们在另一篇文章中进行了介绍:优化高基数时序数据库:GreptimeDB 的 Flat 格式设计 (https://greptime.com/blogs/2025-12-22-flat-format)。本文的要点更简单:Pending Rows Batcher 和 BulkMemtable 共同构成了从入口到存储的列式管道。

性能 (https://greptime.com/blogs/2026-05-26-pending-rows-batcher#performance)

基准测试针对一个包含 16 个 Region 的物理表运行。在相同的 Prometheus Remote Write 负载下,我们测量了默认模式和使用 Pending Rows Batcher 模式下的写入吞吐量和资源使用情况。

写入吞吐量 (https://greptime.com/blogs/2026-05-26-pending-rows-batcher#write-throughput)

写入吞吐量对比:默认模式 vs. Batcher 模式 图 5:写入吞吐量对比——默认模式 vs. Batcher 模式

启用 Pending Rows Batcher 后,写入吞吐量从 1.20M 点/秒提升至 2.17M 点/秒,提升了 81%。

资源使用 (https://greptime.com/blogs/2026-05-26-pending-rows-batcher#resource-usage)

指标默认模式Batcher 模式变化
写入吞吐量1.20M 点/秒2.17M 点/秒+81%
Datanode CPU14.9 核11.95 核-20%
Datanode 内存7.14 GB6.36 GB-11%
Frontend CPU5.2 核10.15 核+95%
Frontend 内存2.09 GB2.04 GB持平

Frontend CPU 增加是预期的。Schema 对齐、行到列转换和主键编码都移到了那里。这种权衡是用 Frontend 计算换取更高的吞吐量和更低的 Datanode 负载。在大多数部署中,Datanode 是受限资源,因为它直接与存储、刷新和查询性能相关,而 Frontend 是无状态的且易于水平扩展。这种权衡在生产环境中得到了回报。

原始监控数据 (https://greptime.com/blogs/2026-05-26-pending-rows-batcher#raw-monitoring-data)

以下两张 Grafana 截图是在基准测试期间捕获的,可作为上述数字的原始参考。

默认模式下的 Grafana 仪表板 图 6:默认模式下的 Grafana 仪表板

Batcher 模式下的 Grafana 仪表板 图 7:Batcher 模式下的 Grafana 仪表板

如何启用 (https://greptime.com/blogs/2026-05-26-pending-rows-batcher#how-to-enable)

批量写入模式在 Frontend 上配置。如果您的负载主要由高 TPS、小批量的 Prometheus Remote Write 请求组成,请启用 Metric Engine 并在 Frontend 配置中设置 Pending Rows Batcher 参数:

[prom_store]
enable = true
with_metric_engine = true
pending_rows_flush_interval = "5s"
max_batch_rows = 20000
max_concurrent_flushes = 256
worker_channel_capacity = 65526
max_inflight_requests = 3000

关键参数:

  • enable:启用 Prometheus Remote Write 存储入口点。
  • with_metric_engine:通过 Metric Engine 存储 Prometheus 指标。这是逻辑/物理表映射和批量写入路径的前提。
  • pending_rows_flush_interval:批处理时间窗口。设置为 0 以禁用 Batcher。示例中使用 5 秒。
  • max_batch_rows:每批次的最大行数。达到此值后立即刷新。示例中使用 20,000。
  • max_concurrent_flushes:同时进行中的最大刷新次数。
  • worker_channel_capacity:每个 Worker 的通道容量,用于缓冲提交给该物理表 Worker 的请求。
  • max_inflight_requests:最大进行中的请求数,用作背压以防止 Frontend 堆积未完成的写入。

总结 (https://greptime.com/blogs/2026-05-26-pending-rows-batcher#summary)

Pending Rows Batcher 使用三阶段流水线(对齐 schema、积累、列式批量写入),将 Prometheus Remote Write 吞吐量提升了 80% 以上,同时降低了 Datanode 资源使用。

它所做的是重新分配 Frontend 和 Datanode 之间的工作。Frontend 承担了更多的预处理(积累、schema 对齐、行到列转换、主键编码),这样 Datanode 的 Region Worker 可以专注于真正需要在临界区内执行的操作。结合 BulkMemtable 的列式透传,写入路径不再在行格式和列格式之间来回切换。

对于高基数、高 TPS 的可观测性工作负载,启用新路径只需在 Frontend 配置中添加几行。

相关 PR:#7831 (https://github.com/GreptimeTeam/greptimedb/pull/7831)、#7877 (https://github.com/GreptimeTeam/greptimedb/pull/7877)、#7902 (https://github.com/GreptimeTeam/greptimedb/pull/7902)、#8054 (https://github.com/GreptimeTeam/greptimedb/pull/8054)。


参考文献 (https://greptime.com/blogs/2026-05-26-pending-rows-batcher#references)


  1. Prometheus Remote Write 规范 (https://prometheus.io/docs/specs/remote_write_spec/) ↩︎ (https://greptime.com/blogs/2026-05-26-pending-rows-batcher#fnref1)
  2. GreptimeDB Flat 格式和 BulkMemtable 设计 (https://greptime.com/blogs/2025-12-22-flat-format) ↩︎ (https://greptime.com/blogs/2026-05-26-pending-rows-batcher#fnref2)
  3. Apache Arrow RecordBatch (https://arrow.apache.org/docs/cpp/ipc.html) ↩︎ (https://greptime.com/blogs/2026-05-26-pending-rows-batcher#fnref3)

相似文章

@Greptime: GreptimeDB v1.1.2 已发布 — 这是一个值得升级的 v1.1 补丁。主要修复:定时 Flows 现在绑定 now()/current_timest…

X AI KOLs Following

GreptimeDB v1.1.2 是一个补丁版本,修复了定时 Flows 中 now() 的绑定问题,以确保 EVAL INTERVAL 窗口的确定性。此外还修复了以下问题:Kafka SASL 密码在调试输出中的屏蔽、GC 索引文件列表、parquet 元数据缓存大小、Prometheus 标签发现扫描以及 PromQL 时间二元聚合。建议用户升级。