{"record":{"id":"386c4698dac3fde9","repo":"risingwavelabs/risingwave","slug":"empty-payload-with-non-empty-key","errorCode":null,"errorMessage":"empty payload with non-empty key","messagePattern":"empty payload with non-empty key","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/connector/src/parser/utils.rs","lineNumber":91,"sourceCode":"        .error_for_status()\n        .with_context(|| format!(\"http request failed for {location}\"))?;\n\n    let bytes = res\n        .bytes()\n        .await\n        .with_context(|| format!(\"failed to read HTTP body of {location}\"))?;\n\n    Ok(bytes)\n}\n\n// For parser that doesn't support key currently\n#[macro_export]\nmacro_rules! only_parse_payload {\n    ($self:ident, $payload:ident, $writer:ident) => {\n        if let Some(payload) = $payload {\n            $self.parse_inner(payload, $writer).await\n        } else {\n            risingwave_common::bail!(\"empty payload with non-empty key\")\n        }\n    };\n}\n\n/// Load raw bytes from:\n/// * local file, for on-premise or testing.\n/// * http/https, for common usage.\n/// * s3 file location format: <s3://bucket_name/file_name>\npub(super) async fn bytes_from_url(\n    url: &Url,\n    config: Option<&AwsAuthProps>,\n) -> ConnectorResult<Vec<u8>> {\n    match (url.scheme(), config) {\n        // TODO(Tao): support local file only when it's compiled in debug mode.\n        (\"file\", _) => {\n            let path = url\n                .to_file_path()\n                .ok()","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/connector/src/parser/utils.rs#L73-L109","documentation":"The only_parse_payload! macro bails when a Debezium/native upsert event carries no payload but does carry a key. In upsert semantics the payload (after/value) is required to know what to upsert; a key-only event cannot be interpreted, so parsing fails loudly.","triggerScenarios":"Kafka log-compacted tombstone-like messages in an upsert source where the message has a key but the value/payload field is absent rather than explicitly null-deleted; a parser invoking only_parse_payload! with None payload.","commonSituations":"Kafka compaction emitting messages with null values (tombstones) into an UPSERT source; producers writing key-only records; misconfigured Debezium serializer emitting empty value envelopes.","solutions":["Ensure producers always emit a value payload for upsert records, or use the proper delete/tombstone format supported by RisingWave (explicit null value handled as DELETE).","Check the Debezium/Kafka producer config so compacted tombstones aren't consumed as key-only non-null-payload records.","If key-only events are expected, handle them before parse_inner instead of routing through only_parse_payload!."],"exampleFix":"// before (producer)\nproducer.send(KeyRecord { key }, None); // key-only message\n// after\nproducer.send(KeyRecord { key }, Some(ValueRecord { .. }));","handlingStrategy":"validation","validationCode":"// producer-side guard before emitting an upsert record\nif value.is_none() && !is_tombstone_format_supported {\n    return Err(\"cannot emit key-only record to upsert source\".into());\n}","typeGuard":null,"tryCatchPattern":"match res {\n    Err(e) if e.to_string().contains(\"empty payload with non-empty key\") => {\n        // inspect upstream topic for key-only/tombstone messages\n    }\n    Err(e) => return Err(e.into()),\n    Ok(v) => v,\n}","preventionTips":["Enable Kafka compaction only with a delete semantics compatible with RisingWave upsert.","Monitor producer logs for key-only writes.","Ensure Debezium serializers always emit a value envelope."],"tags":["upsert","parser","kafka","payload"],"backgroundTag":"empty-required-field","analyzedSha":"6469eb736d691e8e9b8a419a57edd6429ca77417","analyzedAt":"2026-09-11T21:06:21.487Z","contentChangedAt":"2026-09-11T21:06:21.487Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}