github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/create_stats.go (about) 1 // Copyright 2017 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package sql 12 13 import ( 14 "context" 15 "fmt" 16 17 "github.com/cockroachdb/cockroach/pkg/jobs" 18 "github.com/cockroachdb/cockroach/pkg/jobs/jobspb" 19 "github.com/cockroachdb/cockroach/pkg/kv" 20 "github.com/cockroachdb/cockroach/pkg/server/telemetry" 21 "github.com/cockroachdb/cockroach/pkg/settings" 22 "github.com/cockroachdb/cockroach/pkg/settings/cluster" 23 "github.com/cockroachdb/cockroach/pkg/sql/catalog/resolver" 24 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode" 25 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror" 26 "github.com/cockroachdb/cockroach/pkg/sql/privilege" 27 "github.com/cockroachdb/cockroach/pkg/sql/rowcontainer" 28 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 29 "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" 30 "github.com/cockroachdb/cockroach/pkg/sql/sqltelemetry" 31 "github.com/cockroachdb/cockroach/pkg/sql/stats" 32 "github.com/cockroachdb/cockroach/pkg/sql/types" 33 "github.com/cockroachdb/cockroach/pkg/util" 34 "github.com/cockroachdb/cockroach/pkg/util/errorutil/unimplemented" 35 "github.com/cockroachdb/cockroach/pkg/util/hlc" 36 "github.com/cockroachdb/cockroach/pkg/util/log" 37 "github.com/cockroachdb/errors" 38 "google.golang.org/grpc/codes" 39 "google.golang.org/grpc/status" 40 ) 41 42 // createStatsPostEvents controls the cluster setting for logging 43 // automatic table statistics collection to the event log. 44 var createStatsPostEvents = settings.RegisterPublicBoolSetting( 45 "sql.stats.post_events.enabled", 46 "if set, an event is logged for every CREATE STATISTICS job", 47 false, 48 ) 49 50 func (p *planner) CreateStatistics(ctx context.Context, n *tree.CreateStats) (planNode, error) { 51 return &createStatsNode{ 52 CreateStats: *n, 53 p: p, 54 }, nil 55 } 56 57 // createStatsNode is a planNode implemented in terms of a function. The 58 // startJob function starts a Job during Start, and the remainder of the 59 // CREATE STATISTICS planning and execution is performed within the jobs 60 // framework. 61 type createStatsNode struct { 62 tree.CreateStats 63 p *planner 64 65 run createStatsRun 66 } 67 68 // createStatsRun contains the run-time state of createStatsNode during local 69 // execution. 70 type createStatsRun struct { 71 resultsCh chan tree.Datums 72 errCh chan error 73 } 74 75 func (n *createStatsNode) startExec(params runParams) error { 76 telemetry.Inc(sqltelemetry.SchemaChangeCreateCounter("stats")) 77 n.run.resultsCh = make(chan tree.Datums) 78 n.run.errCh = make(chan error) 79 go func() { 80 err := n.startJob(params.ctx, n.run.resultsCh) 81 select { 82 case <-params.ctx.Done(): 83 case n.run.errCh <- err: 84 } 85 close(n.run.errCh) 86 close(n.run.resultsCh) 87 }() 88 return nil 89 } 90 91 func (n *createStatsNode) Next(params runParams) (bool, error) { 92 select { 93 case <-params.ctx.Done(): 94 return false, params.ctx.Err() 95 case err := <-n.run.errCh: 96 return false, err 97 case <-n.run.resultsCh: 98 return true, nil 99 } 100 } 101 102 func (*createStatsNode) Close(context.Context) {} 103 func (*createStatsNode) Values() tree.Datums { return nil } 104 105 // startJob starts a CreateStats job to plan and execute statistics creation. 106 func (n *createStatsNode) startJob(ctx context.Context, resultsCh chan<- tree.Datums) error { 107 record, err := n.makeJobRecord(ctx) 108 if err != nil { 109 return err 110 } 111 112 if n.Name == stats.AutoStatsName { 113 // Don't start the job if there is already a CREATE STATISTICS job running. 114 // (To handle race conditions we check this again after the job starts, 115 // but this check is used to prevent creating a large number of jobs that 116 // immediately fail). 117 if err := checkRunningJobs(ctx, nil /* job */, n.p); err != nil { 118 return err 119 } 120 } else { 121 telemetry.Inc(sqltelemetry.CreateStatisticsUseCounter) 122 } 123 124 job, errCh, err := n.p.ExecCfg().JobRegistry.CreateAndStartJob(ctx, resultsCh, *record) 125 if err != nil { 126 return err 127 } 128 129 if err = <-errCh; err != nil { 130 if errors.Is(err, stats.ConcurrentCreateStatsError) { 131 // Delete the job so users don't see it and get confused by the error. 132 const stmt = `DELETE FROM system.jobs WHERE id = $1` 133 if _ /* cols */, delErr := n.p.ExecCfg().InternalExecutor.Exec( 134 ctx, "delete-job", nil /* txn */, stmt, *job.ID(), 135 ); delErr != nil { 136 log.Warningf(ctx, "failed to delete job: %v", delErr) 137 } 138 } 139 } 140 return err 141 } 142 143 // makeJobRecord creates a CreateStats job record which can be used to plan and 144 // execute statistics creation. 145 func (n *createStatsNode) makeJobRecord(ctx context.Context) (*jobs.Record, error) { 146 var tableDesc *ImmutableTableDescriptor 147 var fqTableName string 148 var err error 149 switch t := n.Table.(type) { 150 case *tree.UnresolvedObjectName: 151 tableDesc, err = n.p.ResolveExistingObjectEx(ctx, t, true /*required*/, resolver.ResolveRequireTableDesc) 152 if err != nil { 153 return nil, err 154 } 155 fqTableName = n.p.ResolvedName(t).FQString() 156 157 case *tree.TableRef: 158 flags := tree.ObjectLookupFlags{CommonLookupFlags: tree.CommonLookupFlags{ 159 AvoidCached: n.p.avoidCachedDescriptors, 160 }} 161 tableDesc, err = n.p.Tables().GetTableVersionByID(ctx, n.p.txn, sqlbase.ID(t.TableID), flags) 162 if err != nil { 163 return nil, err 164 } 165 fqTableName, err = n.p.getQualifiedTableName(ctx, &tableDesc.TableDescriptor) 166 if err != nil { 167 return nil, err 168 } 169 } 170 171 if tableDesc.IsVirtualTable() { 172 return nil, pgerror.New( 173 pgcode.WrongObjectType, "cannot create statistics on virtual tables", 174 ) 175 } 176 177 if tableDesc.IsView() { 178 return nil, pgerror.New( 179 pgcode.WrongObjectType, "cannot create statistics on views", 180 ) 181 } 182 183 if err := n.p.CheckPrivilege(ctx, tableDesc, privilege.SELECT); err != nil { 184 return nil, err 185 } 186 187 // Identify which columns we should create statistics for. 188 var colStats []jobspb.CreateStatsDetails_ColStat 189 if len(n.ColumnNames) == 0 { 190 multiColEnabled := stats.MultiColumnStatisticsClusterMode.Get(&n.p.ExecCfg().Settings.SV) 191 if colStats, err = createStatsDefaultColumns(tableDesc, multiColEnabled); err != nil { 192 return nil, err 193 } 194 } else { 195 columns, err := tableDesc.FindActiveColumnsByNames(n.ColumnNames) 196 if err != nil { 197 return nil, err 198 } 199 200 columnIDs := make([]sqlbase.ColumnID, len(columns)) 201 for i := range columns { 202 if columns[i].Type.Family() == types.JsonFamily { 203 return nil, unimplemented.NewWithIssuef(35844, 204 "CREATE STATISTICS is not supported for JSON columns") 205 } 206 columnIDs[i] = columns[i].ID 207 } 208 colStats = []jobspb.CreateStatsDetails_ColStat{{ColumnIDs: columnIDs, HasHistogram: false}} 209 if len(columnIDs) == 1 && columns[0].Type.Family() != types.ArrayFamily { 210 // By default, create histograms on all explicitly requested column stats 211 // with a single column. (We cannot create histograms on array columns 212 // because we do not support key encoding arrays.) 213 colStats[0].HasHistogram = true 214 } 215 } 216 217 // Evaluate the AS OF time, if any. 218 var asOf *hlc.Timestamp 219 if n.Options.AsOf.Expr != nil { 220 asOfTs, err := n.p.EvalAsOfTimestamp(ctx, n.Options.AsOf) 221 if err != nil { 222 return nil, err 223 } 224 asOf = &asOfTs 225 } 226 227 // Create a job to run statistics creation. 228 statement := tree.AsStringWithFQNames(n, n.p.EvalContext().Annotations) 229 var description string 230 if n.Name == stats.AutoStatsName { 231 // Use a user-friendly description for automatic statistics. 232 description = fmt.Sprintf("Table statistics refresh for %s", fqTableName) 233 } else { 234 // This must be a user query, so use the statement (for consistency with 235 // other jobs triggered by statements). 236 description = statement 237 statement = "" 238 } 239 return &jobs.Record{ 240 Description: description, 241 Statement: statement, 242 Username: n.p.User(), 243 Details: jobspb.CreateStatsDetails{ 244 Name: string(n.Name), 245 FQTableName: fqTableName, 246 Table: tableDesc.TableDescriptor, 247 ColumnStats: colStats, 248 Statement: n.String(), 249 AsOf: asOf, 250 MaxFractionIdle: n.Options.Throttling, 251 }, 252 Progress: jobspb.CreateStatsProgress{}, 253 }, nil 254 } 255 256 // maxNonIndexCols is the maximum number of non-index columns that we will use 257 // when choosing a default set of column statistics. 258 const maxNonIndexCols = 100 259 260 // createStatsDefaultColumns creates column statistics on a default set of 261 // column lists when no columns were specified by the caller. 262 // 263 // To determine a useful set of default column statistics, we rely on 264 // information provided by the schema. In particular, the presence of an index 265 // on a particular set of columns indicates that the workload likely contains 266 // queries that involve those columns (e.g., for filters), and it would be 267 // useful to have statistics on prefixes of those columns. For example, if a 268 // table abc contains indexes on (a ASC, b ASC) and (b ASC, c ASC), we will 269 // collect statistics on a, {a, b}, b, and {b, c}. (But if multiColEnabled is 270 // false, we will only collect stats on a and b). 271 // 272 // In addition to the index columns, we collect stats on up to maxNonIndexCols 273 // other columns from the table. We only collect histograms for index columns, 274 // plus any other boolean columns (where the "histogram" is tiny). 275 func createStatsDefaultColumns( 276 desc *ImmutableTableDescriptor, multiColEnabled bool, 277 ) ([]jobspb.CreateStatsDetails_ColStat, error) { 278 colStats := make([]jobspb.CreateStatsDetails_ColStat, 0, len(desc.Indexes)+1) 279 280 requestedStats := make(map[string]struct{}) 281 282 // Add column stats for the primary key. 283 for i := range desc.PrimaryIndex.ColumnIDs { 284 if i != 0 && !multiColEnabled { 285 break 286 } 287 288 // Remember the requested stats so we don't request duplicates. 289 key := makeColStatKey(desc.PrimaryIndex.ColumnIDs[: i+1 : i+1]) 290 requestedStats[key] = struct{}{} 291 292 colStats = append(colStats, jobspb.CreateStatsDetails_ColStat{ 293 ColumnIDs: desc.PrimaryIndex.ColumnIDs[: i+1 : i+1], 294 HasHistogram: i == 0, 295 }) 296 } 297 298 // Add column stats for each secondary index. 299 for i := range desc.Indexes { 300 if desc.Indexes[i].Type == sqlbase.IndexDescriptor_INVERTED { 301 // We don't yet support stats on inverted indexes. 302 continue 303 } 304 for j := range desc.Indexes[i].ColumnIDs { 305 if j != 0 && !multiColEnabled { 306 break 307 } 308 309 // Check for existing stats and remember the requested stats. 310 key := makeColStatKey(desc.Indexes[i].ColumnIDs[: j+1 : j+1]) 311 if _, ok := requestedStats[key]; ok { 312 continue 313 } 314 requestedStats[key] = struct{}{} 315 316 colStats = append(colStats, jobspb.CreateStatsDetails_ColStat{ 317 ColumnIDs: desc.Indexes[i].ColumnIDs[: j+1 : j+1], 318 HasHistogram: j == 0, 319 }) 320 } 321 } 322 323 // Add all remaining non-json columns in the table, up to maxNonIndexCols. 324 nonIdxCols := 0 325 for i := 0; i < len(desc.Columns) && nonIdxCols < maxNonIndexCols; i++ { 326 col := &desc.Columns[i] 327 colList := []sqlbase.ColumnID{col.ID} 328 key := makeColStatKey(colList) 329 if _, ok := requestedStats[key]; !ok && col.Type.Family() != types.JsonFamily { 330 colStats = append(colStats, jobspb.CreateStatsDetails_ColStat{ 331 ColumnIDs: colList, 332 HasHistogram: col.Type.Family() == types.BoolFamily || col.Type.Family() == types.EnumFamily, 333 }) 334 nonIdxCols++ 335 } 336 } 337 338 return colStats, nil 339 } 340 341 // makeColStatKey constructs a unique key representing cols that can be used 342 // as the key in a map. 343 func makeColStatKey(cols []sqlbase.ColumnID) string { 344 var colSet util.FastIntSet 345 for _, c := range cols { 346 colSet.Add(int(c)) 347 } 348 return colSet.String() 349 } 350 351 // newPlanForExplainDistSQL is part of the distSQLExplainable interface. 352 func (n *createStatsNode) newPlanForExplainDistSQL( 353 planCtx *PlanningCtx, distSQLPlanner *DistSQLPlanner, 354 ) (*PhysicalPlan, error) { 355 // Create a job record but don't actually start the job. 356 record, err := n.makeJobRecord(planCtx.ctx) 357 if err != nil { 358 return nil, err 359 } 360 job := n.p.ExecCfg().JobRegistry.NewJob(*record) 361 362 return distSQLPlanner.createPlanForCreateStats(planCtx, job) 363 } 364 365 // createStatsResumer implements the jobs.Resumer interface for CreateStats 366 // jobs. A new instance is created for each job. 367 type createStatsResumer struct { 368 job *jobs.Job 369 tableID sqlbase.ID 370 } 371 372 var _ jobs.Resumer = &createStatsResumer{} 373 374 // Resume is part of the jobs.Resumer interface. 375 func (r *createStatsResumer) Resume( 376 ctx context.Context, phs interface{}, resultsCh chan<- tree.Datums, 377 ) error { 378 p := phs.(*planner) 379 details := r.job.Details().(jobspb.CreateStatsDetails) 380 if details.Name == stats.AutoStatsName { 381 // We want to make sure there is only one automatic CREATE STATISTICS job 382 // running at a time. 383 if err := checkRunningJobs(ctx, r.job, p); err != nil { 384 return err 385 } 386 } 387 388 r.tableID = details.Table.ID 389 evalCtx := p.ExtendedEvalContext() 390 391 ci := sqlbase.ColTypeInfoFromColTypes([]*types.T{}) 392 rows := rowcontainer.NewRowContainer(evalCtx.Mon.MakeBoundAccount(), ci, 0) 393 defer func() { 394 if rows != nil { 395 rows.Close(ctx) 396 } 397 }() 398 399 dsp := p.DistSQLPlanner() 400 if err := p.ExecCfg().DB.Txn(ctx, func(ctx context.Context, txn *kv.Txn) error { 401 // Set the transaction on the EvalContext to this txn. This allows for 402 // use of the txn during processor setup during the execution of the flow. 403 evalCtx.Txn = txn 404 405 if details.AsOf != nil { 406 p.semaCtx.AsOfTimestamp = details.AsOf 407 p.extendedEvalCtx.SetTxnTimestamp(details.AsOf.GoTime()) 408 txn.SetFixedTimestamp(ctx, *details.AsOf) 409 } 410 411 planCtx := dsp.NewPlanningCtx(ctx, evalCtx, txn, true /* distribute */) 412 planCtx.planner = p 413 if err := dsp.planAndRunCreateStats( 414 ctx, evalCtx, planCtx, txn, r.job, NewRowResultWriter(rows), 415 ); err != nil { 416 // Check if this was a context canceled error and restart if it was. 417 if s, ok := status.FromError(errors.UnwrapAll(err)); ok { 418 if s.Code() == codes.Canceled && s.Message() == context.Canceled.Error() { 419 return jobs.NewRetryJobError("node failure") 420 } 421 } 422 423 // If the job was canceled, any of the distsql processors could have been 424 // the first to encounter the .Progress error. This error's string is sent 425 // through distsql back here, so we can't examine the err type in this case 426 // to see if it's a jobs.InvalidStatusError. Instead, attempt to update the 427 // job progress to coerce out the correct error type. If the update succeeds 428 // then return the original error, otherwise return this error instead so 429 // it can be cleaned up at a higher level. 430 if jobErr := r.job.FractionProgressed( 431 ctx, 432 func(ctx context.Context, _ jobspb.ProgressDetails) float32 { 433 // The job failed so the progress value here doesn't really matter. 434 return 0 435 }, 436 ); jobErr != nil { 437 return jobErr 438 } 439 return err 440 } 441 442 return nil 443 }); err != nil { 444 return err 445 } 446 447 // Invalidate the local cache synchronously; this guarantees that the next 448 // statement in the same session won't use a stale cache (whereas the gossip 449 // update is handled asynchronously). 450 evalCtx.ExecCfg.TableStatsCache.InvalidateTableStats(ctx, r.tableID) 451 452 // Record this statistics creation in the event log. 453 if !createStatsPostEvents.Get(&evalCtx.Settings.SV) { 454 return nil 455 } 456 457 // TODO(rytaft): This creates a new transaction for the CREATE STATISTICS 458 // event. It must be different from the CREATE STATISTICS transaction, 459 // because that transaction must be read-only. In the future we may want 460 // to use the transaction that inserted the new stats into the 461 // system.table_statistics table, but that would require calling 462 // MakeEventLogger from the distsqlrun package. 463 return evalCtx.ExecCfg.DB.Txn(ctx, func(ctx context.Context, txn *kv.Txn) error { 464 return MakeEventLogger(evalCtx.ExecCfg).InsertEventRecord( 465 ctx, 466 txn, 467 EventLogCreateStatistics, 468 int32(details.Table.ID), 469 int32(evalCtx.NodeID.SQLInstanceID()), 470 struct { 471 TableName string 472 Statement string 473 }{details.FQTableName, details.Statement}, 474 ) 475 }) 476 } 477 478 // checkRunningJobs checks whether there are any other CreateStats jobs in the 479 // pending, running, or paused status that started earlier than this one. If 480 // there are, checkRunningJobs returns an error. If job is nil, checkRunningJobs 481 // just checks if there are any pending, running, or paused CreateStats jobs. 482 func checkRunningJobs(ctx context.Context, job *jobs.Job, p *planner) error { 483 var jobID int64 484 if job != nil { 485 jobID = *job.ID() 486 } 487 const stmt = `SELECT id, payload FROM system.jobs WHERE status IN ($1, $2, $3) ORDER BY created` 488 489 rows, err := p.ExecCfg().InternalExecutor.Query( 490 ctx, 491 "get-jobs", 492 nil, /* txn */ 493 stmt, 494 jobs.StatusPending, 495 jobs.StatusRunning, 496 jobs.StatusPaused, 497 ) 498 if err != nil { 499 return err 500 } 501 502 for _, row := range rows { 503 payload, err := jobs.UnmarshalPayload(row[1]) 504 if err != nil { 505 return err 506 } 507 508 if payload.Type() == jobspb.TypeCreateStats || payload.Type() == jobspb.TypeAutoCreateStats { 509 id := (*int64)(row[0].(*tree.DInt)) 510 if *id == jobID { 511 break 512 } 513 514 // This is not the first CreateStats job running. This job should fail 515 // so that the earlier job can succeed. 516 return stats.ConcurrentCreateStatsError 517 } 518 } 519 return nil 520 } 521 522 // OnFailOrCancel is part of the jobs.Resumer interface. 523 func (r *createStatsResumer) OnFailOrCancel(context.Context, interface{}) error { return nil } 524 525 func init() { 526 createResumerFn := func(job *jobs.Job, settings *cluster.Settings) jobs.Resumer { 527 return &createStatsResumer{job: job} 528 } 529 jobs.RegisterConstructor(jobspb.TypeCreateStats, createResumerFn) 530 jobs.RegisterConstructor(jobspb.TypeAutoCreateStats, createResumerFn) 531 }