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

  1. Ensure all nodes run matching versions of Databend/Arrow so dictionary packet framing agrees on both ends.
  2. Inspect the sender side: confirm `DataPacket::Dictionary` packets are only created from genuine dictionary-batch IPC messages.
  3. If data came from a spill file, verify the file was not partially written or truncated, then re-run the query.
  4. 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

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


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)