vitessio/vitess · error

in-memory row count exceeded allowed limit of %d

Error message

in-memory row count exceeded allowed limit of %d

What it means

Join and other in-memory engine primitives materialize rows in vtgate memory. Before returning, TryExecute checks vcursor.ExceedsMaxMemoryRows; when the joined result set exceeds --max_memory_rows, execution aborts with this error instead of risking OOM on the gate.

Source

Thrown at go/vt/vtgate/engine/join.go:94

		for k, col := range jn.Vars {
			joinVars[k] = sqltypes.ValueBindVariable(lrow[col])
		}
		rresult, err := vcursor.ExecutePrimitive(ctx, jn.Right, combineVars(bindVars, joinVars), wantfields)
		if err != nil {
			return nil, err
		}
		if wantfields {
			wantfields = false
			result.Fields = joinFields(lresult.Fields, rresult.Fields, jn.Cols)
		}
		for _, rrow := range rresult.Rows {
			result.Rows = append(result.Rows, joinRows(lrow, rrow, jn.Cols))
		}
		if jn.Opcode == LeftJoin && len(rresult.Rows) == 0 {
			result.Rows = append(result.Rows, joinRows(lrow, nil, jn.Cols))
		}
		if vcursor.ExceedsMaxMemoryRows(len(result.Rows)) {
			return nil, fmt.Errorf("in-memory row count exceeded allowed limit of %d", vcursor.MaxMemoryRows())
		}
	}
	return result, nil
}

func bindvarForType(field *querypb.Field) *querypb.BindVariable {
	bv := &querypb.BindVariable{
		Type:  field.Type,
		Value: nil,
	}
	switch field.Type {
	case querypb.Type_INT8, querypb.Type_UINT8, querypb.Type_INT16, querypb.Type_UINT16,
		querypb.Type_INT32, querypb.Type_UINT32, querypb.Type_INT64, querypb.Type_UINT64:
		bv.Value = []byte("0")
	case querypb.Type_FLOAT32, querypb.Type_FLOAT64:
		bv.Value = []byte("0e0")
	case querypb.Type_DECIMAL:
		size := max(1, int(field.ColumnLength-field.Decimals))

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Add filtering/LIMIT to reduce the join result size
  2. Increase --max-memory-rows on vtgate if the query is legitimate and the host has memory headroom
  3. Rewrite the query to avoid large cross joins, or run it via a streaming-capable path / external query engine

Example fix

// before
SELECT * FROM a JOIN b ON a.k = b.k
// after
SELECT * FROM a JOIN b ON a.k = b.k WHERE a.tenant = :tenant LIMIT 1000
Defensive patterns

Strategy: fallback

Validate before calling

// estimate rows first
n, err := countJoinRows(query)
if err == nil && uint64(n) > maxMemoryRows {
    return errors.New("join would exceed in-memory row limit; add LIMIT or filters")
}

Try / catch

res, err := execute(query)
if err != nil && strings.Contains(err.Error(), "in-memory row count exceeded allowed limit") {
    // rewrite query with LIMIT/pagination or route to a batch engine
}

Prevention

When it happens

Trigger: Executing a JOIN query whose materialized result (left rows joined with right rows) exceeds the vtgate --max_memory_rows limit; called from TryExecute when appending joinRows results.

Common situations: Cartesian joins or large fan-out joins without LIMIT; --max_memory_rows left at its low default (100k); analytics-style queries run through vtgate that should go to a different path.

Related errors


AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01). Data as JSON: /api/errors/ac88c40a496afd44. Report an issue: GitHub.