risingwavelabs/risingwave · error · SinkError::Config
'copy-on-write' mode is not supported for append-only iceber
Error message
'copy-on-write' mode is not supported for append-only iceberg sink. Please use 'merge-on-read' instead, which is strictly better for append-only workloads.
What it means
The iceberg sink validates that an append-only sink (sink_type = 'append-only') never uses write_mode = 'copy-on-write'. Because copy-on-write offers no benefit for workloads that never update/delete existing rows, the library hard-rejects this combination and directs users to 'merge-on-read'. The check runs in validate_append_only_write_mode during sink creation.
Source
Thrown at src/connector/src/sink/iceberg/config.rs:531
IcebergCommon::enforce_one(prop)?;
}
Ok(())
}
fn enforce_one(prop: &str) -> crate::error::ConnectorResult<()> {
IcebergCommon::enforce_one(prop)
}
}
impl IcebergConfig {
/// Validate that append-only sinks use merge-on-read mode
/// Copy-on-write is strictly worse than merge-on-read for append-only workloads
pub fn validate_append_only_write_mode(
sink_type: &str,
write_mode: IcebergWriteMode,
) -> Result<()> {
if sink_type == SINK_TYPE_APPEND_ONLY && write_mode == IcebergWriteMode::CopyOnWrite {
return Err(SinkError::Config(anyhow!(
"'copy-on-write' mode is not supported for append-only iceberg sink. \
Please use 'merge-on-read' instead, which is strictly better for append-only workloads."
)));
}
Ok(())
}
pub(crate) fn validate_enable_pk_index(&self) -> Result<()> {
if !self.enable_pk_index {
return Ok(());
}
if self.r#type != SINK_TYPE_UPSERT {
return Err(SinkError::Config(anyhow!(
"`enable_pk_index` is only supported for upsert iceberg sink"
)));
}
View on GitHub (pinned to 6469eb736d)
Solutions
- Change write_mode to 'merge-on-read' in the WITH clause
- Remove the write_mode option so the default (merge-on-read) applies
- If copy-on-write is genuinely required, create the sink without the append-only type (upsert-compatible setup) — though for append-only data this is unnecessary
Example fix
// before WITH ( connector = 'iceberg', sink_type = 'append-only', write_mode = 'copy-on-write' ) // after WITH ( connector = 'iceberg', sink_type = 'append-only', write_mode = 'merge-on-read' )
Defensive patterns
Strategy: validation
Validate before calling
fn validate_sink_options(sink_type: &str, write_mode: &str) -> Result<(), String> {
if sink_type == "append-only" && write_mode == "copy-on-write" {
return Err(
"append-only sink cannot use copy-on-write; use merge-on-read".to_string(),
);
}
Ok(())
} Type guard
fn is_invalid_append_only_combo(sink_type: &str, write_mode: IcebergWriteMode) -> bool {
sink_type == "append-only" && write_mode == IcebergWriteMode::CopyOnWrite
} Try / catch
match IcebergSinkConfig::validate_append_only_write_mode(sink_type, write_mode) {
Err(e) => {
// config conflict: switch write_mode to merge-on-read before creating the sink
return Err(e.context("set write_mode = 'merge-on-read' for append-only sinks"));
}
Ok(()) => {}
} Prevention
- Only set write_mode explicitly for upsert sinks; leave it unset for append-only sinks
- Use merge-on-read by default — it is strictly better for append-only workloads
- Keep separate config templates for append-only and upsert sink types
- Validate the full WITH option set (sink_type x write_mode compatibility) before CREATE SINK
When it happens
Trigger: CREATE SINK ... WITH (connector='iceberg', sink_type='append-only' (or default append-only sink), write_mode='copy-on-write') — the combination is validated and rejected before the sink starts.
Common situations: Users copying write_mode settings from an upsert sink config; assuming copy-on-write is generally 'safer' or required for consistency; templates that set write_mode unconditionally regardless of sink_type.
Understand the failure class
Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.
Related errors
- invalid write_mode: {}, must be one of: {}, {}
- invalid compaction_type: {}, must be one of: {}, {}, {}, {}
- creating an Iceberg table with VARIANT column `{}` requires
- Invalid warehouse path: {}
- Partition source column does not exist in schema: {}
AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11).
Data as JSON: /api/errors/d00a3f6a9a0266b8.
Report an issue: GitHub.