apache/druid · error · AggregatorFactoryNotMergeableException

AggregatorFactoryNotMergeableException

Error message

AggregatorFactoryNotMergeableException

What it means

SimpleFloatAggregatorFactory.getMergingFactory throws AggregatorFactoryNotMergeableException when the other factory's name or class does not match this one. Druid merges partial results across segments only when both factories are the same class (float-based aggregators) and share the same output name. A mismatch means the query is asking to combine a float aggregation with something that cannot be combined into it.

Source

Thrown at processing/src/main/java/org/apache/druid/query/aggregation/SimpleFloatAggregatorFactory.java:183

  {
    return object;
  }

  @Override
  public List<String> requiredFields()
  {
    return fieldName != null
           ? Collections.singletonList(fieldName)
           : fieldExpression.get().analyzeInputs().getRequiredBindingsList();
  }

  @Override
  public AggregatorFactory getMergingFactory(AggregatorFactory other) throws AggregatorFactoryNotMergeableException
  {
    if (other.getName().equals(this.getName()) && this.getClass() == other.getClass()) {
      return getCombiningFactory();
    } else {
      throw new AggregatorFactoryNotMergeableException(this, other);
    }
  }

  @Override
  public int hashCode()
  {
    return Objects.hash(fieldName, expression, name);
  }

  @Override
  public boolean equals(Object o)
  {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }

View on GitHub (pinned to 9b90983fd2)

Solutions

  1. Align the output names of the float aggregators on both sides of the merge
  2. Use the same aggregator class (floatSum/floatMin/floatMax etc.) on both sides
  3. Adjust the query or ingestion spec so the same output name always refers to the same float-based aggregation
  4. If schema drifted, re-run the query with explicit numeric casts or re-ingest data to a consistent type

Example fix

// before
 { type: "floatSum", name: "s" }  vs  { type: "doubleSum", name: "s" }
// after
 { type: "floatSum", name: "s" }  on both shards / subquery and outer query
Defensive patterns

Strategy: validation

Validate before calling

boolean mergeable(AggregatorFactory a, AggregatorFactory b) {
  return a.getName().equals(b.getName()) && a.getClass() == b.getClass();
}

Type guard

boolean isMergeableFloatPair(AggregatorFactory other, SimpleFloatAggregatorFactory self) {
  return other instanceof SimpleFloatAggregatorFactory && other.getName().equals(self.getName());
}

Try / catch

try {
  AggregatorFactory merged = factory.getMergingFactory(other);
} catch (AggregatorFactoryNotMergeableException e) {
  LOGGER.warn(e, "Non-mergeable float aggregators: %s vs %s", e.getFactory1(), e.getFactory2());
}

Prevention

When it happens

Trigger: Calling getMergingFactory(other) with an other whose getName() differs from this factory's name, or whose class is not SimpleFloatAggregatorFactory (e.g. a double or long aggregator), typically during distributed query merging across multiple segments.

Common situations: Mixing float and double aggregators under one output name in a multi-segment query; schema changes between ingestion rounds that changed an aggregator's numeric type; subquery + outer query where the same name maps to different aggregator types.

Related errors


AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07). Data as JSON: /api/errors/12fa65dff5fea9c7. Report an issue: GitHub.