cockroachdb / cockroachdb/cockroach
sql: smarter interaction between index recommendations and existing unique indexes
- Dominant language
- Go
- Stars
- 32.5k
- Forks
- 4.1k
- PR merge metrics
- PR metrics pending
Description
**Is your feature request related to a problem? Please describe.**
When the index recommendation engine produces a candidate that overlaps
with an existing unique index, it does not always do the right thing:
the engine can end up recommending an entirely new non-unique index
that subsumes the unique one's reads, leaving the original unique
index in place. The result is index bloat — both indexes are
maintained on every write, even though one of them is mostly there to
enforce the uniqueness constraint that the new index could carry
instead.
The desired behavior: when the recommendation's keys exactly match an
existing unique index (with extra STORING columns) — or extend it in a
way where uniqueness on the prefix is still meaningful — recommend
*replacing* the unique index with a new index that preserves the
uniqueness constraint, rather than creating a fresh non-unique index
alongside it.
**Describe the solution you'd like**
Extend the recommendation engine's existing replace-vs-create logic
(`findBestExistingIndexToReplace` in `pkg/sql/opt/indexrec/rec.go`)
to handle unique indexes more carefully:
1. **Same explicit keys, extra STORING columns.** Today, when a
hypothetical index has exactly the same explicit columns as an
existing index and adds STORING columns, the engine recommends a
replacement and preserves `Unique` via `existingIndex.IsUnique()`
(see `rec.go:298`). Verify this works as expected in practice for
unique indexes — write end-to-end tests if coverage is thin.
2. **Hypothetical extends a unique key.** When the hypothetical's
explicit columns are a *strict superset* (with the unique key as a
prefix) of an existing unique index's explicit columns, the
replacement path is not taken today. Investigate whether we can
replace the unique index with a new index that preserves uniqueness
on the original prefix — either by retaining a `UNIQUE (prefix)`
constraint via a separate mechanism, or by recognizing when the new
index's prefix is itself unique (e.g. backed by other invariants
like PK or NOT NULL + functional dependency) and dropping the
redundant unique index outright.
3. **Hypothetical reorders or drops a key column of a unique index.**
This should *not* trigger a replacement, because the uniqueness
semantics would change. Make sure these cases are correctly excluded.
The goal is to reduce the number of redundant indexes operators end
up with when acting on recommendations, especially on tables with
several unique indexes (which is common for tables with secondary
business keys).
**Describe alternatives you've considered**
- Leave the unique index alone, always create a separate non-unique
index (status quo). Cheapest to implement but contributes to index
bloat.
- Always replace the unique index when keys match, dropping
uniqueness. Bad — silently weakens a constraint the user declared.
**Additional context**
Code references:
- `findBestExistingIndexToReplace` in `pkg/sql/opt/indexrec/rec.go`
- `hasSameExplicitCols` / `hasPrefixOfExplicitCols` in
`pkg/sql/opt/indexrec/hypothetical_index.go`
- Existing `Unique: existingIndex.IsUnique()` line at `rec.go:298`
Related work in this epic:
- Recommendation validation (issues filed under this epic) means a
replacement of a unique index can be validated before commit, which
reduces the risk of getting the uniqueness-preservation logic wrong.
Open design questions:
- For case (2) above: is there a way to preserve a uniqueness
constraint on a key prefix without keeping the original index? (e.g.
a separate `UNIQUE WITHOUT INDEX` constraint, or recognizing
functional dependencies.)
- Should recommendations involving uniqueness changes be marked
specially in the output so operators review them more carefully?
Epic CRDB-64888
Jira issue: CRDB-64892
Contributor guide
Research direction
Start in pkg/sql/opt/indexrec/rec.go at findBestExistingIndexToReplace and trace the existing Unique: existingIndex.IsUnique() path. Read hasSameExplicitCols and hasPrefixOfExplicitCols in pkg/sql/opt/indexrec/hypothetical_index.go, then inspect the existing index recommendation tests and add end-to-end coverage for matching, extending, reordering, and dropping unique key columns. Done means replacements preserve uniqueness where valid and avoid replacements when semantics would change.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, sql
- Domain
- databases
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100