apache/druid · error · BadQueryContextException

Configured maxScatterGatherBytes =

Error message

Configured maxScatterGatherBytes = %d is more than enforced limit of %d.

What it means

Druid enforces an upper bound on `maxScatterGatherBytes` — the maximum number of bytes a query may buffer while gathering results from data nodes. QueryContext.verifyMaxScatterGatherBytes throws BadQueryContextException when the query's context value exceeds the enforced server limit. This protects brokers from unbounded memory use during fan-out query execution.

Solutions

  1. Reduce the query's `maxScatterGatherBytes` to a value at or below the enforced limit shown in the message.
  2. If the workload legitimately needs more memory, raise the server-side enforced limit (broker's maxScatterGatherBytes config) and restart/roll out.
  3. Restructure the query to return fewer bytes (filters, limits, approximate sketches like DataSketches theta/HLL) instead of raising limits.
  4. Catch BadQueryContextException and surface the enforced limit to the user so they can shrink the query or contact the operator.

Example fix

// before
context.put("maxScatterGatherBytes", 10L * 1024 * 1024 * 1024); // exceeds 1 GiB limit
// after
long limit = 1024L * 1024 * 1024; // enforced by broker
context.put("maxScatterGatherBytes", Math.min(10L * 1024 * 1024 * 1024, limit));
Defensive patterns

Strategy: validation

Validate before calling

Object msg = query.getContext().get("maxScatterGatherBytes");
long enforced = /* broker config */ 1024L * 1024 * 1024;
if (msg instanceof Number && ((Number) msg).longValue() > enforced) {
    query.getContext().put("maxScatterGatherBytes", enforced);
}

Type guard

boolean withinScatterGatherLimit(Object v, long limit) {
    return !(v instanceof Number) || ((Number) v).longValue() <= limit;
}

Try / catch

try {
    client.query(query);
} catch (BadQueryContextException e) {
    if (e.getMessage().contains("maxScatterGatherBytes")) {
        long limit = parseEnforcedLimit(e.getMessage());
        query.getContext().put("maxScatterGatherBytes", limit);
        client.query(query);
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: Calling QueryContext.verifyMaxScatterGatherBytes(long maxScatterGatherBytesLimit) (invoked via withMaxScatterGatherBytes) when the context's MAX_SCATTER_GATHER_BYTES_KEY ("maxScatterGatherBytes") value exceeds the enforced limit, e.g. query asks for 10 GiB while the broker enforces 1 GiB.

Common situations: Clients inflating maxScatterGatherBytes to run very large group-bys/top-N queries on a broker whose druid.server.http.maxScatterGatherBytes or maxQueryTimeout-style cap was configured lower; operators tightening limits after capacity incidents; large-result BI tools with hard-coded generous limits.

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


AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07). Data as JSON: /api/errors/1c70768231d1f232. Report an issue: GitHub.

Appendix: source

Thrown at processing/src/main/java/org/apache/druid/query/QueryContext.java:586

    throw new BadQueryContextException(
        StringUtils.format(
            "Per-segment timeout [%s] must be a non negative value, but was [%d]",
            QueryContexts.PER_SEGMENT_TIMEOUT_KEY,
            timeout
        )
    );
  }

  public boolean usePerSegmentTimeout()
  {
    return getPerSegmentTimeout() != QueryContexts.NO_TIMEOUT;
  }

  public void verifyMaxScatterGatherBytes(long maxScatterGatherBytesLimit)
  {
    long curr = getLong(QueryContexts.MAX_SCATTER_GATHER_BYTES_KEY, 0);
    if (curr > maxScatterGatherBytesLimit) {
      throw new BadQueryContextException(
          StringUtils.format(
            "Configured %s = %d is more than enforced limit of %d.",
            QueryContexts.MAX_SCATTER_GATHER_BYTES_KEY,
            curr,
            maxScatterGatherBytesLimit
          )
      );
    }
  }

  public int getNumRetriesOnMissingSegments(int defaultValue)
  {
    return getInt(QueryContexts.NUM_RETRIES_ON_MISSING_SEGMENTS_KEY, defaultValue);
  }

  public boolean allowReturnPartialResults(boolean defaultValue)
  {
    return getBoolean(QueryContexts.RETURN_PARTIAL_RESULTS_KEY, defaultValue);

View on GitHub (pinned to 9b90983fd2)