Skip to content

Commit

Permalink
sql: improve EXPLAIN (DDL) output
Browse files Browse the repository at this point in the history
Previously, the EXPLAIN (DDL, STAGES) and EXPLAIN (DDL, DEPS) statements
were printing graphviz URLs to help engineers during the development of
the declarative schema changer.

Now that the declarative schema changer is going live, the audience for
EXPLAIN (DDL) changes to become the users. The graphviz URLs were, at
any rate, not very useful for schema changes involving many objects, due
to limitations in the rendering engine.

This commit gets rid of the STAGES and DEPS flags to provide a unified
plan which is similar to the data-driven test output in the scplan
package. The VERBOSE flag can now be used to render a superset of the
information in the default rendering.

Touches #77158.

Release note (sql change): EXPLAIN (DDL), when invoked on statements
supported by the declarative schema changer, prints a plan of what the
schema changer will do. This can be useful for the user to anticipate
the complexity of a schema change (anything involving Backfill or
Validation operations might be slow to run). This can be useful for
troubleshooting. EXPLAIN (DDL, VERBOSE) produces a more detailed plan.
  • Loading branch information
Marius Posta committed Apr 11, 2022
1 parent 6d0d912 commit 00a868c
Show file tree
Hide file tree
Showing 12 changed files with 1,005 additions and 87 deletions.
18 changes: 9 additions & 9 deletions pkg/sql/explain_ddl.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,18 +72,18 @@ func (n *explainDDLNode) startExec(params runParams) error {
if err != nil {
return errors.WithAssertionFailure(err)
}
var vizURL string
if n.options.Flags[tree.ExplainFlagDeps] {
if vizURL, err = sc.DependenciesURL(); err != nil {
return errors.WithAssertionFailure(err)
}
var info string
if n.options.Flags[tree.ExplainFlagVerbose] {
info, err = sc.ExplainVerbose()
} else {
if vizURL, err = sc.StagesURL(); err != nil {
return errors.WithAssertionFailure(err)
}
info, err = sc.ExplainCompact()
}

if err != nil {
return errors.WithAssertionFailure(err)
}
n.values = tree.Datums{
tree.NewDString(vizURL),
tree.NewDString(info),
}
return nil
}
619 changes: 610 additions & 9 deletions pkg/sql/logictest/testdata/logic_test/new_schema_changer

Large diffs are not rendered by default.

7 changes: 2 additions & 5 deletions pkg/sql/opt/optbuilder/explain.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,9 @@ func (b *Builder) buildExplain(explain *tree.Explain, inScope *scope) (outScope

case tree.ExplainVec:
telemetry.Inc(sqltelemetry.ExplainVecUseCounter)

case tree.ExplainDDL:
if explain.Flags[tree.ExplainFlagDeps] {
telemetry.Inc(sqltelemetry.ExplainDDLDeps)
} else {
telemetry.Inc(sqltelemetry.ExplainDDLStages)
}
telemetry.Inc(sqltelemetry.ExplainDDL)

case tree.ExplainGist:
telemetry.Inc(sqltelemetry.ExplainGist)
Expand Down
1 change: 0 additions & 1 deletion pkg/sql/schemachanger/schemachanger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -890,7 +890,6 @@ func TestNewSchemaChangerVersionGating(t *testing.T) {
results := tdb.QueryStr(t, "EXPLAIN (DDL) DROP TABLE db.t;")
require.Equal(t, len(results), 1)
require.Equal(t, len(results[0]), 1)
require.Contains(t, results[0][0], "https://cockroachdb.github.io/scplan/viz.html")
})

t.Run("new_schema_changer_version_disabled", func(t *testing.T) {
Expand Down
9 changes: 8 additions & 1 deletion pkg/sql/schemachanger/scplan/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")

go_library(
name = "scplan",
srcs = ["plan.go"],
srcs = [
"plan.go",
"plan_explain.go",
],
importpath = "github.com/cockroachdb/cockroach/pkg/sql/schemachanger/scplan",
visibility = ["//visibility:public"],
deps = [
Expand All @@ -14,9 +17,13 @@ go_library(
"//pkg/sql/schemachanger/scplan/internal/scgraph",
"//pkg/sql/schemachanger/scplan/internal/scgraphviz",
"//pkg/sql/schemachanger/scplan/internal/scstage",
"//pkg/sql/schemachanger/screl",
"//pkg/util",
"//pkg/util/log",
"//pkg/util/timeutil",
"//pkg/util/treeprinter",
"@com_github_cockroachdb_errors//:errors",
"@in_gopkg_yaml_v2//:yaml_v2",
],
)

Expand Down
9 changes: 5 additions & 4 deletions pkg/sql/schemachanger/scplan/internal/rules/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,14 @@ func ApplyDepRules(g *scgraph.Graph) error {
// to the registered rules.
func ApplyOpRules(g *scgraph.Graph) (*scgraph.Graph, error) {
db := g.Database()
m := make(map[*screl.Node]struct{})
m := make(map[*screl.Node][]string)
for _, rule := range registry.opRules {
var added int
start := timeutil.Now()
err := rule.q.Iterate(db, func(r rel.Result) error {
added++
m[r.Var(rule.from).(*screl.Node)] = struct{}{}
n := r.Var(rule.from).(*screl.Node)
m[n] = append(m[n], rule.name)
return nil
})
if err != nil {
Expand All @@ -74,9 +75,9 @@ func ApplyOpRules(g *scgraph.Graph) (*scgraph.Graph, error) {
}
// Mark any op edges from these nodes as no-op.
ret := g.ShallowClone()
for from := range m {
for from, rules := range m {
if opEdge, ok := g.GetOpEdgeFrom(from); ok {
ret.MarkAsNoOp(opEdge)
ret.MarkAsNoOp(opEdge, rules...)
}
}
return ret, nil
Expand Down
35 changes: 29 additions & 6 deletions pkg/sql/schemachanger/scplan/internal/scgraph/graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
package scgraph

import (
"sort"

"github.com/cockroachdb/cockroach/pkg/sql/schemachanger/rel"
"github.com/cockroachdb/cockroach/pkg/sql/schemachanger/scop"
"github.com/cockroachdb/cockroach/pkg/sql/schemachanger/scpb"
Expand Down Expand Up @@ -48,7 +50,7 @@ type Graph struct {

// noOpOpEdges that are marked optimized out, and will not generate
// any operations.
noOpOpEdges map[*OpEdge]bool
noOpOpEdges map[*OpEdge]map[string]struct{}

edges []Edge

Expand All @@ -75,7 +77,7 @@ func New(cs scpb.CurrentState) (*Graph, error) {
g := Graph{
targetIdxMap: map[*scpb.Target]int{},
opEdgesFrom: map[*screl.Node]*OpEdge{},
noOpOpEdges: map[*OpEdge]bool{},
noOpOpEdges: map[*OpEdge]map[string]struct{}{},
opToOpEdge: map[scop.Op]*OpEdge{},
entities: db,
}
Expand Down Expand Up @@ -112,7 +114,7 @@ func (g *Graph) ShallowClone() *Graph {
opToOpEdge: g.opToOpEdge,
edges: g.edges,
entities: g.entities,
noOpOpEdges: make(map[*OpEdge]bool),
noOpOpEdges: make(map[*OpEdge]map[string]struct{}),
}
// Any decorations for mutations will be copied.
for edge, noop := range g.noOpOpEdges {
Expand Down Expand Up @@ -239,13 +241,34 @@ func (g *Graph) AddDepEdge(

// MarkAsNoOp marks an edge as no-op, so that no operations are emitted from
// this edge during planning.
func (g *Graph) MarkAsNoOp(edge *OpEdge) {
g.noOpOpEdges[edge] = true
func (g *Graph) MarkAsNoOp(edge *OpEdge, rule ...string) {
m := make(map[string]struct{})
for _, r := range rule {
m[r] = struct{}{}
}
g.noOpOpEdges[edge] = m
}

// IsNoOp checks if an edge is marked as an edge that should emit no operations.
func (g *Graph) IsNoOp(edge *OpEdge) bool {
return len(edge.op) == 0 || g.noOpOpEdges[edge]
if len(edge.op) == 0 {
return true
}
_, isNoOp := g.noOpOpEdges[edge]
return isNoOp
}

// NoOpRules returns the rules which caused the edge to not emit any operations.
func (g *Graph) NoOpRules(edge *OpEdge) (rules []string) {
if !g.IsNoOp(edge) {
return nil
}
m := g.noOpOpEdges[edge]
for rule := range m {
rules = append(rules, rule)
}
sort.Strings(rules)
return rules
}

// Order returns the number of nodes in this graph.
Expand Down
27 changes: 0 additions & 27 deletions pkg/sql/schemachanger/scplan/internal/scgraphviz/graphviz.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,33 +70,6 @@ func buildURL(gv string) (string, error) {
}).String(), nil
}

// DecorateErrorWithPlanDetails adds plan graphviz URLs as error details.
func DecorateErrorWithPlanDetails(
err error, cs scpb.CurrentState, g *scgraph.Graph, stages []scstage.Stage,
) error {
if err == nil {
return nil
}

if len(stages) > 0 {
stagesURL, stagesErr := StagesURL(cs, g, stages)
if stagesErr != nil {
return errors.CombineErrors(err, stagesErr)
}
err = errors.WithDetailf(err, "stages: %s", stagesURL)
}

if g != nil {
dependenciesURL, dependenciesErr := DependenciesURL(cs, g)
if dependenciesErr != nil {
return errors.CombineErrors(err, dependenciesErr)
}
err = errors.WithDetailf(err, "dependencies: %s", dependenciesURL)
}

return errors.WithAssertionFailure(err)
}

// DrawStages returns a graphviz string of the stages of the Plan.
func DrawStages(cs scpb.CurrentState, g *scgraph.Graph, stages []scstage.Stage) (string, error) {
if len(stages) == 0 {
Expand Down
16 changes: 0 additions & 16 deletions pkg/sql/schemachanger/scplan/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (
"github.com/cockroachdb/cockroach/pkg/sql/schemachanger/scplan/internal/opgen"
"github.com/cockroachdb/cockroach/pkg/sql/schemachanger/scplan/internal/rules"
"github.com/cockroachdb/cockroach/pkg/sql/schemachanger/scplan/internal/scgraph"
"github.com/cockroachdb/cockroach/pkg/sql/schemachanger/scplan/internal/scgraphviz"
"github.com/cockroachdb/cockroach/pkg/sql/schemachanger/scplan/internal/scstage"
"github.com/cockroachdb/cockroach/pkg/util/log"
"github.com/cockroachdb/cockroach/pkg/util/timeutil"
Expand Down Expand Up @@ -73,21 +72,6 @@ func (p Plan) StagesForCurrentPhase() []scstage.Stage {
return p.Stages
}

// DecorateErrorWithPlanDetails adds plan graphviz URLs as error details.
func (p Plan) DecorateErrorWithPlanDetails(err error) error {
return scgraphviz.DecorateErrorWithPlanDetails(err, p.CurrentState, p.Graph, p.Stages)
}

// DependenciesURL returns a URL to render the dependency graph in the Plan.
func (p Plan) DependenciesURL() (string, error) {
return scgraphviz.DependenciesURL(p.CurrentState, p.Graph)
}

// StagesURL returns a URL to render the stages in the Plan.
func (p Plan) StagesURL() (string, error) {
return scgraphviz.StagesURL(p.CurrentState, p.Graph, p.Stages)
}

// MakePlan generates a Plan for a particular phase of a schema change, given
// the initial state for a set of targets.
// Returns an error when planning fails. It is up to the caller to wrap this
Expand Down
Loading

0 comments on commit 00a868c

Please sign in to comment.