risingwavelabs/risingwave · error · BatchError

no chunk in IngestDmlPayloadRequest

Error message

no chunk in IngestDmlPayloadRequest

What it means

Raised in do_ingest_dml_payload when an IngestDmlPayloadRequest arrives over the batch/DML gRPC service without a `chunk` field. The handler requires exactly one data chunk to decode into a StreamChunk; a request without one is an internal protocol violation and is reported as BatchError::Internal with this message.

Source

Thrown at src/batch/src/rpc/service/task_service.rs:338

        let table_version_id = init.table_version_id;
        let table_dml_handle = self
            .env
            .dml_manager_ref()
            .table_dml_handle(table_id, table_version_id)
            .map_err(|err| Status::internal(format!("{}", err.as_report())))?;
        Ok((table_dml_handle, init.request_id, init.row_id_index))
    }

    async fn do_ingest_dml_payload(
        table_dml_handle: TableDmlHandleRef,
        dml_manager: DmlManagerRef,
        request_id: u32,
        row_id_index: Option<u32>,
        payload: IngestDmlPayloadRequest,
    ) -> Result<impl Future<Output = risingwave_dml::error::Result<()>> + Send + 'static, BatchError>
    {
        let pb_chunk = payload.chunk.ok_or_else(|| {
            BatchError::Internal(anyhow::anyhow!("no chunk in IngestDmlPayloadRequest"))
        })?;
        let mut chunk = StreamChunk::from_protobuf(&pb_chunk)
            .context("failed to decode chunk")
            .map_err(BatchError::Internal)?;
        chunk = inject_optional_row_id_column(chunk, row_id_index.map(|index| index as usize));
        let txn_id = dml_manager.gen_txn_id();
        let mut write_handle = table_dml_handle
            .write_handle(request_id, txn_id)
            .map_err(BatchError::Dml)?;

        write_handle.begin().map_err(BatchError::Dml)?;
        write_handle
            .write_chunk(chunk)
            .await
            .map_err(BatchError::Dml)?;
        let persistence_future = write_handle
            .end_wait_persistence()
            .map_err(BatchError::Dml)?;

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Fix the caller to always populate `chunk` before sending IngestDmlPayloadRequest.
  2. Skip issuing the ingest RPC entirely when there are no rows to write.
  3. Check client/SDK version skew against the cluster's protobuf definitions and upgrade.
  4. If it comes from a driver, capture the request with logging and report/inspect why the chunk was empty.

Example fix

// before
// client sends payload even for empty batch
send(IngestDmlPayloadRequest { request_id, chunk: None });

// after
if !rows.is_empty() {
    send(IngestDmlPayloadRequest { request_id, chunk: Some(to_protobuf_chunk(rows)) });
}
Defensive patterns

Strategy: validation

Validate before calling

// Client-side guard before sending the RPC
if payload.chunk.is_none() {
    return; // skip ingest call entirely for empty batches
}

Try / catch

match do_ingest_dml_payload(...).await {
    Err(BatchError::Internal(msg)) if msg.contains("no chunk in IngestDmlPayloadRequest") => {
        // treat as empty batch: skip or re-send with a valid chunk
    }
    other => other?,
}

Prevention

When it happens

Trigger: A client (or older/newer SDK) calls the ingest DML payload RPC (e.g. from the PostgreSQL wire-protocol DML path) but sends the request with `chunk = None` — e.g. an empty batch, a truncated write, or a client that skips chunk serialization when there are no rows.

Common situations: Writing zero rows via a driver that still issues an ingest call, version skew between frontend and client protocol handling, custom tooling built against the DML gRPC API omitting the chunk field.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11). Data as JSON: /api/errors/821f7c0025b92aa8. Report an issue: GitHub.