Affected version
Druid 27 with Calcite 1.21.0.
Problem
A numeric IN predicate containing 11,481 literals causes a severe SQL planning slowdown:
maxLongUniform IN (11481 literal values)
A deterministic, planning-only JMH benchmark measured approximately 7,160 ms per plan on Druid 27. The delay occurs during SQL planning, before native query execution.
Root cause
For a literal list below inSubQueryThreshold, Calcite converts SQL IN into one equality per literal joined by OR:
SQL IN
-> 11,481 equality OR terms
-> RexSimplify.simplifyOrTerms
-> native IN filter optimization
Druid 27's legacy null-replacement mode exposes numeric columns as non-nullable to Calcite. This makes simplifyOrTerms accumulate prior equality terms as predicates while simplifying later terms, which scales poorly for thousands of values. Druid eventually combines the filters into a native InDimFilter, but only after Calcite has paid the simplification cost.
The SQL-to-OR rewrite is intentional. The defect is the scaling of cross-term predicate simplification for a very large equality disjunction. This is related to #7904 and CALCITE-3178.
Version comparison
| Druid version |
Calcite |
Planning time |
| Druid 27 |
1.21.0 |
approximately 7,160 ms |
| Druid 32.0.0 |
1.37.0 |
25.322 +/- 5.677 ms |
| Current master |
1.42.0 |
27.814 +/- 6.485 ms |
The exact Druid 32.0.0 result was verified using the official Calcite 1.37.0 artifact and the same maxLongUniform IN (11,481 literals) query shape with inSubQueryThreshold = Integer.MAX_VALUE.
Why Druid 32 and later are unaffected
Druid 32 removed legacy SQL-incompatible null handling in #17609. Ordinary numeric datasource columns are now exposed to Calcite as nullable. Calcite therefore skips the expensive predicate accumulation and can later combine the equality terms into SEARCH/Sarg.
The underlying Calcite weakness can still affect genuinely non-nullable schemas or plans where non-nullability is already established, but the original query against an ordinary Druid datasource does not reproduce the slowdown from Druid 32 onward.
Suggested actions
- On Druid 27, set
inSubQueryThreshold below the literal count so Calcite uses an inline VALUES relation instead of generating the large OR expression; validate the resulting execution plan.
- Upgrade to Druid 32 or later for the long-term resolution of this ordinary-datasource case.
- Retain a large-
IN planning benchmark as a regression guard.
Related Calcite allocation reductions
This is separate from the original Druid 27 numeric planning regression: these upstream changes reduce temporary allocation for large string IN predicates on Calcite 1.42.0.
| Calcite PR |
Optimization |
Allocation reduction (100,000 / 1,000,000 strings) |
| #5280 |
Avoid repeated generated-CAST resolution |
19.95% / 20.48% |
| #5281 |
Reuse unchanged character SQL types |
9.05% / 8.72% |
| #5282 |
Cache recently created and decorated SQL types |
45.74% / 44.70% |
| #5283 |
Copy SQL and Rex operands only when changed |
2.72% / 1.55% |
| #5284 |
Write Rex string literal digests directly |
2.46% / 1.31% |
The isolated reductions overlap and should not be added. With all five changes applied, allocation fell by 70.8% at 100,000 strings and 70.5% at 1,000,000 strings.
PR #20314 adds a separate planning-timeout guardrail that limits the Broker impact of pathological planning cases; it does not change the root cause described here.
Affected version
Druid 27 with Calcite 1.21.0.
Problem
A numeric
INpredicate containing 11,481 literals causes a severe SQL planning slowdown:A deterministic, planning-only JMH benchmark measured approximately 7,160 ms per plan on Druid 27. The delay occurs during SQL planning, before native query execution.
Root cause
For a literal list below
inSubQueryThreshold, Calcite converts SQLINinto one equality per literal joined byOR:Druid 27's legacy null-replacement mode exposes numeric columns as non-nullable to Calcite. This makes
simplifyOrTermsaccumulate prior equality terms as predicates while simplifying later terms, which scales poorly for thousands of values. Druid eventually combines the filters into a nativeInDimFilter, but only after Calcite has paid the simplification cost.The SQL-to-OR rewrite is intentional. The defect is the scaling of cross-term predicate simplification for a very large equality disjunction. This is related to #7904 and CALCITE-3178.
Version comparison
The exact Druid 32.0.0 result was verified using the official Calcite 1.37.0 artifact and the same
maxLongUniform IN (11,481 literals)query shape withinSubQueryThreshold = Integer.MAX_VALUE.Why Druid 32 and later are unaffected
Druid 32 removed legacy SQL-incompatible null handling in #17609. Ordinary numeric datasource columns are now exposed to Calcite as nullable. Calcite therefore skips the expensive predicate accumulation and can later combine the equality terms into
SEARCH/Sarg.The underlying Calcite weakness can still affect genuinely non-nullable schemas or plans where non-nullability is already established, but the original query against an ordinary Druid datasource does not reproduce the slowdown from Druid 32 onward.
Suggested actions
inSubQueryThresholdbelow the literal count so Calcite uses an inlineVALUESrelation instead of generating the large OR expression; validate the resulting execution plan.INplanning benchmark as a regression guard.Related Calcite allocation reductions
This is separate from the original Druid 27 numeric planning regression: these upstream changes reduce temporary allocation for large string
INpredicates on Calcite 1.42.0.The isolated reductions overlap and should not be added. With all five changes applied, allocation fell by 70.8% at 100,000 strings and 70.5% at 1,000,000 strings.
PR #20314 adds a separate planning-timeout guardrail that limits the Broker impact of pathological planning cases; it does not change the root cause described here.