Skip to content

Commit

Permalink
sql: generate index recommendations
Browse files Browse the repository at this point in the history
We want to collect index recommendations
per statement and save it to the statement_statistics table.
To accomplish this, a new cache map was created.
The key for the map is a combination of the
statement fingerprint, the database and the plan hash,
since those are the parameters that would affect
an index recommendation (from the parameters we use
as keys for the statement fingerprint ID at least).

This cache has a limit of unique recommendations,
limited by a new cluster setting called
`sql.metrics.statement_details.max_mem_reported_idx_recommendations`
with a default value of 100k.
If this limit is reached, we delete entries that
had their last update made more than 24h ago.

A new recommendations is generated if has been more
than 1h since the last recommendation (saved on cache)
and it has at least a few executions (so we handle the case
where we have just one execution of several different
fingerprints, and is not worth calculating recommendations
for them).

The recommendations are saved as a list, with each
recommendations being "type : sql command", e.g.
`{"creation : CREATE INDEX ON t2 (i) STORING (k);"}`
and
`{"replacement : CREATE UNIQUE INDEX ON t1 (i) STORING (k);
DROP INDEX t1@existing_t1_i;"}`

Closes cockroachdb#83782

Release note (sql change): Index recommendations are generated
for statements. New recommendations are generated every hour and
available on `system.statement_statistics` and
`crdb_internal.statement_statistics`.
New cluster setting with default value of 100k
sql.metrics.statement_details.max_mem_reported_idx_recommendations
  • Loading branch information
