Skip to content

Commit af0a3f6

Browse files
committed
sql/opt: add cluster setting sql.stats.canary_fraction
See release note for details. Note that this cluster setting doesn't apply to internal queries. Release note (sql change): introduce the cluster setting `sql.stats.canary_fraction` which takes a float number within range [0, 1]. Its value determines what fraction of queries will use "canary statistics" (newly collected stats within their canary window) versus "stable statistics" (previously proven stats). For example, a value of 0.2 means 20% of queries will test canary stats while 80% use stable stats. The selection is atomic per query: if a query is chosen for canary evaluation, it will use canary statistics for ALL tables it references (where available). A query never uses a mix of canary and stable statistics.
1 parent 0a9de0c commit af0a3f6

File tree

6 files changed

+79
-4
lines changed

6 files changed

+79
-4
lines changed

pkg/sql/opt/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ go_library(
2929
"//pkg/clusterversion",
3030
"//pkg/security/username",
3131
"//pkg/server/telemetry",
32+
"//pkg/settings",
3233
"//pkg/sql/catalog",
3334
"//pkg/sql/catalog/catpb",
3435
"//pkg/sql/catalog/colinfo",

pkg/sql/opt/memo/memo.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ func (m *Memo) Init(ctx context.Context, evalCtx *eval.Context) {
322322
rowSecurity: evalCtx.SessionData().RowSecurity,
323323
txnIsoLevel: evalCtx.TxnIsoLevel,
324324
}
325-
m.metadata.Init()
325+
m.metadata.Init(evalCtx)
326326
m.logPropsBuilder.init(ctx, evalCtx, m)
327327
}
328328

pkg/sql/opt/metadata.go

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ import (
99
"context"
1010
"fmt"
1111
"math/bits"
12+
"math/rand"
1213
"strings"
1314

1415
"github.com/cockroachdb/cockroach/pkg/clusterversion"
1516
"github.com/cockroachdb/cockroach/pkg/security/username"
17+
"github.com/cockroachdb/cockroach/pkg/settings"
1618
"github.com/cockroachdb/cockroach/pkg/sql/catalog"
1719
"github.com/cockroachdb/cockroach/pkg/sql/catalog/multiregion"
1820
"github.com/cockroachdb/cockroach/pkg/sql/catalog/typedesc"
@@ -46,6 +48,44 @@ type routineDep struct {
4648
invocationTypes []*types.T
4749
}
4850

51+
// canaryFraction controls the probabilistic sampling rate for queries
52+
// participating in the canary statistics rollout feature.
53+
//
54+
// This cluster-level setting determines what fraction of queries will use
55+
// "canary statistics" (newly collected stats within their canary window)
56+
// versus "stable statistics" (previously proven stats). For example, a value
57+
// of 0.2 means 20% of queries will test canary stats while 80% use stable stats.
58+
//
59+
// The selection is atomic per query: if a query is chosen for canary evaluation,
60+
// it will use canary statistics for ALL tables it references (where available).
61+
// A query never uses a mix of canary and stable statistics.
62+
var canaryFraction = settings.RegisterFloatSetting(
63+
settings.ApplicationLevel,
64+
"sql.stats.canary_fraction",
65+
"probability that a query will use canary statistics instead of stable statistics (0.0-1.0)",
66+
0.1,
67+
settings.FloatInRange(0, 1),
68+
)
69+
70+
// canaryRollDice performs the probabilistic check to determine if a query
71+
// should use the "canary path" for statistics.
72+
// This selection is atomic per query.
73+
func canaryRollDice(evalCtx *eval.Context) bool {
74+
threshold := canaryFraction.Get(&evalCtx.Settings.SV)
75+
76+
// If the fraction is 0, never use canary stats.
77+
if threshold == 0 {
78+
return false
79+
}
80+
// If the fraction is 1, always use canary stats.
81+
if threshold == 1 {
82+
return true
83+
}
84+
85+
actual := rand.Float64()
86+
return actual < threshold
87+
}
88+
4989
// Metadata assigns unique ids to the columns, tables, and other metadata used
5090
// for global identification within the scope of a particular query. These ids
5191
// tend to be small integers that can be efficiently stored and manipulated.
@@ -153,12 +193,35 @@ type Metadata struct {
153193
depDigest cat.DependencyDigest
154194
}
155195

196+
// useCanaryStats indicates whether this query participates in the canary
197+
// statistics rollout feature. When set to true, the optimizer attempts to use
198+
// "canary statistics" for all tables referenced by the query.
199+
//
200+
// This flag is determined probabilistically during query planning based on the
201+
// sql.stats.canary_fraction cluster setting. The selection is atomic per query:
202+
// either all tables use canary stats (when available) or all use stable stats.
203+
//
204+
// Canary statistics are newly collected table statistics that are still within
205+
// their configured "canary window" (canary_stats_window storage parameter).
206+
// These stats provide a controlled way to gradually roll out new statistics
207+
// before promoting them to stable, allowing for manual intervention if
208+
// performance regressions are detected.
209+
//
210+
// Stable statistics are the previously established statistics that have either
211+
// been promoted from canary status or were collected before canary mode was
212+
// enabled for the table.
213+
//
214+
// Fallback behavior: If a table lacks distinct canary statistics (e.g., only
215+
// one statistics version exists, or canary stats have expired), the optimizer
216+
// will use the available stable statistics even when this flag is true.
217+
useCanaryStats bool
218+
156219
// NOTE! When adding fields here, update Init (if reusing allocated
157220
// data structures is desired), CopyFrom and TestMetadata.
158221
}
159222

