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
- Add filtering/LIMIT to reduce the join result size
- Increase --max-memory-rows on vtgate if the query is legitimate and the host has memory headroom
- 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
- Set --max-memory-rows appropriately for vtgate host memory and expected workloads
- Add LIMIT/ key-range filters to join queries issued through vtgate
- Avoid unbounded cross joins in OLTP paths; paginate large result sets
- Alert on this error rate to catch runaway queries early
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
- in-memory row count exceeded allowed limit of %d
- VT09015
- cell mode should be handled by the gateway, not the balancer
- both the dry-run mode and actual buffering is enabled. To av
- non-constant regexp
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/ac88c40a496afd44.
Report an issue: GitHub.