Skip to content

Commit

Permalink
stats: table-level setting to turn auto stats collection on/off
Browse files Browse the repository at this point in the history
Fixes cockroachdb#40989

Previously, there was no way to enable or disable automatic statistics
collection at the table level. It could only be turned on or off via the
`sql.stats.automatic_collection.enabled` cluster setting.

This was inadequate because statistics collection can be expensive for
large tables, and it would be desirable to defer collection until after
data is finished loading, or in off hours. Also, small tables which are
frequently updated may trigger statistics collection leading to
unnecessary overhead and/or unpredictable query plan changes.

To address this, this patch adds support for setting of the following
cluster settings at the table level:
```
sql.stats.automatic_collection.enabled
sql.stats.automatic_collection.fraction_stale_rows
sql.stats.automatic_collection.min_stale_rows
```
for example:
```
ALTER TABLE t1 SET ("sql.stats.automatic_collection.enabled" = true);
ALTER TABLE t1
      SET ("sql.stats.automatic_collection.fraction_stale_rows" = 0.1,
           "sql.stats.automatic_collection.min_stale_rows" = 2000);
```
The table-level setting takes precedence over the cluster setting.

Release justification: Low risk fix for missing fine-grained control
over automatic statistics collection.

Release note (sql change): Automatic statistics collection can now be
enabled or disabled for individual tables, taking precedence over the
cluster setting, for example:
```
ALTER TABLE t1 SET ("sql.stats.automatic_collection.enabled" = true);
ALTER TABLE t1 SET ("sql.stats.automatic_collection.enabled" = false);
ALTER TABLE t1 RESET ("sql.stats.automatic_collection.enabled");
```
RESET removes the setting value entirely, in which case the cluster
setting of the same name is in effect for the table.

Cluster settings `sql.stats.automatic_collection.fraction_stale_rows`
and `sql.stats.automatic_collection.min_stale_rows` can now also be
set at the table level, either at table creation time, or later,
independent of whether auto stats is enabled:
```
ALTER TABLE t1
      SET ("sql.stats.automatic_collection.fraction_stale_rows" = 0.1,
           "sql.stats.automatic_collection.min_stale_rows" = 2000);
CREATE TABLE t1 (a INT, b INT)
       WITH ("sql.stats.automatic_collection.enabled" = true,
             "sql.stats.automatic_collection.min_stale_rows" = 1000000,
             "sql.stats.automatic_collection.fraction_stale_rows" = 0.05
             );
```
The current table-level cluster settings, along with storage parameters,
is shown in the `WITH` clause output of `SHOW CREATE TABLE`.
Note, any row mutations which have occurred within a couple minutes of
disabling auto stats collection via `ALTER TABLE ... SET` may trigger
stats collection, though DML submitted after the setting change will
not.
  • Loading branch information