maryliag committed Aug 8, 2022
1 parent d4f2c41 commit 464f0cf
Show file tree
Hide file tree
Showing 16 changed files with 620 additions and 4 deletions.
1 change: 1 addition & 0 deletions docs/generated/settings/settings-for-tenants.txt
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ sql.metrics.max_mem_txn_fingerprints integer 100000 the maximum number of transa
sql.metrics.statement_details.dump_to_logs boolean false dump collected statement statistics to node logs when periodically cleared
sql.metrics.statement_details.enabled boolean true collect per-statement query statistics
sql.metrics.statement_details.index_recommendation_collection.enabled boolean true generate an index recommendation for each fingerprint ID
sql.metrics.statement_details.max_mem_reported_idx_recommendations integer 5000 the maximum number of reported index recommendation info stored in memory
sql.metrics.statement_details.plan_collection.enabled boolean true periodically save a logical plan for each fingerprint
sql.metrics.statement_details.plan_collection.period duration 5m0s the time until a new logical plan is collected
sql.metrics.statement_details.threshold duration 0s minimum execution time to cause statement statistics to be collected. If configured, no transaction stats are collected.
Expand Down
1 change: 1 addition & 0 deletions docs/generated/settings/settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@
<tr><td><code>sql.metrics.statement_details.dump_to_logs</code></td><td>boolean</td><td><code>false</code></td><td>dump collected statement statistics to node logs when periodically cleared</td></tr>
<tr><td><code>sql.metrics.statement_details.enabled</code></td><td>boolean</td><td><code>true</code></td><td>collect per-statement query statistics</td></tr>
<tr><td><code>sql.metrics.statement_details.index_recommendation_collection.enabled</code></td><td>boolean</td><td><code>true</code></td><td>generate an index recommendation for each fingerprint ID</td></tr>
<tr><td><code>sql.metrics.statement_details.max_mem_reported_idx_recommendations</code></td><td>integer</td><td><code>5000</code></td><td>the maximum number of reported index recommendation info stored in memory</td></tr>
<tr><td><code>sql.metrics.statement_details.plan_collection.enabled</code></td><td>boolean</td><td><code>true</code></td><td>periodically save a logical plan for each fingerprint</td></tr>
<tr><td><code>sql.metrics.statement_details.plan_collection.period</code></td><td>duration</td><td><code>5m0s</code></td><td>the time until a new logical plan is collected</td></tr>
<tr><td><code>sql.metrics.statement_details.threshold</code></td><td>duration</td><td><code>0s</code></td><td>minimum execution time to cause statement statistics to be collected. If configured, no transaction stats are collected.</td></tr>
Expand Down
4 changes: 4 additions & 0 deletions pkg/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ ALL_TESTS = [
"//pkg/sql/gcjob/gcjobnotifier:gcjobnotifier_test",
"//pkg/sql/gcjob:gcjob_test",
"//pkg/sql/gcjob_test:gcjob_test_test",
"//pkg/sql/idxrecommendations:idxrecommendations_test",
"//pkg/sql/idxusage:idxusage_test",
"//pkg/sql/importer:importer_test",
"//pkg/sql/inverted:inverted_disallowed_imports_test",
Expand Down Expand Up @@ -1416,6 +1417,8 @@ GO_TARGETS = [
"//pkg/sql/gcjob:gcjob",
"//pkg/sql/gcjob:gcjob_test",
"//pkg/sql/gcjob_test:gcjob_test_test",
"//pkg/sql/idxrecommendations:idxrecommendations",
"//pkg/sql/idxrecommendations:idxrecommendations_test",
"//pkg/sql/idxusage:idxusage",
"//pkg/sql/idxusage:idxusage_test",
"//pkg/sql/importer:importer",
Expand Down Expand Up @@ -2523,6 +2526,7 @@ GET_X_DATA_TARGETS = [
"//pkg/sql/gcjob:get_x_data",
"//pkg/sql/gcjob/gcjobnotifier:get_x_data",
"//pkg/sql/gcjob_test:get_x_data",
"//pkg/sql/idxrecommendations:get_x_data",
"//pkg/sql/idxusage:get_x_data",
"//pkg/sql/importer:get_x_data",
"//pkg/sql/inverted:get_x_data",
Expand Down
2 changes: 1 addition & 1 deletion pkg/roachpb/app_stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ func (s *StatementStatistics) Add(other *StatementStatistics) {
s.RowsWritten.Add(other.RowsWritten, s.Count, other.Count)
s.Nodes = util.CombineUniqueInt64(s.Nodes, other.Nodes)
s.PlanGists = util.CombineUniqueString(s.PlanGists, other.PlanGists)
s.IndexRecommendations = util.CombineUniqueString(s.IndexRecommendations, other.IndexRecommendations)
s.IndexRecommendations = other.IndexRecommendations

s.ExecStats.Add(other.ExecStats)

Expand Down
1 change: 1 addition & 0 deletions pkg/sql/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@ go_library(
"//pkg/sql/faketreeeval",
"//pkg/sql/flowinfra",
"//pkg/sql/gcjob/gcjobnotifier",
"//pkg/sql/idxrecommendations",
"//pkg/sql/idxusage",
"//pkg/sql/inverted",
"//pkg/sql/lex",
Expand Down
6 changes: 6 additions & 0 deletions pkg/sql/conn_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import (
"github.com/cockroachdb/cockroach/pkg/sql/clusterunique"
"github.com/cockroachdb/cockroach/pkg/sql/contention/txnidcache"
"github.com/cockroachdb/cockroach/pkg/sql/execstats"
"github.com/cockroachdb/cockroach/pkg/sql/idxrecommendations"
"github.com/cockroachdb/cockroach/pkg/sql/idxusage"
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode"
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror"
Expand Down Expand Up @@ -305,6 +306,8 @@ type Server struct {
// TelemetryLoggingMetrics is used to track metrics for logging to the telemetry channel.
TelemetryLoggingMetrics *TelemetryLoggingMetrics

idxRecommendationsCache *idxrecommendations.IndexRecCache

mu struct {
syncutil.Mutex
connectionCount int64
Expand Down Expand Up @@ -393,6 +396,9 @@ func NewServer(cfg *ExecutorConfig, pool *mon.BytesMonitor) *Server {
txnIDCache: txnidcache.NewTxnIDCache(
cfg.Settings,
&serverMetrics.ContentionSubsystemMetrics),
idxRecommendationsCache: idxrecommendations.NewIndexRecommendationsCache(
cfg.Settings,
sqlstats.MaxMemReportedSampleIndexRecommendations),
}

telemetryLoggingMetrics := &TelemetryLoggingMetrics{}
Expand Down
3 changes: 3 additions & 0 deletions pkg/sql/conn_executor_exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -1194,6 +1194,9 @@ func (ex *connExecutor) dispatchToExecutionEngine(
ex.extraTxnState.bytesRead += stats.bytesRead
ex.extraTxnState.rowsWritten += stats.rowsWritten

// Set index recommendations so it can be saved on statement statistics.
planner.instrumentation.SetIndexRecommendations(ctx, ex.server.idxRecommendationsCache, planner)

// Record the statement summary. This also closes the plan if the
// plan has not been closed earlier.
stmtFingerprintID = ex.recordStatementSummary(
Expand Down
4 changes: 3 additions & 1 deletion pkg/sql/executor_statement_metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (

"github.com/cockroachdb/cockroach/pkg/roachpb"
"github.com/cockroachdb/cockroach/pkg/sql/execinfrapb"
"github.com/cockroachdb/cockroach/pkg/sql/idxrecommendations"
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
"github.com/cockroachdb/cockroach/pkg/sql/sessionphase"
"github.com/cockroachdb/cockroach/pkg/sql/sqlstats"
Expand Down Expand Up @@ -169,6 +170,7 @@ func (ex *connExecutor) recordStatementSummary(
PlanHash: planner.instrumentation.planGist.Hash(),
}

idxRecommendations := idxrecommendations.FormatIdxRecommendations(planner.instrumentation.indexRecommendations)
recordedStmtStats := sqlstats.RecordedStmtStats{
SessionID: ex.sessionID,
StatementID: planner.stmt.QueryID,
Expand All @@ -187,7 +189,7 @@ func (ex *connExecutor) recordStatementSummary(
Plan: planner.instrumentation.PlanForStats(ctx),
PlanGist: planner.instrumentation.planGist.String(),
StatementError: stmtErr,
IndexRecommendations: planner.instrumentation.indexRecommendations,
IndexRecommendations: idxRecommendations,
Query: stmt.StmtNoConstants,
StartTime: phaseTimes.GetSessionPhaseTime(sessionphase.PlannerStartExecStmt),
EndTime: phaseTimes.GetSessionPhaseTime(sessionphase.PlannerEndExecStmt),
Expand Down
43 changes: 43 additions & 0 deletions pkg/sql/idxrecommendations/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
load("//build/bazelutil/unused_checker:unused.bzl", "get_x_data")
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")

go_library(
name = "idxrecommendations",
srcs = [
"idx_recommendations.go",
"idx_recommendations_cache.go",
],
importpath = "github.com/cockroachdb/cockroach/pkg/sql/idxrecommendations",
visibility = ["//visibility:public"],
deps = [
"//pkg/settings",
"//pkg/settings/cluster",
"//pkg/sql/sem/tree",
"//pkg/sql/sqlstats",
"//pkg/util/syncutil",
"//pkg/util/timeutil",
],
)

go_test(
name = "idxrecommendations_test",
srcs = [
"idx_recommendations_cache_test.go",
"main_test.go",
],
deps = [
":idxrecommendations",
"//pkg/security/securityassets",
"//pkg/security/securitytest",
"//pkg/server",
"//pkg/sql/tests",
"//pkg/testutils/serverutils",
"//pkg/testutils/sqlutils",
"//pkg/testutils/testcluster",
"//pkg/util/leaktest",
"//pkg/util/log",
"@com_github_stretchr_testify//require",
],
)

get_x_data(name = "get_x_data")
69 changes: 69 additions & 0 deletions pkg/sql/idxrecommendations/idx_recommendations.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright 2022 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

package idxrecommendations

import (
"fmt"
"strings"

"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
)

// IdxRecommendations controls the generation of index recommendations
// for specific statements, and update accordingly.
type IdxRecommendations interface {
ShouldGenerateIndexRecommendation(
fingerprint string, planHash uint64, database string, stmtType tree.StatementType,
) bool
UpdateIndexRecommendations(
fingerprint string,
planHash uint64,
database string,
stmtType tree.StatementType,
recommendations []string,
reset bool,
) []string
}

// FormatIdxRecommendations received a list with recommendations info, e.g.:
//{
// "index recommendations: 2",
// "1. type: index replacement",
// "SQL commands: CREATE UNIQUE INDEX ON t1 (i) STORING (k); DROP INDEX t1@existing_t1_i;",
// "2. type: index creation",
// "SQL command: CREATE INDEX ON t2 (i) STORING (k);",
//}
// and returns a list of type and recommendations, e.g.:
//{
// "replacement : CREATE UNIQUE INDEX ON t1 (i) STORING (k); DROP INDEX t1@existing_t1_i;",
// "creation : CREATE INDEX ON t2 (i) STORING (k);",
//}
func FormatIdxRecommendations(idxRec []string) []string {
recommendations := []string{}
if len(idxRec) == 0 {
return recommendations
}

var recType string
var recCommand string
var rec string

for i := 1; i < len(idxRec); i++ {
recType = strings.Split(idxRec[i], "type: index ")[1]
recCommand = strings.Replace(idxRec[i+1], " SQL command: ", "", 1)
recCommand = strings.Replace(recCommand, " SQL commands: ", "", 1)
rec = fmt.Sprintf("%s : %s", recType, recCommand)
recommendations = append(recommendations, rec)
i++
}

return recommendations
}
Loading

0 comments on commit 464f0cf

Please sign in to comment.