Skip to content

Commit e0df19f

Browse files
craig[bot]Uzair5162
andcommitted
Merge #154370
154370: sql: log when optimizer estimates for scans are inaccurate r=Uzair5162 a=Uzair5162 #### sql: estimate table statistic staleness in stats.Refresher This commit adds an `EstimateStaleness()` method to the table statistic `Refresher`, which estimates the current fraction of stale rows in a given table with the formula: `cur_fraction_stale = (time_since_last_refresh / avg_time_between_refreshes) * target_fraction_stale_rows` Although this isn’t used anywhere yet, it will be useful for logging when scans are misestimated (see #153748). Part of: #153748, #153873 Release note: None #### sql: plumb StageID into table reader processors This commit plumbs `StageID` from the general `ProcessorSpec` into table reader processors. Specifically, `StageID` is plumbed into `tableReader` for the row-based flow and `colBatchScanBase` (via `ColBatchScan` and `ColBatchDirectScan`) for the vectorized flow. Although `stageID` isn't used in these processors yet, it will be useful for aggregating row counts from metrics across distributed table readers for misestimate logging. Part of: #153748 Release note: None #### sql: log when optimizer estimates for scans are inaccurate This commit logs a warning on the gateway node when the estimated row count for a logical scan is inaccurate. The `DistSQLReceiver` on the gateway node now maintains the row count estimate and metadata for each logical scan stage. Table reader metrics are extended to include their StageID, which we use to aggregate the emitted row counts from all table readers processors corresponding to the same plan stage at the receiver. An estimate is considered inaccurate if it is off by at least a factor of 2 and a fixed offset of 100, matching the logic in the warning from `EXPLAIN ANALYZE`. The log message includes the table and index being scanned, the estimated and actual row counts, the time since the last table stats collection, and the table's estimated staleness. This log is gated behind a new cluster setting, `sql.log.scan_row_count_misestimate.enabled` (default off). Logging only happens for user tables and is rate limited to log misestimates from at most 1 query every 10 seconds. Fixes: #153748 Fixes: #153873 Release note (sql change): Added a default-off cluster setting (`sql.log.scan_row_count_misestimate.enabled`) that enables logging a warning on the gateway node when optimizer estimates for scans are inaccurate. The log message includes the table and index being scanned, the estimated and actual row counts, the time since the last table stats collection, and the table's estimated staleness. Co-authored-by: Uzair Ahmad <uzair.ahmad@cockroachlabs.com>
2 parents cb76195 + 25f5d44 commit e0df19f

30 files changed

+492
-43
lines changed

docs/generated/settings/settings-for-tenants.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,7 @@ sql.insights.execution_insights_capacity integer 1000 the size of the per-node s
325325
sql.insights.high_retry_count.threshold integer 10 the number of retries a slow statement must have undergone for its high retry count to be highlighted as a potential problem application
326326
sql.insights.latency_threshold duration 100ms amount of time after which an executing statement is considered slow. Use 0 to disable. application
327327
sql.log.redact_names.enabled boolean false if set, schema object identifers are redacted in SQL statements that appear in event logs application
328+
sql.log.scan_row_count_misestimate.enabled boolean false when set to true, log a warning when a scan's actual row count differs significantly from the optimizer's estimate application
328329
sql.log.slow_query.experimental_full_table_scans.enabled boolean false when set to true, statements that perform a full table/index scan will be logged to the slow query log even if they do not meet the latency threshold. Must have the slow query log enabled for this setting to have any effect. application
329330
sql.log.slow_query.internal_queries.enabled boolean false when set to true, internal queries which exceed the slow query log threshold are logged to a separate log. Must have the slow query log enabled for this setting to have any effect. application
330331
sql.log.slow_query.latency_threshold duration 0s when set to non-zero, log statements whose service latency exceeds the threshold to a secondary logger on each node application

