diff --git a/config/config.md b/config/config.md
index 29deeb15234..694359713eb 100644
--- a/config/config.md
+++ b/config/config.md
@@ -75,7 +75,7 @@
| `influxdb` | -- | -- | InfluxDB protocol options. |
| `influxdb.enable` | Bool | `true` | Whether to enable InfluxDB protocol in HTTP API. |
| `influxdb.default_merge_mode` | String | `last_non_null` | Default merge mode for tables automatically created by InfluxDB protocol.
Available values: "last_non_null", "last_row". |
-| `pending_rows_batcher` | -- | -- | Ordinary-table batching for opted-in HTTP ingestion protocols.
Omitted or empty protocols disables batching. Prom without metric engine uses this batcher.
OTLP logs, traces and ordinary metrics use this batcher. |
+| `pending_rows_batcher` | -- | -- | Ordinary-table batching for opted-in HTTP ingestion protocols.
PENDING_ROWS_BATCH_SYNC defaults to true for both batchers. Set it to false to acknowledge
queue admission without waiting for storage; later failures cannot be returned to the client.
Omitted or empty protocols disables batching. Prom without metric engine uses this batcher.
OTLP logs, traces and ordinary metrics use this batcher. |
| `pending_rows_batcher.pending_rows_flush_interval` | String | `0s` | Flush interval measured from the first pending submission. Zero disables batching. |
| `pending_rows_batcher.max_batch_rows` | Integer | `100000` | Flush after a complete submission reaches this row threshold. |
| `pending_rows_batcher.max_concurrent_flushes` | Integer | `256` | Maximum concurrent flushes shared by the frontend batcher. |
@@ -343,7 +343,7 @@
| `influxdb` | -- | -- | InfluxDB protocol options. |
| `influxdb.enable` | Bool | `true` | Whether to enable InfluxDB protocol in HTTP API. |
| `influxdb.default_merge_mode` | String | `last_non_null` | Default merge mode for tables automatically created by InfluxDB protocol.
Available values: "last_non_null", "last_row". |
-| `pending_rows_batcher` | -- | -- | Ordinary-table batching for opted-in HTTP ingestion protocols.
Omitted or empty protocols disables batching. Prom without metric engine uses this batcher.
OTLP logs, traces and ordinary metrics use this batcher. |
+| `pending_rows_batcher` | -- | -- | Ordinary-table batching for opted-in HTTP ingestion protocols.
PENDING_ROWS_BATCH_SYNC defaults to true for both batchers. Set it to false to acknowledge
queue admission without waiting for storage; later failures cannot be returned to the client.
Omitted or empty protocols disables batching. Prom without metric engine uses this batcher.
OTLP logs, traces and ordinary metrics use this batcher. |
| `pending_rows_batcher.pending_rows_flush_interval` | String | `0s` | Flush interval measured from the first pending submission. Zero disables batching. |
| `pending_rows_batcher.max_batch_rows` | Integer | `100000` | Flush after a complete submission reaches this row threshold. |
| `pending_rows_batcher.max_concurrent_flushes` | Integer | `256` | Maximum concurrent flushes shared by the frontend batcher. |
diff --git a/config/frontend.example.toml b/config/frontend.example.toml
index 2e9ee1d4e93..4263c0bc7c3 100644
--- a/config/frontend.example.toml
+++ b/config/frontend.example.toml
@@ -232,6 +232,8 @@ enable = true
default_merge_mode = "last_non_null"
## Ordinary-table batching for opted-in HTTP ingestion protocols.
+## PENDING_ROWS_BATCH_SYNC defaults to true for both batchers. Set it to false to acknowledge
+## queue admission without waiting for storage; later failures cannot be returned to the client.
## Omitted or empty protocols disables batching. Prom without metric engine uses this batcher.
## OTLP logs, traces and ordinary metrics use this batcher.
[pending_rows_batcher]
diff --git a/config/standalone.example.toml b/config/standalone.example.toml
index 813a121a630..aa6f894d38e 100644
--- a/config/standalone.example.toml
+++ b/config/standalone.example.toml
@@ -211,6 +211,8 @@ enable = true
default_merge_mode = "last_non_null"
## Ordinary-table batching for opted-in HTTP ingestion protocols.
+## PENDING_ROWS_BATCH_SYNC defaults to true for both batchers. Set it to false to acknowledge
+## queue admission without waiting for storage; later failures cannot be returned to the client.
## Omitted or empty protocols disables batching. Prom without metric engine uses this batcher.
## OTLP logs, traces and ordinary metrics use this batcher.
[pending_rows_batcher]
diff --git a/src/frontend/src/instance/otlp.rs b/src/frontend/src/instance/otlp.rs
index 231fc874880..7a0f706c58d 100644
--- a/src/frontend/src/instance/otlp.rs
+++ b/src/frontend/src/instance/otlp.rs
@@ -189,7 +189,7 @@ impl OpenTelemetryProtocolHandler for Instance {
// OTLP tables have one sample field in both the legacy and physical paths.
let output = if let Some(batcher) = batcher {
let (rows, cost) = batcher
- .submit_sync(requests, ctx.clone(), |mut requests| {
+ .submit_with(requests, ctx.clone(), |mut requests| {
let ctx = ctx.clone();
async move {
Inserter::meter_row_inserts(&mut requests, &ctx)
@@ -216,9 +216,9 @@ impl OpenTelemetryProtocolHandler for Instance {
};
outcome.write_cost = output.meta.cost;
- // Derived enrichment, written after the metric data is committed:
- // failing here would make the client retry data the server already
- // accepted, so every failure degrades to a warning instead.
+ // Derived enrichment follows the accepted metric submission, which may
+ // still be queued in asynchronous mode. Failures remain warning-only
+ // to avoid retrying metric data the server already accepted.
if let Some(resource_info) = resource_info {
let written = match self.check_row_insert_permission(
&resource_info,
diff --git a/src/frontend/src/server.rs b/src/frontend/src/server.rs
index c07b57bafb2..0a6612ba31c 100644
--- a/src/frontend/src/server.rs
+++ b/src/frontend/src/server.rs
@@ -24,7 +24,7 @@ use common_base::Plugins;
use common_config::Configurable;
use common_telemetry::{info, warn};
use meta_client::MetaClientOptions;
-use servers::batcher::logical_table::pending_rows_batch_sync_enabled;
+use servers::batcher::pending_rows_batch_sync_enabled;
use servers::error::Error as ServerError;
use servers::grpc::builder::GrpcServerBuilder;
use servers::grpc::flight::FlightCraftRef;
@@ -449,9 +449,8 @@ fn effective_http_options_with_sync(opts: &FrontendOptions, batch_sync: bool) ->
let mut http = opts.http.clone();
let prom_store = effective_prom_store_options(opts);
let shared = opts.table_batcher_options();
- // Ordinary-table batching always waits for its flush, independently of the
- // dedicated Prom batcher's asynchronous acknowledgement mode.
- let common_enabled = shared.pending_rows_batching_enabled()
+ let common_enabled = batch_sync
+ && shared.pending_rows_batching_enabled()
&& shared.protocols.iter().any(|protocol| {
*protocol != BatchingProtocol::Prom
|| (prom_store.enable && !prom_store.with_metric_engine)
@@ -460,7 +459,8 @@ fn effective_http_options_with_sync(opts: &FrontendOptions, batch_sync: bool) ->
let prom_interval = (prom_store.pending_rows_batching_enabled() && batch_sync)
.then_some(prom_store.pending_rows_flush_interval);
let logical = opts.logical_batcher_options();
- let otlp_interval = (opts.otlp.enable
+ let otlp_interval = (batch_sync
+ && opts.otlp.enable
&& opts.prom_store.with_metric_engine
&& logical.protocols.contains(&BatchingProtocol::Otlp)
&& logical.pending_rows_batching_enabled())
@@ -561,13 +561,17 @@ mod tests {
..Default::default()
});
assert!(!effective_prom_store_options(&opts).pending_rows_batching_enabled());
- // OTLP waits for storage even when Prom acknowledges asynchronously,
- // and does not depend on enabling the Prom HTTP endpoint.
+ // Both logical protocols follow the global acknowledgement policy,
+ // independently of enabling the Prom HTTP endpoint.
opts.prom_store.enable = false;
assert_eq!(
- effective_http_options_with_sync(&opts, false).timeout,
+ effective_http_options_with_sync(&opts, true).timeout,
Duration::from_secs(6)
);
+ assert_eq!(
+ effective_http_options_with_sync(&opts, false).timeout,
+ opts.http.timeout
+ );
opts.prom_store.with_metric_engine = false;
assert_eq!(
effective_http_options_with_sync(&opts, false).timeout,
@@ -640,7 +644,7 @@ mod tests {
#[test]
fn test_http_timeout_covers_synchronous_batchers() {
- // Shared ordinary writes remain synchronous even when Prom is asynchronous.
+ // Only synchronous batchers extend the HTTP timeout.
for (
protocols,
metric_engine,
@@ -650,10 +654,10 @@ mod tests {
timeout_secs,
expected_secs,
) in [
- (vec![BatchingProtocol::Prom], false, false, 5, 2, 1, 6),
+ (vec![BatchingProtocol::Prom], false, false, 5, 2, 1, 1),
(vec![BatchingProtocol::Prom], true, false, 5, 2, 1, 1),
(vec![BatchingProtocol::Prom], true, true, 5, 2, 1, 6),
- (vec![BatchingProtocol::Influxdb], true, false, 5, 2, 1, 6),
+ (vec![BatchingProtocol::Influxdb], true, false, 5, 2, 1, 1),
(vec![BatchingProtocol::Influxdb], true, true, 5, 8, 1, 9),
(vec![BatchingProtocol::Influxdb], true, true, 8, 5, 1, 9),
(vec![BatchingProtocol::Influxdb], true, false, 5, 2, 0, 0),
diff --git a/src/operator/src/batcher.rs b/src/operator/src/batcher.rs
index 4c5e9892c0b..677524d53f3 100644
--- a/src/operator/src/batcher.rs
+++ b/src/operator/src/batcher.rs
@@ -29,7 +29,8 @@ pub trait PendingRowsBatcher: Send + Sync {
/// Acquires one slot per original request, shared by all of its table submissions.
async fn acquire(&self) -> Result>;
- /// Waits for the submitted rows to be written, retaining the slot through completion.
+ /// Submits rows according to the acknowledgement policy, retaining the slot until
+ /// writing completes even when the response acknowledges queue admission only.
/// Cancelling the response wait does not retract an already enqueued submission.
async fn submit(
&self,
diff --git a/src/servers/src/batcher.rs b/src/servers/src/batcher.rs
index 10f8f558eb3..6b6ca8f7ee7 100644
--- a/src/servers/src/batcher.rs
+++ b/src/servers/src/batcher.rs
@@ -21,3 +21,21 @@ pub mod table;
#[cfg(test)]
mod test_util;
+
+/// Controls whether batching waits for storage before replying to the client.
+const PENDING_ROWS_BATCH_SYNC_ENV: &str = "PENDING_ROWS_BATCH_SYNC";
+
+/// Returns whether pending-row batch submissions wait for the flush result
+/// before replying to the client (synchronous mode), controlled by the
+/// `PENDING_ROWS_BATCH_SYNC` environment variable and defaulting to `true`.
+///
+/// Callers that reason about how long a remote write request may block (e.g.
+/// the frontend HTTP timeout fallback) must consult this instead of
+/// duplicating the env lookup.
+pub fn pending_rows_batch_sync_enabled() -> bool {
+ std::env::var(PENDING_ROWS_BATCH_SYNC_ENV)
+ .ok()
+ .as_deref()
+ .and_then(|v| v.parse::().ok())
+ .unwrap_or(true)
+}
diff --git a/src/servers/src/batcher/logical_table.rs b/src/servers/src/batcher/logical_table.rs
index a340e76e3df..bd7a26da613 100644
--- a/src/servers/src/batcher/logical_table.rs
+++ b/src/servers/src/batcher/logical_table.rs
@@ -55,6 +55,7 @@ pub use crate::batcher::logical_table::region_write::{
pub use crate::batcher::logical_table::tables::{
PendingRowsSchemaAlterer, PendingRowsSchemaAltererRef,
};
+use crate::batcher::pending_rows_batch_sync_enabled;
use crate::error;
use crate::error::{Error, Result};
use crate::metrics::{
@@ -63,24 +64,6 @@ use crate::metrics::{
const PHYSICAL_TABLE_KEY: &str = "physical_table";
-/// Whether wait for ingestion result before reply to client.
-const PENDING_ROWS_BATCH_SYNC_ENV: &str = "PENDING_ROWS_BATCH_SYNC";
-
-/// Returns whether pending-row batch submissions wait for the flush result
-/// before replying to the client (synchronous mode), controlled by the
-/// `PENDING_ROWS_BATCH_SYNC` environment variable and defaulting to `true`.
-///
-/// Callers that reason about how long a remote write request may block (e.g.
-/// the frontend HTTP timeout fallback) must consult this instead of
-/// duplicating the env lookup.
-pub fn pending_rows_batch_sync_enabled() -> bool {
- std::env::var(PENDING_ROWS_BATCH_SYNC_ENV)
- .ok()
- .as_deref()
- .and_then(|v| v.parse::().ok())
- .unwrap_or(true)
-}
-
const WORKER_IDLE_TIMEOUT_MULTIPLIER: u32 = 3;
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
@@ -183,34 +166,19 @@ impl LogicalTablePendingRowsBatcher {
impl LogicalTablePendingRowsBatcher {
pub async fn submit(&self, requests: RowInsertRequests, ctx: QueryContextRef) -> Result {
- self.submit_inner(requests, ctx, self.pending_rows_batch_sync, |_| {
- ready(Ok(()))
- })
- .await
- .map(|(rows, ())| rows)
+ self.submit_with(requests, ctx, |_| ready(Ok(())))
+ .await
+ .map(|(rows, ())| rows)
}
- /// Waits for storage regardless of the Prom acknowledgement mode. The hook
- /// runs after schema preparation, before admission, for request-level accounting.
- pub async fn submit_sync(
+ /// Submits with request-level accounting after schema preparation and before
+ /// queue admission. Acknowledgement follows the global batching policy.
+ pub async fn submit_with(
&self,
requests: RowInsertRequests,
ctx: QueryContextRef,
after_prepare: impl FnOnce(RowInsertRequests) -> F,
) -> Result<(u64, T)>
- where
- F: Future