{"record":{"id":"71c790b11c338d6a","repo":"apache/druid","slug":"clusterby-key-must-be-sortable","errorCode":null,"errorMessage":"ClusterBy key must be sortable","messagePattern":"ClusterBy key must be sortable","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"multi-stage-query/src/main/java/org/apache/druid/msq/kernel/GlobalSortMaxCountShuffleSpec.java","lineNumber":73,"sourceCode":"      @JsonProperty(\"adjustable\") final boolean adjustable\n  )\n  {\n    this.clusterBy = Preconditions.checkNotNull(clusterBy, \"clusterBy\");\n    this.maxPartitions = maxPartitions;\n    this.aggregate = aggregate;\n    this.limitHint = limitHint == null ? UNLIMITED : limitHint;\n    this.adjustable = adjustable;\n\n    if (maxPartitions < 1) {\n      throw new IAE(\"Partition count must be at least 1\");\n    }\n\n    if (adjustable && maxPartitions != 1) {\n      throw new IAE(\"Partition count must be 1 when adjustable is true, but was [%d]\", maxPartitions);\n    }\n\n    if (!clusterBy.sortable()) {\n      throw new IAE(\"ClusterBy key must be sortable\");\n    }\n\n    if (clusterBy.getBucketByCount() > 0) {\n      // Only GlobalSortTargetSizeShuffleSpec supports bucket-by.\n      throw new IAE(\"Cannot bucket with %s partitioning\", TYPE);\n    }\n  }\n\n  public GlobalSortMaxCountShuffleSpec(\n      final ClusterBy clusterBy,\n      final int maxPartitions,\n      final boolean aggregate,\n      final Long limitHint\n  )\n  {\n    this(clusterBy, maxPartitions, aggregate, limitHint, false);\n  }\n","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/multi-stage-query/src/main/java/org/apache/druid/msq/kernel/GlobalSortMaxCountShuffleSpec.java#L55-L91","documentation":"This shuffle spec orders output globally by the clusterBy key, so the key must support total ordering (sortable()). If the key columns' types cannot be sorted (e.g. complex/nested types without comparators), the constructor throws IAE.","triggerScenarios":"Constructing the spec with a ClusterBy whose key contains columns of non-sortable types - typically COMPLEX or array/nested types without an ordering, coming from a query whose ORDER BY or grouping key includes such columns.","commonSituations":"ORDER BY on complex columns (e.g. nested JSON) in an MSQ insert/replace; sorting on array-typed expressions when the sort spec doesn't support them; schema changes making previously sortable columns complex.","solutions":["Remove non-sortable columns from the ORDER BY / cluster key in the SQL query.","Cast or extract a primitive (string/number) from complex values before sorting, e.g. ORDER BY JSON_VALUE(col, '$.field').","Choose a shuffle spec type that supports the key if global sorting is not required."],"exampleFix":"// before\nSELECT ... ORDER BY complexCol\n// after\nSELECT ... ORDER BY JSON_VALUE(complexCol, '$.field')","handlingStrategy":"validation","validationCode":"if (!clusterBy.sortable()) {\n  throw new IllegalArgumentException(\"ClusterBy key must be sortable before using maxCountSorting\");\n}","typeGuard":null,"tryCatchPattern":"try {\n  spec = new GlobalSortMaxCountShuffleSpec(clusterBy, ...);\n} catch (IllegalArgumentException e) {\n  if (e.getMessage() != null && e.getMessage().equals(\"ClusterBy key must be sortable\")) {\n    // fall back to a non-globally-sorted shuffle spec or rewrite the key\n  } else {\n    throw e;\n  }\n}","preventionTips":["Restrict ORDER BY keys to primitive sortable types in MSQ queries.","Use JSON_VALUE / casting to extract sortable scalars from complex columns.","Check column types in the ingestion schema before planning global sorts."],"tags":["msq","shuffle-spec","sort-order","validation"],"backgroundTag":"invalid-argument-value","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}