Skip to content

Commit

Permalink
sql: show forecasted stats time in EXPLAIN
Browse files Browse the repository at this point in the history
When using statistics forecasts, add the forecast time (which could be
in the future) to EXPLAIN output. This both indicates that forecasts are
in use, and gives us an idea of how up-to-date / ahead they are.

Assists: cockroachdb#79872

Release note: None
  • Loading branch information
michae2 committed Aug 16, 2022
1 parent b4420e5 commit 430c824
Show file tree
Hide file tree
Showing 7 changed files with 57 additions and 2 deletions.
3 changes: 3 additions & 0 deletions pkg/sql/opt/cat/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,9 @@ type TableStatistic interface {
// HistogramType returns the type that the histogram was created on. For
// inverted index histograms, this will always return types.Bytes.
HistogramType() *types.T

// IsForecast returns true if this statistic is a forecast.
IsForecast() bool
}

// HistogramBucket contains the data for a single histogram bucket. Note
Expand Down
12 changes: 12 additions & 0 deletions pkg/sql/opt/exec/execbuilder/relational.go
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,18 @@ func (b *Builder) maybeAnnotateWithEstimates(node exec.Node, e memo.RelExpr) {
}
val.TableStatsCreatedAt = stat.CreatedAt()
val.LimitHint = scan.RequiredPhysical().LimitHint
val.Forecast = stat.IsForecast()
if val.Forecast {
val.ForecastAt = stat.CreatedAt()
// Find the first non-forecast stat.
for i := 0; i < tab.StatisticCount(); i++ {
nextStat := tab.Statistic(i)
if !nextStat.IsForecast() {
val.TableStatsCreatedAt = nextStat.CreatedAt()
break
}
}
}
}
}
ef.AnnotateNode(node, exec.EstimatedStatsID, &val)
Expand Down
25 changes: 23 additions & 2 deletions pkg/sql/opt/exec/explain/emit.go
Original file line number Diff line number Diff line change
Expand Up @@ -475,10 +475,31 @@ func (e *emitter) emitNodeAttributes(n *Node) error {
}
duration = string(humanizeutil.LongDuration(timeSinceStats))
}

var forecastStr string
if s.Forecast {
if e.ob.flags.Redact.Has(RedactVolatile) {
forecastStr = "; using stats forecast"
} else {
timeSinceStats := timeutil.Since(s.ForecastAt)
if timeSinceStats >= 0 {
forecastStr = fmt.Sprintf(
"; using stats forecast for %s ago", humanizeutil.LongDuration(timeSinceStats),
)
} else {
timeSinceStats *= -1
forecastStr = fmt.Sprintf(
"; using stats forecast for %s in the future",
humanizeutil.LongDuration(timeSinceStats),
)
}
}
}

e.ob.AddField("estimated row count", fmt.Sprintf(
"%s (%s%% of the table; stats collected %s ago)",
"%s (%s%% of the table; stats collected %s ago%s)",
estimatedRowCountString, percentageStr,
duration,
duration, forecastStr,
))
} else {
e.ob.AddField("estimated row count", estimatedRowCountString)
Expand Down
6 changes: 6 additions & 0 deletions pkg/sql/opt/exec/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,12 @@ type EstimatedStats struct {
// LimitHint is the "soft limit" of the number of result rows that may be
// required. See physical.Required for details.
LimitHint float64
// Forecast is set only for scans; it is true if the stats for the scan were
// forecasted rather than collected.
Forecast bool
// ForecastAt is set only for scans with forecasted stats; it is the time the
// forecast was for (which could be in the past, present, or future).
ForecastAt time.Time
}

// ExecutionStats contain statistics about a given operator gathered from the
Expand Down
1 change: 1 addition & 0 deletions pkg/sql/opt/testutils/testcat/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ go_library(
deps = [
"//pkg/config/zonepb",
"//pkg/geo/geoindex",
"//pkg/jobs/jobspb",
"//pkg/roachpb",
"//pkg/security/username",
"//pkg/settings/cluster",
Expand Down
6 changes: 6 additions & 0 deletions pkg/sql/opt/testutils/testcat/test_catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (

"github.com/cockroachdb/cockroach/pkg/config/zonepb"
"github.com/cockroachdb/cockroach/pkg/geo/geoindex"
"github.com/cockroachdb/cockroach/pkg/jobs/jobspb"
"github.com/cockroachdb/cockroach/pkg/roachpb"
"github.com/cockroachdb/cockroach/pkg/security/username"
"github.com/cockroachdb/cockroach/pkg/settings/cluster"
Expand Down Expand Up @@ -1230,6 +1231,11 @@ func (ts *TableStat) HistogramType() *types.T {
return tree.MustBeStaticallyKnownType(colTypeRef)
}

// IsForecast is part of the cat.TableStatistic interface.
func (ts *TableStat) IsForecast() bool {
return ts.js.Name == jobspb.ForecastStatsName
}

// TableStats is a slice of TableStat pointers.
type TableStats []*TableStat

Expand Down
6 changes: 6 additions & 0 deletions pkg/sql/opt_catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/cockroachdb/cockroach/pkg/clusterversion"
"github.com/cockroachdb/cockroach/pkg/config"
"github.com/cockroachdb/cockroach/pkg/geo/geoindex"
"github.com/cockroachdb/cockroach/pkg/jobs/jobspb"
"github.com/cockroachdb/cockroach/pkg/keys"
"github.com/cockroachdb/cockroach/pkg/kv"
"github.com/cockroachdb/cockroach/pkg/roachpb"
Expand Down Expand Up @@ -1685,6 +1686,11 @@ func (os *optTableStat) HistogramType() *types.T {
return os.stat.HistogramData.ColumnType
}

// IsForecast is part of the cat.TableStatistic interface.
func (os *optTableStat) IsForecast() bool {
return os.stat.Name == jobspb.ForecastStatsName
}

// optFamily is a wrapper around descpb.ColumnFamilyDescriptor that keeps a
// reference to the table wrapper.
type optFamily struct {
Expand Down

0 comments on commit 430c824

Please sign in to comment.