Mark Sirek committed Mar 28, 2022
1 parent cb10bfe commit e6f508f
Show file tree
Hide file tree
Showing 19 changed files with 1,396 additions and 63 deletions.
24 changes: 24 additions & 0 deletions pkg/settings/cluster/cluster_settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,30 @@ func TelemetryOptOut() bool {
// (for example, a CLI subcommand that does not connect to a cluster).
var NoSettings *Settings // = nil

// BoolSetting represents a boolean setting value.
type BoolSetting int

// The values for BoolSetting.
const (
NotSet = iota // NotSet is like a null. The value has not been set.
True
False
)

const (
// AutoStatsEnabledSettingName is the name of the automatic stats collection
// enabled cluster setting.
AutoStatsEnabledSettingName = "sql.stats.automatic_collection.enabled"

// AutoStatsMinStaleSettingName is the name of the automatic stats collection
// min stale rows cluster setting.
AutoStatsMinStaleSettingName = "sql.stats.automatic_collection.min_stale_rows"

// AutoStatsFractionStaleSettingName is the name of the automatic stats
// collection fraction stale rows cluster setting.
AutoStatsFractionStaleSettingName = "sql.stats.automatic_collection.fraction_stale_rows"
)

// CPUProfileType tracks whether a CPU profile is in progress.
type CPUProfileType int32

Expand Down
1 change: 1 addition & 0 deletions pkg/sql/catalog/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ go_library(
"//pkg/kv",
"//pkg/roachpb",
"//pkg/server/telemetry",
"//pkg/settings/cluster",
"//pkg/sql/catalog/catpb",
"//pkg/sql/catalog/descpb",
"//pkg/sql/pgwire/pgcode",
Expand Down
2 changes: 2 additions & 0 deletions pkg/sql/catalog/catpb/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ go_proto_library(
go_library(
name = "catpb",
srcs = [
"catalog.go",
"constraint.go",
"default_privilege.go",
"doc.go",
Expand All @@ -44,6 +45,7 @@ go_library(
deps = [
"//pkg/keys",
"//pkg/security",
"//pkg/settings/cluster",
"//pkg/sql/catalog/catconstants",
"//pkg/sql/privilege",
"//pkg/sql/sem/catid",
Expand Down
112 changes: 112 additions & 0 deletions pkg/sql/catalog/catpb/catalog.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// 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 catpb

import "github.com/cockroachdb/cockroach/pkg/settings/cluster"

// AutoStatsCollectionEnabled indicates if automatic statistics collection is
// explicitly enabled or disabled.
func (cs *ClusterSettingsForTable) AutoStatsCollectionEnabled() cluster.BoolSetting {
if cs.SqlStatsAutomaticCollectionEnabled == nil {
return cluster.NotSet
}
if *cs.SqlStatsAutomaticCollectionEnabled {
return cluster.True
}
return cluster.False
}

// AutoStatsMinStaleRows indicates the setting of
// sql.stats.automatic_collection.min_stale_rows in ClusterSettingsForTable.
// If ok is true, then the minStaleRows value is valid, otherwise this has not
// been set.
func (cs *ClusterSettingsForTable) AutoStatsMinStaleRows() (minStaleRows int64, ok bool) {
if cs.SqlStatsAutomaticCollectionMinStaleRows == nil {
return 0, false
}
return *cs.SqlStatsAutomaticCollectionMinStaleRows, true
}

// AutoStatsFractionStaleRows indicates the setting of
// sql.stats.automatic_collection.fraction_stale_rows in
// ClusterSettingsForTable. If ok is true, then the fractionStaleRows value is
// valid, otherwise this has not been set.
func (cs *ClusterSettingsForTable) AutoStatsFractionStaleRows() (
fractionStaleRows float64,
ok bool,
) {
if cs.SqlStatsAutomaticCollectionFractionStaleRows == nil {
return 0, false
}
return *cs.SqlStatsAutomaticCollectionFractionStaleRows, true
}

// NoAutoStatsSettingsOverrides is true if no auto stats related cluster
// settings are present in these ClusterSettingsForTable.
func (cs *ClusterSettingsForTable) NoAutoStatsSettingsOverrides() bool {
if cs == nil {
return true
}
if cs.SqlStatsAutomaticCollectionEnabled != nil ||
cs.SqlStatsAutomaticCollectionMinStaleRows != nil ||
cs.SqlStatsAutomaticCollectionFractionStaleRows != nil {
return false
}
return true
}

// AutoStatsSettingsEqual returns true if all auto stats related
// ClusterSettingsForTable in this structure match those in `other`. An unset
// value must also be unset in the other's settings to be equal.
func (cs *ClusterSettingsForTable) AutoStatsSettingsEqual(other *ClusterSettingsForTable) bool {
otherHasNoAutoStatsSettings := other.NoAutoStatsSettingsOverrides()
if cs.NoAutoStatsSettingsOverrides() {
return otherHasNoAutoStatsSettings
}
if otherHasNoAutoStatsSettings {
return false
}
if cs.AutoStatsCollectionEnabled() != other.AutoStatsCollectionEnabled() {
return false
}
minStaleRows := cs.SqlStatsAutomaticCollectionMinStaleRows
otherMinStaleRows := other.SqlStatsAutomaticCollectionMinStaleRows
if minStaleRows == nil {
if otherMinStaleRows != nil {
return false
}
}
if otherMinStaleRows == nil {
if minStaleRows != nil {
return false
}
}
if *minStaleRows != *otherMinStaleRows {
return false
}

fractionStaleRows := cs.SqlStatsAutomaticCollectionFractionStaleRows
otherFractionStaleRows := other.SqlStatsAutomaticCollectionFractionStaleRows
if fractionStaleRows == nil {
if otherFractionStaleRows != nil {
return false
}
}
if otherFractionStaleRows == nil {
if fractionStaleRows != nil {
return false
}
}
if *fractionStaleRows != *otherFractionStaleRows {
return false
}
return true
}
19 changes: 19 additions & 0 deletions pkg/sql/catalog/catpb/catalog.proto
Original file line number Diff line number Diff line change
Expand Up @@ -217,3 +217,22 @@ message RowLevelTTL {
// the relation name.
optional bool label_metrics = 10 [(gogoproto.nullable) = false];
}

// ClusterSettingsForTable represents cluster settings specified at the table
// level. Each setting is nullable so queries of the descriptor in JSON form
// only list values which have been set.
message ClusterSettingsForTable {
option (gogoproto.equal) = true;
optional bool sql_stats_automatic_collection_enabled = 1;
optional int64 sql_stats_automatic_collection_min_stale_rows = 2;
optional double sql_stats_automatic_collection_fraction_stale_rows = 3;
}

// SessionSettingsForTable represents session settings specified at the table
// level. Each setting is nullable so queries of the descriptor in JSON form
// only list values which have been set.
// This is currently unused, but defined for backwards compatibility in
// anticipation of expected use going forward.
message SessionSettingsForTable {
option (gogoproto.equal) = true;
}
8 changes: 7 additions & 1 deletion pkg/sql/catalog/descpb/structured.proto
Original file line number Diff line number Diff line change
Expand Up @@ -1198,7 +1198,13 @@ message TableDescriptor {
optional uint32 next_constraint_id = 49 [(gogoproto.nullable) = false,
(gogoproto.customname) = "NextConstraintID", (gogoproto.casttype) = "ConstraintID"];

// Next ID: 51
// ClusterSettingsForTable are cluster settings specified at the table level.
optional cockroach.sql.catalog.catpb.ClusterSettingsForTable cluster_settings_for_table = 51 [(gogoproto.customname)="ClusterSettingsForTable"];

// SessionSettingsForTable are session settings specified at the table level.
optional cockroach.sql.catalog.catpb.SessionSettingsForTable session_settings_for_table = 52 [(gogoproto.customname)="SessionSettingsForTable"];

// Next ID: 53
}

// SurvivalGoal is the survival goal for a database.
Expand Down
29 changes: 29 additions & 0 deletions pkg/sql/catalog/descriptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (

"github.com/cockroachdb/cockroach/pkg/keys"
"github.com/cockroachdb/cockroach/pkg/roachpb"
"github.com/cockroachdb/cockroach/pkg/settings/cluster"
"github.com/cockroachdb/cockroach/pkg/sql/catalog/catpb"
"github.com/cockroachdb/cockroach/pkg/sql/catalog/descpb"
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode"
Expand Down Expand Up @@ -678,6 +679,34 @@ type TableDescriptor interface {
GetExcludeDataFromBackup() bool
// GetStorageParams returns a list of storage parameters for the table.
GetStorageParams(spaceBetweenEqual bool) []string
// NoClusterSettingOverrides is true if no cluster settings are set at the
// table level for the given table, meaning no cluster settings are overridden
// for this table.
NoClusterSettingOverrides() bool
// NoAutoStatsSettingsOverrides is true if no auto stats related cluster
// settings are set at the table level for the given table.
NoAutoStatsSettingsOverrides() bool
// AutoStatsCollectionEnabled indicates if automatic statistics collection is
// explicitly enabled or disabled for this table.
AutoStatsCollectionEnabled() cluster.BoolSetting
// AutoStatsMinStaleRows indicates the setting of
// sql.stats.automatic_collection.min_stale_rows for this table.
// If ok is true, then the minStaleRows value is valid, otherwise this has not
// been set at the table level.
AutoStatsMinStaleRows() (minStaleRows int64, ok bool)
// AutoStatsFractionStaleRows indicates the setting of
// sql.stats.automatic_collection.fraction_stale_rows for this table. If ok is
// true, then the fractionStaleRows value is valid, otherwise this has not
// been set at the table level.
AutoStatsFractionStaleRows() (fractionStaleRows float64, ok bool)
// GetClusterSettingsForTable returns the cluster settings specified at
// the table level for this table. May return nil if none are set.
GetClusterSettingsForTable() *catpb.ClusterSettingsForTable
// AutoStatsClusterSettingOverridesEqual returns true if all auto stats
// related cluster setting overrides in this descriptor match those in the
// `otherClusterSettings`. An unset value must also be unset in the other's
// settings to be equal.
AutoStatsClusterSettingOverridesEqual(otherClusterSettings *catpb.ClusterSettingsForTable) bool
}

// TypeDescriptor will eventually be called typedesc.Descriptor.
Expand Down
78 changes: 78 additions & 0 deletions pkg/sql/catalog/tabledesc/structured.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"context"
"fmt"
"sort"
"strconv"
"strings"

"github.com/cockroachdb/cockroach/pkg/clusterversion"
Expand Down Expand Up @@ -2565,6 +2566,24 @@ func (desc *wrapper) GetStorageParams(spaceBetweenEqual bool) []string {
if exclude := desc.GetExcludeDataFromBackup(); exclude {
appendStorageParam(`exclude_data_from_backup`, `true`)
}
if settings := desc.ClusterSettingsForTable; settings != nil {
// These need to be wrapped in double-quotes because they contain '.' chars.
if settings.SqlStatsAutomaticCollectionEnabled != nil {
value := *settings.SqlStatsAutomaticCollectionEnabled
appendStorageParam(fmt.Sprintf(`"%s"`, cluster.AutoStatsEnabledSettingName),
fmt.Sprintf("%v", value))
}
if settings.SqlStatsAutomaticCollectionMinStaleRows != nil {
value := *settings.SqlStatsAutomaticCollectionMinStaleRows
appendStorageParam(fmt.Sprintf(`"%s"`, cluster.AutoStatsMinStaleSettingName),
strconv.FormatInt(value, 10))
}
if settings.SqlStatsAutomaticCollectionFractionStaleRows != nil {
value := *settings.SqlStatsAutomaticCollectionFractionStaleRows
appendStorageParam(fmt.Sprintf(`"%s"`, cluster.AutoStatsFractionStaleSettingName),
fmt.Sprintf("%g", value))
}
}
return storageParams
}

Expand All @@ -2583,6 +2602,65 @@ func (desc *wrapper) GetMultiRegionEnumDependencyIfExists() bool {
return false
}

// NoClusterSettingOverrides implements the TableDescriptor interface.
func (desc *wrapper) NoClusterSettingOverrides() bool {
return desc.ClusterSettingsForTable == nil
}

// NoAutoStatsSettingsOverrides implements the TableDescriptor interface.
func (desc *wrapper) NoAutoStatsSettingsOverrides() bool {
if desc.ClusterSettingsForTable == nil {
return true
}
return desc.ClusterSettingsForTable.NoAutoStatsSettingsOverrides()
}

// AutoStatsCollectionEnabled implements the TableDescriptor interface.
func (desc *wrapper) AutoStatsCollectionEnabled() cluster.BoolSetting {
if desc.NoClusterSettingOverrides() {
return cluster.NotSet
}
return desc.ClusterSettingsForTable.AutoStatsCollectionEnabled()
}

// AutoStatsMinStaleRows implements the TableDescriptor interface.
func (desc *wrapper) AutoStatsMinStaleRows() (minStaleRows int64, ok bool) {
if desc.NoClusterSettingOverrides() {
return 0, false
}
return desc.ClusterSettingsForTable.AutoStatsMinStaleRows()
}

// AutoStatsFractionStaleRows implements the TableDescriptor interface.
func (desc *wrapper) AutoStatsFractionStaleRows() (fractionStaleRows float64, ok bool) {
if desc.NoClusterSettingOverrides() {
return 0, false
}
return desc.ClusterSettingsForTable.AutoStatsFractionStaleRows()
}

// GetClusterSettingsForTable implements the TableDescriptor interface.
func (desc *wrapper) GetClusterSettingsForTable() *catpb.ClusterSettingsForTable {
return desc.ClusterSettingsForTable
}

// AutoStatsClusterSettingOverridesEqual implements the TableDescriptor
// interface.
func (desc *wrapper) AutoStatsClusterSettingOverridesEqual(
otherClusterSettings *catpb.ClusterSettingsForTable,
) bool {
otherHasNoAutoStatsSettings := otherClusterSettings == nil ||
otherClusterSettings.NoAutoStatsSettingsOverrides()

if desc.NoAutoStatsSettingsOverrides() {
return otherHasNoAutoStatsSettings
}
if otherHasNoAutoStatsSettings {
return false
}
return desc.ClusterSettingsForTable.AutoStatsSettingsEqual(otherClusterSettings)
}

// SetTableLocalityRegionalByTable sets the descriptor's locality config to
// regional at the table level in the supplied region. An empty region name
// (or its alias PrimaryRegionNotSpecifiedName) denotes that the table is homed in
Expand Down
Loading

0 comments on commit e6f508f

Please sign in to comment.