docs/generated/settings/settings.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,7 @@
280280
<tr><td><div id="setting-sql-insights-high-retry-count-threshold" class="anchored"><code>sql.insights.high_retry_count.threshold</code></div></td><td>integer</td><td><code>10</code></td><td>the number of retries a slow statement must have undergone for its high retry count to be highlighted as a potential problem</td><td>Basic/Standard/Advanced/Self-Hosted</td></tr>
281281
<tr><td><div id="setting-sql-insights-latency-threshold" class="anchored"><code>sql.insights.latency_threshold</code></div></td><td>duration</td><td><code>100ms</code></td><td>amount of time after which an executing statement is considered slow. Use 0 to disable.</td><td>Basic/Standard/Advanced/Self-Hosted</td></tr>
282282
<tr><td><div id="setting-sql-log-redact-names-enabled" class="anchored"><code>sql.log.redact_names.enabled</code></div></td><td>boolean</td><td><code>false</code></td><td>if set, schema object identifers are redacted in SQL statements that appear in event logs</td><td>Basic/Standard/Advanced/Self-Hosted</td></tr>
283+
<tr><td><div id="setting-sql-log-scan-row-count-misestimate-enabled" class="anchored"><code>sql.log.scan_row_count_misestimate.enabled</code></div></td><td>boolean</td><td><code>false</code></td><td>when set to true, log a warning when a scan&#39;s actual row count differs significantly from the optimizer&#39;s estimate</td><td>Basic/Standard/Advanced/Self-Hosted</td></tr>
283284
<tr><td><div id="setting-sql-log-slow-query-experimental-full-table-scans-enabled" class="anchored"><code>sql.log.slow_query.experimental_full_table_scans.enabled</code></div></td><td>boolean</td><td><code>false</code></td><td>when set to true, statements that perform a full table/index scan will be logged to the slow query log even if they do not meet the latency threshold. Must have the slow query log enabled for this setting to have any effect.</td><td>Basic/Standard/Advanced/Self-Hosted</td></tr>
284285
<tr><td><div id="setting-sql-log-slow-query-internal-queries-enabled" class="anchored"><code>sql.log.slow_query.internal_queries.enabled</code></div></td><td>boolean</td><td><code>false</code></td><td>when set to true, internal queries which exceed the slow query log threshold are logged to a separate log. Must have the slow query log enabled for this setting to have any effect.</td><td>Basic/Standard/Advanced/Self-Hosted</td></tr>
285286
<tr><td><div id="setting-sql-log-slow-query-latency-threshold" class="anchored"><code>sql.log.slow_query.latency_threshold</code></div></td><td>duration</td><td><code>0s</code></td><td>when set to non-zero, log statements whose service latency exceeds the threshold to a secondary logger on each node</td><td>Basic/Standard/Advanced/Self-Hosted</td></tr>

