prestodb/presto · error · PinotException

PINOT_UNEXPECTED_RESPONSE

PINOT_UNEXPECTED_RESPONSE

Error message

Expected row of %d columns

What it means

PinotBrokerPageSource.setRows throws PinotException(PINOT_UNEXPECTED_RESPONSE) when a row in the Pinot broker's JSON response (the 'rows' array of a broker query result) is missing or has fewer fields than the query's column count (blockBuilders.size()). The connector expects the response's rows to align 1:1 with the requested columns; a short or null row means the broker returned a malformed or structurally unexpected response. The query text is attached via the exception's Optional.of(query) for debugging.

Source

Thrown at presto-pinot-toolkit/src/main/java/com/facebook/presto/pinot/PinotBrokerPageSource.java:265

            pageBuilder.declarePositions(counter);
            Page page = pageBuilder.build();

            // TODO: Implement chunking if the result set is ginormous
            finished = true;

            return page;
        }
        finally {
            readTimeNanos += System.nanoTime() - start;
        }
    }

    protected void setRows(String query, List<BlockBuilder> blockBuilders, List<Type> types, JsonNode rows)
    {
        for (int rowNumber = 0; rowNumber < rows.size(); ++rowNumber) {
            JsonNode result = rows.get(rowNumber);
            if (result == null || result.size() < blockBuilders.size()) {
                throw new PinotException(
                    PINOT_UNEXPECTED_RESPONSE,
                    Optional.of(query),
                    String.format("Expected row of %d columns", blockBuilders.size()));
            }
            for (int columnNumber = 0; columnNumber < blockBuilders.size(); columnNumber++) {
                setValue(types.get(columnNumber), blockBuilders.get(columnNumber), result.get(columnNumber));
            }
        }
    }

    protected static void handleCommonResponse(String pinotQuery, JsonNode jsonBody)
    {
        JsonNode numServersResponded = jsonBody.get("numServersResponded");
        JsonNode numServersQueried = jsonBody.get("numServersQueried");

        if (numServersQueried == null || numServersResponded == null || numServersQueried.asInt() > numServersResponded.asInt()) {
            throw new PinotException(
                PINOT_INSUFFICIENT_SERVER_RESPONSE,

View on GitHub (pinned to 55bb57d202)

Solutions

  1. Log/inspect the actual Pinot broker response (the failing query is in the PinotException's error message) to see the returned row shape
  2. Run the same query directly against the Pinot broker (POST /query/sql) to confirm whether the broker itself returns short rows
  3. Check Pinot broker health/version and network stability — retry the query; transient broker errors often produce malformed responses
  4. Upgrade the Pinot connector and Pinot cluster together so the JSON result format expectations match
  5. Reduce query complexity/column count or add LIMIT to rule out response-size related truncation

Example fix

// before (fragile direct query)
SELECT * FROM pinot_table WHERE ts > 0
// after (explicit columns, bounded result)
SELECT id, CAST(value AS DOUBLE) FROM pinot_table WHERE ts > 0 LIMIT 1000
Defensive patterns

Strategy: retry

Validate before calling

// Validate broker response shape before iterating rows
JsonNode rows = response.get("Rows");
if (rows == null || !rows.isArray()) {
  throw new IllegalStateException("Pinot broker response missing Rows array");
}
for (JsonNode row : rows) {
  if (row == null || row.size() < expectedColumnCount) {
    throw new IllegalStateException("Pinot returned short row: " + row);
  }
}

Type guard

boolean isValidRows(JsonNode rows, int expectedColumns) {
  return rows != null && rows.isArray()
      && java.util.stream.StreamSupport.stream(rows.spliterator(), false)
          .allMatch(r -> r != null && r.size() >= expectedColumns);
}

Try / catch

try {
  return pageSource.getNextPage();
} catch (PinotException e) {
  if (e.getErrorCode() == PINOT_UNEXPECTED_RESPONSE.toErrorCode()) {
    if (attempt < MAX_RETRIES) { return retryQuery(); } // transient broker truncation
    throw new IllegalStateException("Pinot broker returned malformed response for query: " + e.getMessage());
  }
  throw e;
}

Prevention

When it happens

Trigger: populateFromQueryResults iterates resultJson.get('Rows') and a row is null or result.size() < number of requested columns: Pinot returned an error/empty payload shaped differently than expected, a broker-side query failure produced a partial response, or the number of selected columns/aggregates changed while the response retained an older shape.

Common situations: Pinot broker under stress returning truncated responses; broker version incompatibility with the connector's expected JSON result format (e.g. pagination or aggregation result format change); queries mixing selection and aggregation where the connector builds column expectations differently from the server; large responses cut off by broker timeouts.

Related errors


AI-assisted analysis of prestodb/presto@55bb57d202 (2026-09-04). Data as JSON: /api/errors/726d1d91a39b62fa. Report an issue: GitHub.