apache/druid · error · IllegalArgumentException
Cannot mix sortable and unsortable key columns
Error message
Cannot mix sortable and unsortable key columns
What it means
ClusterBy requires all key columns to be uniformly sortable or uniformly non-sortable, because mixing them prevents a consistent key ordering for partitioning/shuffling. If the first column's order is sortable and any later column's is not (or vice versa), construction fails.
Source
Thrown at processing/src/main/java/org/apache/druid/frame/key/ClusterBy.java:73
)
{
this.columns = Preconditions.checkNotNull(columns, "columns");
this.bucketByCount = bucketByCount;
if (bucketByCount < 0 || bucketByCount > columns.size()) {
throw new IAE("Invalid bucketByCount [%d]", bucketByCount);
}
// Key must be 100% sortable or 100% nonsortable. If empty, call it sortable.
boolean sortable = true;
for (int i = 0; i < columns.size(); i++) {
final KeyColumn column = columns.get(i);
if (i == 0) {
sortable = column.order().sortable();
} else if (sortable != column.order().sortable()) {
throw new IAE("Cannot mix sortable and unsortable key columns");
}
}
this.sortable = sortable;
}
/**
* Create an empty key.
*/
public static ClusterBy none()
{
return new ClusterBy(Collections.emptyList(), 0);
}
/**
* The columns that comprise this key, in order.
*/
@JsonPropertyView on GitHub (pinned to 9b90983fd2)
Solutions
- Change the clustering expression so every column uses a sortable ordering (avoid complex/sketch-type columns in CLUSTERED BY).
- Split the clustering key so non-sortable columns are not included, or wrap them with a sortable expression (e.g. cast or stringify).
- Inspect each KeyColumn's order().sortable() before constructing ClusterBy programmatically.
- If this comes from a generated plan, check the SQL layer's column ordering resolution for the query.
Example fix
// before // CLUSTERED BY region, APPROX_COUNT_DISTINCT_DS_HLL(user) -> mixed sortability // after // CLUSTERED BY region, user (use only sortable columns in the clustering key)
Defensive patterns
Strategy: validation
Validate before calling
boolean first = columns.get(0).order().sortable();
for (KeyColumn c : columns) {
if (c.order().sortable() != first) {
throw new IllegalArgumentException("Mixed sortable/unsortable clustering columns");
}
} Type guard
static boolean uniformlySortable(List<KeyColumn> columns) {
if (columns.isEmpty()) return true;
boolean s = columns.get(0).order().sortable();
return columns.stream().allMatch(c -> c.order().sortable() == s);
} Try / catch
try {
new ClusterBy(columns, bucketByCount);
} catch (IllegalArgumentException e) {
if (e.getMessage().contains("mix sortable")) {
columns = dropUnsortableColumns(columns);
new ClusterBy(columns, Math.min(bucketByCount, columns.size()));
} else {
throw e;
}
} Prevention
- Only include sortable columns (no complex/sketch types) in CLUSTERED BY.
- Check KeyColumn.order().sortable() programmatically before building ClusterBy.
- Sanitize user SQL clustering expressions to reject non-sortable column types.
- Document which column types are non-sortable for spec authors.
When it happens
Trigger: Building a ClusterBy from a column list where KeyColumn.order().sortable() differs between columns — e.g. clustering on a mix of standard comparable columns and non-sortable column types (like complex/sketch columns).
Common situations: MSQ CLUSTERED BY including a non-sortable expression or complex column alongside regular columns; user typos in SQL clustering expressions; new column types whose order is non-sortable being added to an existing clustering spec.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Invalid bucketByCount [%d]
- Must provide at least one range
- DynamicPartitionsSpec must be used for best-effort rollup
- maxRetainedPartitionSketchBytes must be positive
- maxConcurrentStagesPerWorker must be >= 2 when pipelining
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/7b1a4a52585fca2c.
Report an issue: GitHub.