apache/druid · error · IllegalArgumentException

Key is not sortable

Error message

Key is not sortable

What it means

FrameChannelMerger requires that every KeyColumn in the supplied sortKey has a sortable KeyOrder (a defined ascending/descending order, not an unspecifiable one). It throws IAE if any column's order cannot be used for actual comparison-based sorting, since the merger physically orders rows.

Source

Thrown at processing/src/main/java/org/apache/druid/frame/processor/FrameChannelMerger.java:127

      @Nullable final ClusterByPartitions partitions,
      final long rowLimit
  )
  {
    if (inputChannels.isEmpty()) {
      throw new IAE("Must have at least one input channel");
    }

    final ClusterByPartitions partitionsToUse =
        partitions == null ? ClusterByPartitions.oneUniversalPartition() : partitions;

    if (!partitionsToUse.allAbutting()) {
      // To simplify merging logic, when frames we only look at the earliest and latest key in "partitions". To ensure
      // correctness, we need to verify that there are no gaps.
      throw new IAE("Partitions must all abut each other");
    }

    if (!sortKey.stream().allMatch(keyColumn -> keyColumn.order().sortable())) {
      throw new IAE("Key is not sortable");
    }

    this.inputChannels = inputChannels;
    this.outputChannel = outputChannel;
    this.frameReader = frameReader;
    this.frameWriterFactory = frameWriterFactory;
    this.sortKey = sortKey;
    this.partitions = partitionsToUse;
    this.rowLimit = rowLimit;
    this.currentFrames = new FramePlus[inputChannels.size()];
    this.remainingChannels = new IntAVLTreeSet(IntSets.fromTo(0, inputChannels.size()));
    this.tournamentTree = new TournamentTree(
        inputChannels.size(),
        (k1, k2) -> {
          final FramePlus frame1 = currentFrames[k1];
          final FramePlus frame2 = currentFrames[k2];

          if (frame1 == frame2) {

View on GitHub (pinned to 9b90983fd2)

Solutions

  1. Set an explicit KeyOrder.ASCENDING or KeyOrder.DESCENDING on every KeyColumn in the sort key.
  2. Check orders before constructing: sortKey.stream().allMatch(k -> k.order().sortable()), and fix any that fail.
  3. If the key was deserialized, ensure the order field is populated with a valid sortable value.

Example fix

// before
new KeyColumn("col", KeyOrder.NONE);
// after
new KeyColumn("col", KeyOrder.ASCENDING);
Defensive patterns

Strategy: validation

Validate before calling

for (KeyColumn kc : sortKey) {
  if (!kc.order().sortable()) {
    throw new IllegalArgumentException("Column '" + kc.columnName() + "' has a non-sortable order");
  }
}

Type guard

boolean sortableKey(List<KeyColumn> key) { return key.stream().allMatch(k -> k.order().sortable()); }

Try / catch

try {
  merger = new FrameChannelMerger(allocator, sortKey, channels, null, partitions, rowLimit);
} catch (IllegalArgumentException e) {
  // fix sort key orders to ASCENDING/DESCENDING
}

Prevention

When it happens

Trigger: Constructing a FrameChannelMerger with a sortKey containing a KeyColumn whose KeyOrder is not sortable (e.g. KeyOrder.NONE or an order flag where 'unsorted' is meaningful), often when reusing clustering keys that were only meant for partitioning.

Common situations: Copying a ClusterBy sort key into a merge stage after a code change introduced a non-sortable order; deserializing keys from config/query JSON where the order field is unset or wrong.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


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