databendlabs/databend · critical
Error parsing dictionary
Error message
Error parsing dictionary
What it means
After the Arrow IPC message header verifies, the code asks for the message's `header_as_dictionary_batch()` and unwraps it with `.expect("Error parsing dictionary")`. A `None` here means the message parsed but its header type is not a `DictionaryBatch` — the packet labeled as a dictionary contained a different batch type (e.g., a RecordBatch) or an unsupported/empty header. This is a wire-format invariant violation on the exchange path and panics the deserializer.
Solutions
- Ensure all nodes run matching versions of Databend/Arrow so dictionary packet framing agrees on both ends.
- Inspect the sender side: confirm `DataPacket::Dictionary` packets are only created from genuine dictionary-batch IPC messages.
- If data came from a spill file, verify the file was not partially written or truncated, then re-run the query.
- Return a typed error instead of panicking so the affected query fails without killing the deserialization task.
Example fix
// before
message
.header_as_dictionary_batch()
.expect("Error parsing dictionary"),
// after
message.header_as_dictionary_batch().ok_or_else(|| {
ErrorCode::Internal(
"IPC message is not a dictionary batch in DataPacket::Dictionary",
)
})?, Defensive patterns
Strategy: validation
Validate before calling
// Check header type before read_dictionary
let message = root_as_message(&data.data_header[..]).map_err(|e| ErrorCode::Internal(format!("bad IPC message: {e}")))?;
if message.header_as_dictionary_batch().is_none() {
return Err(ErrorCode::Internal("DataPacket::Dictionary did not contain a dictionary batch"));
} Type guard
fn is_dictionary_batch(msg: &arrow_ipc::Message) -> bool {
msg.header_as_dictionary_batch().is_some()
} Try / catch
std::panic::catch_unwind(|| deserialize_block(&dict, &fragment_data, schema.clone()))
.unwrap_or_else(|_| Err(ErrorCode::Internal("invalid dictionary batch header in exchange packet"))) Prevention
- Align serialization format versions across the cluster before rolling upgrades.
- Validate spill files for truncation before re-reading them.
- Add unit tests asserting DataPacket::Dictionary always wraps a DictionaryBatch header.
When it happens
Trigger: `deserialize_block` receiving a `DataPacket::Dictionary` whose IPC message header is actually a `RecordBatch` (sender/receiver packet-type mismatch), an IPC message with no header, or data produced by an Arrow writer emitting non-standard dictionary encoding.
Common situations: Version skew between cluster nodes after an Arrow or Databend upgrade changing how dictionaries are serialized; custom/forked serialization code writing the wrong header type into `Dictionary` packets; corrupted spill files whose bytes were shifted so the header decodes as the wrong type.
Understand the failure class
Background: "cannot parse invalid wire-format data", "cannot unmarshal", "failed unmarshalling": protobuf unmarshal errors explained — this error's family across 10 libraries.
Related errors
- Error parsing first message
- Error reading dictionary
- DataType::Map should contain a struct field child
- internal error: entered unreachable code
- QueryInfo is none
AI-assisted analysis of databendlabs/databend@288d84d76e (2026-09-11).
Data as JSON: /api/errors/30dde56959aa31e5.
Report an issue: GitHub.
Appendix: source
Thrown at src/query/service/src/servers/flight/v1/exchange/serde/exchange_deserializer.rs:113
}
pub fn deserialize_block(
dict: Vec<DataPacket>,
fragment_data: FragmentData,
schema: &DataSchema,
arrow_schema: Arc<ArrowSchema>,
) -> Result<DataBlock> {
let mut dictionaries_by_id = HashMap::new();
for dict_packet in dict {
if let DataPacket::Dictionary(data) = dict_packet {
let message =
root_as_message(&data.data_header[..]).expect("Error parsing first message");
let buffer = Buffer::from(data.data_body);
arrow_ipc::reader::read_dictionary(
&buffer,
message
.header_as_dictionary_batch()
.expect("Error parsing dictionary"),
&arrow_schema,
&mut dictionaries_by_id,
&message.version(),
)
.expect("Error reading dictionary");
}
}
let batch = flight_data_to_arrow_batch(&fragment_data.data, arrow_schema, &dictionaries_by_id)?;
let data_block = DataBlock::from_record_batch(schema, &batch)?;
Ok(data_block)
}
impl BlockMetaTransform<ExchangeDeserializeMeta> for TransformExchangeDeserializer {
const UNKNOWN_MODE: UnknownMode = UnknownMode::Pass;
const NAME: &'static str = "TransformExchangeDeserializer";
fn transform(&mut self, mut meta: ExchangeDeserializeMeta) -> Result<Vec<DataBlock>> {View on GitHub (pinned to 288d84d76e)