apache/druid · error · IllegalArgumentException
Got a join, with a cartesian product that exceeds 1,000,000…
Error message
Got a join, with a cartesian product that exceeds 1,000,000 rows, cannot handle it
What it means
During a sorted inner join, when join key values are equal across all inputs but the combined cross-product of matching rows would exceed 1,000,000 rows, Druid refuses to materialize the join. The library enforces this hard cap because a huge cartesian product would exhaust memory and stall the query. The message deliberately omits the offending values to avoid leaking data.
Solutions
- Fix the join condition so keys are high-cardinality and actually correlate the two inputs
- Filter or pre-aggregate the inputs to reduce rows sharing a single key value
- Reformulate as a GROUP BY or subquery to avoid the cross product
- Increase limits by splitting the query into smaller partitions if the join is legitimately needed
Example fix
// before: join on constant/low-cardinality column JOIN dim ON (fact.partition = dim.partition) // both have millions of rows per value // after: join on a selective key JOIN dim ON (fact.user_id = dim.user_id)
Defensive patterns
Strategy: validation
Validate before calling
// before issuing the join, check key cardinality per value
long maxRowsPerKey = computeMaxRowsPerJoinKey(factRows, joinKey); // via GROUP BY key count()
if (maxRowsPerKey * matchingDimRows > 1_000_000L) {
throw new IllegalArgumentException("join key too low-cardinality; refactor query");
} Type guard
boolean isSafeJoin(DataSource left, DataSource right, JoinCondition c) {
return !c.getCondition().isAlwaysTrue() && estimateJoinCardinality(left, right, c) <= 1_000_000L;
} Try / catch
try {
runJoin(query);
} catch (IllegalArgumentException e) {
if (e.getMessage().contains("cartesian product that exceeds")) {
// fall back to pre-aggregated or filtered join
runJoin(refactorQuery(query));
} else throw e;
} Prevention
- Join on high-cardinality keys, never on booleans/status/constant columns
- Run a GROUP BY key COUNT(*) to detect skewed keys before joining
- Never let a join condition degenerate to a tautology (e.g. 1=1)
When it happens
Trigger: joinRows (also reached via process/alternativeRow) encounters rows on all inputs whose join keys match, and the product of the number of matching rows per join part (numRowsExpected) exceeds 1,000,000.
Common situations: Joining on a low-cardinality key (e.g. boolean, status flag, or constant column) so one key value matches millions of rows; accidental cartesian join caused by a wrong or constant join condition; skewed data where one tenant/ID dominates.
Understand the failure class
Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.
Related errors
- FrameTooLarge
- AggregatorFactoryNotMergeableException
- Already closed
- BroadcastTablesTooLarge
- buffer for list is too small, was
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/1f12c2d5b0479f83.
Report an issue: GitHub.
Appendix: source
Thrown at processing/src/main/java/org/apache/druid/query/operator/join/SortedInnerJoinOperator.java:357
if (joinPartIndex == 0) {
// We have walked through all of the joinParts and have matches, so time to add to rowsToInclude
// We have ranges in each of the parts, we will do the cartesian product, so we will produce the product
// of the length of those ranges number of rows. Let's compute it to see how many rows we will produce.
int numRowsExpected = joinPart.scanToRowIndex - joinPart.currRowIndex;
for (int i = 1; i < joinParts.size(); ++i) {
final JoinPart subPart = joinParts.get(i);
numRowsExpected *= subPart.scanToRowIndex - subPart.currRowIndex;
}
if (numRowsExpected == 1) {
for (int i = 0; i < joinParts.size(); ++i) {
rowsToInclude[i].add(joinParts.get(i).currRowIndex);
}
} else {
if (numRowsExpected > 1_000_000) {
// It would be helpful to serialize the actual value out with this error, but that risks leaking data
throw new IAE("Got a join, with a cartesian product that exceeds 1,000,000 rows, cannot handle it");
}
// The rowIds that will be used in the result of the join will be a cartesian product, which means that
// the "deepest" row ids will be repeated in-order over and over, then the next layer will have each
// value repeated runSize times, forming a new run of length * runSize, and so on and so forth
int partIndex = joinParts.size() - 1;
int runSize = 1;
// We are guaranteed that there is a runSize greater than 1 because otherwise numRowsExpected would be 1
while (partIndex >= 0) {
final JoinPart part = joinParts.get(partIndex);
final int size = part.scanToRowIndex - part.currRowIndex;
if (size == 1) {
rowsToInclude[partIndex].fill(part.currRowIndex, numRowsExpected);
} else {
int[] vals = new int[size];
for (int i = 0; i < vals.length; ++i) {
vals[i] = i + part.currRowIndex;
}View on GitHub (pinned to 9b90983fd2)