Skip to content

Large numeric IN predicates cause slow SQL planning on Druid 27 #20326

Description

@FrankChen021

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions