cockroachdb / cockroachdb/cockroach
sql/opt: unbounded constant folding of string || during planning can OOM
- Dominant language
- Go
- Stars
- 32.5k
- Forks
- 4.1k
- PR merge metrics
- PR metrics pending
Description
**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:
```sql
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
Contributor guide
Research direction
Reproduce the six-operand EXPLAIN case, then inspect EvalConcatOp and EvalConcatStringOp in pkg/sql/sem/eval/binary_op.go. Follow canFoldOperator and ConstructConstVal in pkg/sql/opt/norm/fold_constants_funcs.go to understand how concatenated constants are folded and interned. Done means the reproducer no longer permits unbounded planner-time string growth or OOM while constant folding remains correct.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, sql
- Domain
- databases, performance
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100