160223
// Init prepares the metadata for use (or reuse).
161-
func (md *Metadata) Init() {
224+
func (md *Metadata) Init(evalCtx *eval.Context) {
162225
// Clear the metadata objects to release memory (this clearing pattern is
163226
// optimized by Go).
164227
schemas := md.schemas
@@ -239,6 +302,11 @@ func (md *Metadata) Init() {
239302
md.objectRefsByName = objectRefsByName
240303
md.privileges = privileges
241304
md.builtinRefsByName = builtinRefsByName
305+
// TODO(janexing): figure out if this gate can overkill. Can any routines
306+
// or builtin functions access tables as an internal query?
307+
if !evalCtx.SessionData().Internal {
308+
md.useCanaryStats = canaryRollDice(evalCtx)
309+
}
242310
}
243311

244312
// CopyFrom initializes the metadata with a copy of the provided metadata.
@@ -337,6 +405,7 @@ func (md *Metadata) CopyFrom(from *Metadata, copyScalarFn func(Expr) Expr) {
337405
md.withBindings = nil
338406

339407
md.rlsMeta = from.rlsMeta.Copy()
408+
md.useCanaryStats = from.useCanaryStats
340409
}
341410

342411
// MDDepName stores either the unresolved DataSourceName or the StableID from

pkg/sql/opt/metadata_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ func TestMetadata(t *testing.T) {
6363
},
6464
}
6565

66-
md.Init()
66+
md.Init(&evalCtx)
6767
if md.AddSchema(testCat.Schema()) != schID {
6868
t.Fatalf("unexpected schema id")
6969
}

pkg/sql/opt/testutils/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ go_test(
2929
srcs = ["scalar_vars_test.go"],
3030
embed = [":testutils"],
3131
deps = [
32+
"//pkg/settings/cluster",
3233
"//pkg/sql/opt",
34+
"//pkg/sql/sem/eval",
3335
"@com_github_stretchr_testify//assert",
3436
],
3537
)

pkg/sql/opt/testutils/scalar_vars_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ import (
1111
"strings"
1212
"testing"
1313

14+
"github.com/cockroachdb/cockroach/pkg/settings/cluster"
1415
"github.com/cockroachdb/cockroach/pkg/sql/opt"
16+
"github.com/cockroachdb/cockroach/pkg/sql/sem/eval"
1517
"github.com/stretchr/testify/assert"
1618
)
1719

@@ -37,7 +39,8 @@ func TestScalarVars(t *testing.T) {
3739
}
3840

3941
vars := "a int, b string not null, c decimal"
40-
md.Init()
42+
evalCtx := eval.MakeTestingEvalContext(cluster.MakeTestingClusterSettings())
43+
md.Init(&evalCtx)
4144
if err := sv.Init(&md, strings.Split(vars, ", ")); err != nil {
4245
t.Fatal(err)
4346
}

0 commit comments

Comments
 (0)