risingwavelabs/risingwave · error · SinkError::Config

HTTP sink only supports append-only mode

Error message

HTTP sink only supports append-only mode

What it means

Configuration rejection raised in HttpSink::from_btreemap: the HTTP sink can only write rows, never emit deletes or updates, so a sink whose declaration is not append-only (e.g. an upsert sink with a primary key) cannot be instantiated. It fires when the caller passes a non-append-only sink type while creating or altering an HTTP sink.

Source

Thrown at src/connector/src/sink/http.rs:80

    pub fn from_btreemap(
        values: BTreeMap<String, String>,
    ) -> Result<(Self, BTreeMap<String, String>)> {
        // Extract header.* keys before serde parsing
        let mut headers = BTreeMap::new();
        let mut rest = BTreeMap::new();
        for (k, v) in &values {
            if let Some(header_name) = k.strip_prefix("header.") {
                headers.insert(header_name.to_owned(), v.clone());
            } else {
                rest.insert(k.clone(), v.clone());
            }
        }

        let config = serde_json::from_value::<HttpConfig>(serde_json::to_value(rest).unwrap())
            .map_err(|e| SinkError::Config(anyhow!(e)))?;

        if config.r#type != SINK_TYPE_APPEND_ONLY {
            return Err(SinkError::Config(anyhow!(
                "HTTP sink only supports append-only mode"
            )));
        }

        Ok((config, headers))
    }
}

#[derive(Clone, Debug)]
enum HttpUrl {
    Static(reqwest::Url),
    Dynamic { url_index: usize },
}

/// Validates the HTTP sink parameters and returns the sink so callers can use it directly without
/// re-parsing.
fn validate_http_sink(
    is_append_only: bool,

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Set type='append-only' in the sink WITH options
  2. Ensure the backing stream/MV is append-only (no UPDATE/DELETE)
  3. Use a sink connector that supports upsert/debezium modes (e.g. JDBC/Kafka) if you need mutations

Example fix

// before
WITH (connector='http', url='...', type='debezium')
// after
WITH (connector='http', url='...', type='append-only')
Defensive patterns

Strategy: validation

Validate before calling

// reject non-append-only types before sink creation
if let Some(t) = options.get("type") {
    if t != "append-only" { return Err(format!("HTTP sink requires type='append-only', got '{t}'")); }
}

Try / catch

match HttpSink::from_btreemap(&props, ...) {
    Err(SinkError::Config(e)) if e.to_string().contains("append-only") => {
        log::error!("HTTP sink configured over mutating stream: {e}");
    }
    r => r?,
}

Prevention

When it happens

Trigger: `from_btreemap` parses HttpConfig whose `r#type` field does not equal SINK_TYPE_APPEND_ONLY (e.g. user set type='debezium' or an upsert variant).

Common situations: User configures an HTTP sink over a source with updates/deletes and sets type to something other than 'append-only'; copy-pasting sink configs from Kafka/Debezium examples.

Related errors


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