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
- Set an explicit KeyOrder.ASCENDING or KeyOrder.DESCENDING on every KeyColumn in the sort key.
- Check orders before constructing: sortKey.stream().allMatch(k -> k.order().sortable()), and fix any that fail.
- 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
- Always specify KeyOrder.ASCENDING or DESCENDING explicitly.
- Never reuse partition-only keys as sort keys without setting orders.
- Validate deserialized keys before use.
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
- Cannot have null or empty column name
- Invalid stageNumber [%s]
- Only positive integers or zero can be added
- Must have at least one input channel
- Partitions must all abut each other
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/3700a99ff33e2930.
Report an issue: GitHub.