apache/druid · error · UnsupportedOperationException

Cannot operate on a dimension with unknown cardinality

Error message

Cannot operate on a dimension with unknown cardinality

What it means

In PooledTopNAlgorithm.scanAndAggregateDefault, params.getCardinality() must be non-negative because the default scan path indexes directly into a positions array sized by dimension cardinality. A negative cardinality (dimension with unknown/no dictionary) makes indexed aggregation impossible, so UnsupportedOperationException is thrown. This guards the aggregation loop against out-of-range indexing into per-value slots.

Source

Thrown at processing/src/main/java/org/apache/druid/query/topn/PooledTopNAlgorithm.java:498

   * See http://en.wikipedia.org/wiki/Duff's_device for more information on this kind of approach
   *
   * This allows out of order execution of the code. In local tests, the JVM inlines all the way to this function.
   *
   * If there are more than AGG_UNROLL_COUNT aggregates, then the remainder is calculated with the switch, and the
   * blocks of AGG_UNROLL_COUNT are calculated in a partially unrolled for-loop.
   *
   * Putting the switch first allows for optimization for the common case (less than AGG_UNROLL_COUNT aggs) but
   * still optimizes the high quantity of aggregate queries which benefit greatly from any speed improvements
   * (they simply take longer to start with).
   */
  private static long scanAndAggregateDefault(
      final PooledTopNParams params,
      final int[] positions,
      final BufferAggregator[] theAggregators
  )
  {
    if (params.getCardinality() < 0) {
      throw new UnsupportedOperationException("Cannot operate on a dimension with unknown cardinality");
    }

    final ByteBuffer resultsBuf = params.getResultsBuf();
    final int numBytesPerRecord = params.getNumBytesPerRecord();
    final int[] aggregatorSizes = params.getAggregatorSizes();
    final Cursor cursor = params.getCursor();
    final CursorGranularizer granularizer = params.getGranularizer();
    final DimensionSelector dimSelector = params.getDimSelector();

    final int[] aggregatorOffsets = new int[aggregatorSizes.length];
    for (int j = 0, offset = 0; j < aggregatorSizes.length; ++j) {
      aggregatorOffsets[j] = offset;
      offset += aggregatorSizes[j];
    }

    final int aggSize = theAggregators.length;
    final int aggExtra = aggSize % AGG_UNROLL_COUNT;
    int currentPosition = 0;

View on GitHub (pinned to 9b90983fd2)

Solutions

  1. Use a dictionary-encoded string dimension for topN
  2. Convert the query to groupBy, which handles unknown cardinality
  3. Verify the segment is not being swapped/unmapped mid-query; retry the query
  4. Check ingestion config so the dimension column is dictionary-encoded

Example fix

// before
topN over non-dictionary dimension -> UnsupportedOperationException
// after
GroupByQuery.newBuilder().setDimension(new DefaultDimensionSpec("country", "country"))...
Defensive patterns

Strategy: validation

Validate before calling

if (params.getCardinality() < 0) { /* use groupBy fallback before scanAndAggregateDefault */ }

Type guard

boolean supportsPooledTopN(PooledTopNParams params) {
  return params.getCardinality() >= 0;
}

Try / catch

try {
  result = algorithm.run(params);
} catch (UnsupportedOperationException e) {
  if (e.getMessage().contains("unknown cardinality")) { /* rerun as groupBy */ }
  throw e;
}

Prevention

When it happens

Trigger: TopN query falling back to scanAndAggregateDefault while the PooledTopNParams carries cardinality < 0 — a dimension selector with unknown cardinality, or params built without a valid cardinality (e.g. after init skipped validation or cardinality changed between init and scan).

Common situations: Querying segments where the chosen dimension lacks dictionary encoding; race between segment replacement/unmapping and query execution leaving stale params; using topN against data sources that don't dictionary-encode the dimension.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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