apache/druid · error · FrameRowTooLargeException

FrameTooLarge

FrameTooLarge

Error message

FrameRowTooLargeException: row too large for frame allocator capacity %s

What it means

In the sort-merge join frame processor, emitRowIfNeeded appends the joined row to the current frame. If the frame already has rows, it is flushed and processing resumes; but if the frame is empty and addSelection() still fails, the single joined row exceeds the frame allocator capacity and FrameRowTooLargeException is thrown. The query cannot buffer even one output row within the frame memory budget.

Source

Thrown at multi-stage-query/src/main/java/org/apache/druid/msq/querykit/common/SortMergeJoinFrameProcessor.java:277

   * @param markCmp    result of {@link #compareMarks()}
   * @param marksMatch whether the marks actually matched, taking nulls into account
   *
   * @return true if cursors should be advanced, false if we should run again without moving cursors
   */
  private boolean emitRowIfNeeded(final int markCmp, final boolean marksMatch) throws IOException
  {
    if (marksMatch || (markCmp <= 0 && joinType.isLefty()) || (markCmp >= 0 && joinType.isRighty())) {
      // Emit row, if there's room in the current frameWriter.
      joinColumnSelectorFactory.cmp = markCmp;
      joinColumnSelectorFactory.match = marksMatch;

      if (!frameWriter.addSelection()) {
        if (frameWriter.getNumRows() > 0) {
          // Out of space in the current frame. Run again without moving cursors.
          flushCurrentFrame();
          return false;
        } else {
          throw new FrameRowTooLargeException(frameWriterFactory.allocatorCapacity());
        }
      }
    }

    return true;
  }

  /**
   * Advance one or both trackers after emitting a row.
   *
   * @param markCmp    result of {@link #compareMarks()}
   * @param marksMatch whether the marks actually matched, taking nulls into account
   */
  private void advanceTrackersAfterEmittingRow(final int markCmp, final boolean marksMatch)
  {
    if (marksMatch) {
      // Matching keys. First advance the tracker with the complete set.
      final Tracker completeSetTracker = trackers.get(trackerWithCompleteSetForCurrentKey);

View on GitHub (pinned to 9b90983fd2)

Solutions

  1. Increase the MSQ frame allocator capacity (frame size / worker memory settings)
  2. Project only required columns from each side of the JOIN before the join stage
  3. Shorten or hash large join payload columns; move big columns to a post-join lookup
  4. Increase peon/task memory to allow bigger frames

Example fix

// before
SELECT a.*, b.* FROM tbl1 a JOIN tbl2 b ON a.k = b.k
// after
SELECT a.k, a.val1, b.val2 FROM tbl1 a JOIN tbl2 b ON a.k = b.k
Defensive patterns

Strategy: validation

Validate before calling

// Ensure joined row width fits frame capacity
long joinedRowBytes = approxRowBytes(leftCols) + approxRowBytes(rightCols);
if (joinedRowBytes >= frameAllocatorCapacityBytes) {
  throw new IllegalStateException("Join output row (" + joinedRowBytes + "B) exceeds frame capacity");
}

Try / catch

try {
  runMsqQuery(query);
} catch (MSQException e) {
  if (e.getFault() instanceof FrameTooLarge) {
    // narrow join projection or raise frame allocator capacity, then retry
  }
}

Prevention

When it happens

Trigger: Sort-merge join stage emits a merged row (left+right columns concatenated) whose encoded size exceeds frameWriterFactory.allocatorCapacity() when the current frame is empty; reached via runIncrementally.

Common situations: Joining tables with many/wide string columns so combined rows are huge; low frame allocator capacity after aggressive memory tuning; large ARRAY/COMPLEX columns from either side of the join.

Understand the failure class

Background: payload too large / request exceeds maximum size: why libraries cap bytes and how to fix oversize payloads — this error's family across 50 libraries.

Related errors


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