pkg/sql/colexec/colbuilder/execplan.go

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,7 @@ func (r opResult) createAndWrapRowSource(
566566
core *execinfrapb.ProcessorCoreUnion,
567567
post *execinfrapb.PostProcessSpec,
568568
processorID int32,
569+
stageID int32,
569570
factory coldata.ColumnFactory,
570571
causeToWrap error,
571572
) error {
@@ -592,7 +593,7 @@ func (r opResult) createAndWrapRowSource(
592593
// here because when wrapping the processor, the materializer will
593594
// be its output, and it will be set up in wrapRowSources.
594595
proc, err := args.ProcessorConstructor(
595-
ctx, flowCtx, processorID, core, post, inputs, args.LocalProcessors,
596+
ctx, flowCtx, processorID, stageID, core, post, inputs, args.LocalProcessors,
596597
)
597598
if err != nil {
598599
return nil, err
@@ -819,7 +820,7 @@ func NewColOperator(
819820
post = &newPosts[1]
820821
err = result.createAndWrapRowSource(
821822
ctx, flowCtx, args, inputs, inputTypes, core,
822-
wrappingPost, spec.ProcessorID, factory, err,
823+
wrappingPost, spec.ProcessorID, spec.StageID, factory, err,
823824
)
824825
} else {
825826
switch {
@@ -969,7 +970,8 @@ func NewColOperator(
969970
if canUseDirectScan() {
970971
scanOp, resultTypes, err = colfetcher.NewColBatchDirectScan(
971972
ctx, colmem.NewAllocator(ctx, accounts[0], factory), accounts[1],
972-
flowCtx, spec.ProcessorID, core.TableReader, post, args.TypeResolver,
973+
flowCtx, spec.ProcessorID, spec.StageID, core.TableReader, post,
974+
args.TypeResolver,
973975
)
974976
if err != nil {
975977
return r, err
@@ -979,7 +981,8 @@ func NewColOperator(
979981
if scanOp == nil {
980982
scanOp, resultTypes, err = colfetcher.NewColBatchScan(
981983
ctx, colmem.NewAllocator(ctx, accounts[0], factory), accounts[1],
982-
flowCtx, spec.ProcessorID, core.TableReader, post, estimatedRowCount, args.TypeResolver,
984+
flowCtx, spec.ProcessorID, spec.StageID, core.TableReader, post,
985+
estimatedRowCount, args.TypeResolver,
983986
)
984987
if err != nil {
985988
return r, err
@@ -1027,7 +1030,7 @@ func NewColOperator(
10271030
result.ColumnTypes = spec.Input[0].ColumnTypes
10281031
result.Root = inputs[0].Root
10291032
if err := result.planAndMaybeWrapFilter(
1030-
ctx, flowCtx, args, spec.ProcessorID, core.Filterer.Filter, factory,
1033+
ctx, flowCtx, args, spec.ProcessorID, spec.StageID, core.Filterer.Filter, factory,
10311034
); err != nil {
10321035
return r, err
10331036
}
@@ -1309,7 +1312,7 @@ func NewColOperator(
13091312

13101313
if !core.HashJoiner.OnExpr.Empty() && core.HashJoiner.Type == descpb.InnerJoin {
13111314
if err = result.planAndMaybeWrapFilter(
1312-
ctx, flowCtx, args, spec.ProcessorID, core.HashJoiner.OnExpr, factory,
1315+
ctx, flowCtx, args, spec.ProcessorID, spec.StageID, core.HashJoiner.OnExpr, factory,
13131316
); err != nil {
13141317
return r, err
13151318
}
@@ -1354,7 +1357,7 @@ func NewColOperator(
13541357

13551358
if onExpr != nil {
13561359
if err = result.planAndMaybeWrapFilter(
1357-
ctx, flowCtx, args, spec.ProcessorID, *onExpr, factory,
1360+
ctx, flowCtx, args, spec.ProcessorID, spec.StageID, *onExpr, factory,
13581361
); err != nil {
13591362
return r, err
13601363
}
@@ -1797,7 +1800,8 @@ func NewColOperator(
17971800
}
17981801
err = ppr.planPostProcessSpec(ctx, flowCtx, args, post, factory, &r.Releasables, args.Spec.EstimatedRowCount)
17991802
if err != nil {
1800-
err = result.wrapPostProcessSpec(ctx, flowCtx, args, post, spec.ProcessorID, factory, err)
1803+
err = result.wrapPostProcessSpec(ctx, flowCtx, args, post,
1804+
spec.ProcessorID, spec.StageID, factory, err)
18011805
} else {
18021806
// The result can be updated with the post process result.
18031807
r.Root = ppr.Op
@@ -1845,7 +1849,8 @@ func NewColOperator(
18451849
post.RenderExprs[i].LocalExpr = tree.NewTypedOrdinalReference(i, args.Spec.ResultTypes[i])
18461850
}
18471851
}
1848-
if err = result.wrapPostProcessSpec(ctx, flowCtx, args, post, spec.ProcessorID, factory, errWrappedCast); err != nil {
1852+
if err = result.wrapPostProcessSpec(ctx, flowCtx, args, post,
1853+
spec.ProcessorID, spec.StageID, factory, errWrappedCast); err != nil {
18491854
return r, err
18501855
}
18511856
} else if numMismatchedTypes > 0 {
@@ -1909,6 +1914,7 @@ func (r opResult) planAndMaybeWrapFilter(
19091914
flowCtx *execinfra.FlowCtx,
19101915
args *colexecargs.NewColOperatorArgs,
19111916
processorID int32,
1917+
stageID int32,
19121918
filter execinfrapb.Expression,
19131919
factory coldata.ColumnFactory,
19141920
) error {
@@ -1928,7 +1934,7 @@ func (r opResult) planAndMaybeWrapFilter(
19281934
return r.createAndWrapRowSource(
19291935
ctx, flowCtx, args, []colexecargs.OpWithMetaInfo{inputToMaterializer},
19301936
[][]*types.T{r.ColumnTypes}, filtererCore, &execinfrapb.PostProcessSpec{},
1931-
processorID, factory, err,
1937+
processorID, stageID, factory, err,
19321938
)
19331939
}
19341940
return nil
@@ -1945,6 +1951,7 @@ func (r opResult) wrapPostProcessSpec(
19451951
args *colexecargs.NewColOperatorArgs,
19461952
post *execinfrapb.PostProcessSpec,
19471953
processorID int32,
1954+
stageID int32,
19481955
factory coldata.ColumnFactory,
19491956
causeToWrap error,
19501957
) error {
@@ -1956,7 +1963,8 @@ func (r opResult) wrapPostProcessSpec(
19561963
// createAndWrapRowSource updates r.ColumnTypes accordingly.
19571964
return r.createAndWrapRowSource(
19581965
ctx, flowCtx, args, []colexecargs.OpWithMetaInfo{inputToMaterializer},
1959-
[][]*types.T{r.ColumnTypes}, noopCore, post, processorID, factory, causeToWrap,
1966+
[][]*types.T{r.ColumnTypes}, noopCore, post, processorID, stageID, factory,
1967+
causeToWrap,
19601968
)
19611969
}
19621970

pkg/sql/colexec/values_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,8 @@ func BenchmarkValues(b *testing.B) {
150150
var core execinfrapb.ProcessorCoreUnion
151151
core.Values = spec
152152
proc, err := rowexec.NewProcessor(
153-
ctx, &flowCtx, 0 /* processorID */, &core, &post, nil /* inputs */, nil, /* localProcessors */
153+
ctx, &flowCtx, 0 /* processorID */, 0 /* stageID */, &core, &post,
154+
nil /* inputs */, nil, /* localProcessors */
154155
)
155156
if err != nil {
156157
b.Fatal(err)

pkg/sql/colfetcher/colbatch_direct_scan.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ func (s *ColBatchDirectScan) DrainMeta() []execinfrapb.ProducerMetadata {
127127
meta.Metrics = execinfrapb.GetMetricsMeta()
128128
meta.Metrics.BytesRead = s.GetBytesRead()
129129
meta.Metrics.RowsRead = s.GetRowsRead()
130+
meta.Metrics.StageID = s.stageID
130131
trailingMeta = append(trailingMeta, *meta)
131132
return trailingMeta
132133
}
@@ -181,12 +182,13 @@ func NewColBatchDirectScan(
181182
kvFetcherMemAcc *mon.BoundAccount,
182183
flowCtx *execinfra.FlowCtx,
183184
processorID int32,
185+
stageID int32,
184186
spec *execinfrapb.TableReaderSpec,
185187
post *execinfrapb.PostProcessSpec,
186188
typeResolver *descs.DistSQLTypeResolver,
187189
) (*ColBatchDirectScan, []*types.T, error) {
188190
base, bsHeader, tableArgs, err := newColBatchScanBase(
189-
ctx, kvFetcherMemAcc, flowCtx, processorID, spec, post, typeResolver,
191+
ctx, kvFetcherMemAcc, flowCtx, processorID, stageID, spec, post, typeResolver,
190192
)
191193
if err != nil {
192194
return nil, nil, err

pkg/sql/colfetcher/colbatch_scan.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ type colBatchScanBase struct {
3838

3939
flowCtx *execinfra.FlowCtx
4040
processorID int32
41+
stageID int32
4142
limitHint rowinfra.RowLimit
4243
batchBytesLimit rowinfra.BytesLimit
4344
parallelize bool
@@ -122,6 +123,7 @@ func newColBatchScanBase(
122123
kvFetcherMemAcc *mon.BoundAccount,
123124
flowCtx *execinfra.FlowCtx,
124125
processorID int32,
126+
stageID int32,
125127
spec *execinfrapb.TableReaderSpec,
126128
post *execinfrapb.PostProcessSpec,
127129
typeResolver *descs.DistSQLTypeResolver,
@@ -186,6 +188,7 @@ func newColBatchScanBase(
186188
SpansWithCopy: s.SpansWithCopy,
187189
flowCtx: flowCtx,
188190
processorID: processorID,
191+
stageID: stageID,
189192
limitHint: limitHint,
190193
batchBytesLimit: batchBytesLimit,
191194
parallelize: spec.Parallelize,
@@ -254,6 +257,7 @@ func (s *ColBatchScan) DrainMeta() []execinfrapb.ProducerMetadata {
254257
meta.Metrics = execinfrapb.GetMetricsMeta()
255258
meta.Metrics.BytesRead = s.GetBytesRead()
256259
meta.Metrics.RowsRead = s.GetRowsRead()
260+
meta.Metrics.StageID = s.stageID
257261
trailingMeta = append(trailingMeta, *meta)
258262
return trailingMeta
259263
}
@@ -312,13 +316,14 @@ func NewColBatchScan(
312316
kvFetcherMemAcc *mon.BoundAccount,
313317
flowCtx *execinfra.FlowCtx,
314318
processorID int32,
319+
stageID int32,
315320
spec *execinfrapb.TableReaderSpec,
316321
post *execinfrapb.PostProcessSpec,
317322
estimatedRowCount uint64,
318323
typeResolver *descs.DistSQLTypeResolver,
319324
) (*ColBatchScan, []*types.T, error) {
320325
base, bsHeader, tableArgs, err := newColBatchScanBase(
321-
ctx, kvFetcherMemAcc, flowCtx, processorID, spec, post, typeResolver,
326+
ctx, kvFetcherMemAcc, flowCtx, processorID, stageID, spec, post, typeResolver,
322327
)
323328
if err != nil {
324329
return nil, nil, err

pkg/sql/conn_executor_exec.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3360,6 +3360,10 @@ func (ex *connExecutor) execWithDistSQLEngine(
33603360
}
33613361
err = ex.server.cfg.DistSQLPlanner.PlanAndRunAll(ctx, evalCtx, planCtx, planner, recv, evalCtxFactory)
33623362
}
3363+
3364+
if err == nil && res.Err() == nil {
3365+
recv.maybeLogMisestimates(ctx, planner)
3366+
}
33633367
return recv.stats, err
33643368
}
33653369

pkg/sql/distsql/columnar_utils_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ func verifyColOperator(t *testing.T, args verifyColOperatorArgs) error {
115115
}
116116

117117
proc, err := rowexec.NewProcessor(
118-
ctx, flowCtx, 0, &args.pspec.Core, &args.pspec.Post, inputsProc, nil,
118+
ctx, flowCtx, 0, 0, &args.pspec.Core, &args.pspec.Post, inputsProc, nil,
119119
)
120120
if err != nil {
121121
return err

pkg/sql/distsql_physical_planner.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"fmt"
1212
"reflect"
1313
"sort"
14+
"time"
1415

1516
"github.com/cockroachdb/cockroach/pkg/base"
1617
"github.com/cockroachdb/cockroach/pkg/cloud"
@@ -2245,6 +2246,7 @@ func (dsp *DistSQLPlanner) createTableReaders(
22452246
reverse: n.reverse,
22462247
parallelize: n.parallelize,
22472248
estimatedRowCount: n.estimatedRowCount,
2249+
statsCreatedAt: n.statsCreatedAt,
22482250
reqOrdering: n.reqOrdering,
22492251
finalizeLastStageCb: planCtx.associateWithPlanNode(n),
22502252
},
@@ -2263,6 +2265,7 @@ type tableReaderPlanningInfo struct {
22632265
reverse bool
22642266
parallelize bool
22652267
estimatedRowCount uint64
2268+
statsCreatedAt time.Time
22662269
reqOrdering ReqOrdering
22672270
finalizeLastStageCb func(*physicalplan.PhysicalPlan) // will be nil in the spec factory
22682271
}
@@ -2498,6 +2501,7 @@ func (dsp *DistSQLPlanner) planTableReaders(
24982501

24992502
corePlacement[i].SQLInstanceID = sp.SQLInstanceID
25002503
corePlacement[i].EstimatedRowCount = info.estimatedRowCount
2504+
corePlacement[i].StatsCreatedAt = info.statsCreatedAt
25012505
corePlacement[i].Core.TableReader = tr
25022506
}
25032507

0 commit comments

Comments
 (0)