databendlabs/databend · error

[TRANSFORM-AGGREGATOR] Hash table already moved out in…

Error message

[TRANSFORM-AGGREGATOR] Hash table already moved out in finish

What it means

TransformPartialAggregate::on_finish takes the hash table via std::mem::take; if it finds HashTable::MovedOut on a path where output is expected (!output is false branch), it hits unreachable!, meaning the table was already moved out before finish. It guards that finish is reached exactly once with the table still present.

Solutions

  1. Upgrade to a version with the lifecycle fix and retry
  2. Audit any custom pipeline for duplicate on_finish calls
  3. File a bug with stack trace and query profile if reproducible on one version
Defensive patterns

Strategy: try-catch

Type guard

fn finishable(ht: &HashTable, output: bool) -> bool {
    !output || matches!(ht, HashTable::AggregateHashTable(_))
}

Try / catch

match res {
    Err(e) if e.message().contains("Hash table already moved out in finish") => {
        // query abort; retry on fixed version; file with stack trace
    }
    ...
}

Prevention

When it happens

Trigger: on_finish runs twice, or the table was moved out earlier (spill_out/execute paths) while output was still required — caused by pipeline driver bugs, double process/finish, or retry logic re-invoking the transform.

Common situations: Executor regressions in AccumulatingTransform lifecycle; query retries replaying finish; custom pipelines mis-wiring event ordering.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of databendlabs/databend@288d84d76e (2026-09-11). Data as JSON: /api/errors/cbb5f53fad6a8d25. Report an issue: GitHub.

Appendix: source

Thrown at src/query/service/src/pipelines/processors/transforms/aggregator/transform_aggregate_partial.rs:247

impl AccumulatingTransform for TransformPartialAggregate {
    const NAME: &'static str = "TransformPartialAggregate";

    fn transform(&mut self, block: DataBlock) -> Result<Vec<DataBlock>> {
        self.execute_one_block(block)?;

        if self.settings.check_spill() {
            self.spill_out()?;
        }

        Ok(vec![])
    }

    fn on_finish(&mut self, output: bool) -> Result<Vec<DataBlock>> {
        Ok(match std::mem::take(&mut self.hash_table) {
            HashTable::MovedOut => match !output {
                true => vec![],
                false => {
                    unreachable!("[TRANSFORM-AGGREGATOR] Hash table already moved out in finish")
                }
            },
            HashTable::AggregateHashTable(hashtable) => {
                let mut blocks = self.spillers.finish()?;

                self.statistics.log_finish_statistics(&hashtable);

                let payloads = hashtable
                    .payload
                    .into_bucket_payloads()
                    .map(|(bucket, payload)| AggregatePayload {
                        bucket: bucket as isize,
                        payload,
                        max_partition_count: 0,
                    })
                    .collect::<Vec<_>>();
                blocks.push(DataBlock::empty_with_meta(
                    AggregateMeta::create_partitioned(

View on GitHub (pinned to 288d84d76e)