Describe the problem
Constant folding of string || (concat) during normalization has no size cap.
EvalConcatOp / EvalConcatStringOp perform a bare concat, and the folded
datum is interned unconditionally with no limit. While each repeat() operand
is capped at 128 MiB, the || that combines them is not, so chaining a handful
of compact repeat() calls folds — at planning time — to an arbitrarily large
interned string (plus left-fold intermediates) on the plain Go heap, which is
not covered by --max-sql-memory.
Because repeat('x', 100000000) expresses a 100 MB operand in ~26 bytes of SQL,
the SQL text needed to OOM is tiny. EXPLAIN alone triggers it (no execution);
wrapping in length(...) keeps the giant value out of the result set but the
folder still materializes it.
To Reproduce
On a node limited to ~2GB (e.g. cockroach demo under a 2GB cgroup, or with
GOMEMLIMIT=1100000000 --max-sql-memory=512MiB --cache=512MiB), run this
171-byte statement:
EXPLAIN SELECT length(
repeat('x',100000000) || repeat('x',100000000) || repeat('x',100000000) ||
repeat('x',100000000) || repeat('x',100000000) || repeat('x',100000000));
The folded constant is O(N × 128 MiB) for N operands while the SQL is
O(N × ~26 bytes).
Observed
- 3 operands: completes, ~1.4GB peak RSS.
- 6 operands (171 bytes of SQL): heap grows past 2GB, OOM-killed during
EXPLAIN within a few seconds.
Environment
- CockroachDB
v26.4.0-alpha (master), CCL, cockroach demo single node.
- Client:
cockroach sql.
Code reference
EvalConcatOp / EvalConcatStringOp in pkg/sql/sem/eval/binary_op.go;
folding gated by canFoldOperator and interned by ConstructConstVal in
pkg/sql/opt/norm/fold_constants_funcs.go.
Jira issue: CRDB-67027
Describe the problem
Constant folding of string
||(concat) during normalization has no size cap.EvalConcatOp/EvalConcatStringOpperform a bare concat, and the foldeddatum is interned unconditionally with no limit. While each
repeat()operandis capped at 128 MiB, the
||that combines them is not, so chaining a handfulof compact
repeat()calls folds — at planning time — to an arbitrarily largeinterned string (plus left-fold intermediates) on the plain Go heap, which is
not covered by
--max-sql-memory.Because
repeat('x', 100000000)expresses a 100 MB operand in ~26 bytes of SQL,the SQL text needed to OOM is tiny.
EXPLAINalone triggers it (no execution);wrapping in
length(...)keeps the giant value out of the result set but thefolder still materializes it.
To Reproduce
On a node limited to ~2GB (e.g.
cockroach demounder a 2GB cgroup, or withGOMEMLIMIT=1100000000 --max-sql-memory=512MiB --cache=512MiB), run this171-byte statement:
The folded constant is
O(N × 128 MiB)forNoperands while the SQL isO(N × ~26 bytes).Observed
EXPLAINwithin a few seconds.Environment
v26.4.0-alpha(master), CCL,cockroach demosingle node.cockroach sql.Code reference
EvalConcatOp/EvalConcatStringOpinpkg/sql/sem/eval/binary_op.go;folding gated by
canFoldOperatorand interned byConstructConstValinpkg/sql/opt/norm/fold_constants_funcs.go.Jira issue: CRDB-67027