Files
tidb/pkg/statistics/handle/autoanalyze/priorityqueue/interval.go
2024-03-01 13:36:32 +00:00

151 lines
4.5 KiB
Go

// Copyright 2024 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package priorityqueue
import (
"time"
"github.com/pingcap/tidb/pkg/sessionctx"
"github.com/pingcap/tidb/pkg/statistics/handle/util"
)
// NoRecord is used to indicate that there is no related record in mysql.analyze_jobs.
const NoRecord = -1
// justFailed is used to indicate that the last analysis has just failed.
const justFailed = 0
const avgDurationQueryForTable = `
SELECT AVG(TIMESTAMPDIFF(SECOND, start_time, end_time)) AS avg_duration
FROM (
SELECT start_time, end_time
FROM mysql.analyze_jobs
WHERE table_schema = %? AND table_name = %? AND state = 'finished' AND fail_reason IS NULL AND partition_name = ''
ORDER BY id DESC
LIMIT 5
) AS recent_analyses;
`
// For multiple partitions, we only need to return the average duration of the most recent 5 successful analyses
// from all partitions.
const avgDurationQueryForPartition = `
SELECT AVG(TIMESTAMPDIFF(SECOND, start_time, end_time)) AS avg_duration
FROM (
SELECT start_time, end_time
FROM mysql.analyze_jobs
WHERE table_schema = %? AND table_name = %? AND state = 'finished' AND fail_reason IS NULL AND partition_name in (%?)
ORDER BY id DESC
LIMIT 5
) AS recent_analyses;
`
const lastFailedDurationQueryForTable = `
SELECT TIMESTAMPDIFF(SECOND, start_time, CURRENT_TIMESTAMP)
FROM mysql.analyze_jobs
WHERE table_schema = %? AND table_name = %? AND state = 'failed' AND partition_name = ''
ORDER BY id DESC
LIMIT 1;
`
// For multiple partitions, we only need to return the duration of the most recent failed analysis.
// We pick the minimum duration of all failed analyses because we want to be conservative.
const lastFailedDurationQueryForPartition = `
SELECT
MIN(TIMESTAMPDIFF(SECOND, aj.start_time, CURRENT_TIMESTAMP)) AS min_duration
FROM (
SELECT
MAX(id) AS max_id
FROM
mysql.analyze_jobs
WHERE
table_schema = %?
AND table_name = %?
AND state = 'failed'
AND partition_name IN (%?)
GROUP BY
partition_name
) AS latest_failures
JOIN mysql.analyze_jobs aj ON aj.id = latest_failures.max_id;
`
// GetAverageAnalysisDuration returns the average duration of the last 5 successful analyses for each specified partition.
// If there are no successful analyses, it returns 0.
func GetAverageAnalysisDuration(
sctx sessionctx.Context,
schema, tableName string,
partitionNames ...string,
) (time.Duration, error) {
query := ""
params := make([]any, 0, len(partitionNames)+3)
if len(partitionNames) == 0 {
query = avgDurationQueryForTable
params = append(params, schema, tableName)
} else {
query = avgDurationQueryForPartition
params = append(params, schema, tableName, partitionNames)
}
rows, _, err := util.ExecRows(sctx, query, params...)
if err != nil {
return NoRecord, err
}
// NOTE: if there are no successful analyses, we return 0.
if len(rows) == 0 || rows[0].IsNull(0) {
return NoRecord, nil
}
avgDuration := rows[0].GetMyDecimal(0)
duration, err := avgDuration.ToFloat64()
if err != nil {
return NoRecord, err
}
return time.Duration(duration) * time.Second, nil
}
// GetLastFailedAnalysisDuration returns the duration since the last failed analysis.
// If there is no failed analysis, it returns 0.
func GetLastFailedAnalysisDuration(
sctx sessionctx.Context,
schema, tableName string,
partitionNames ...string,
) (time.Duration, error) {
query := ""
params := make([]any, 0, len(partitionNames)+3)
if len(partitionNames) == 0 {
query = lastFailedDurationQueryForTable
params = append(params, schema, tableName)
} else {
query = lastFailedDurationQueryForPartition
params = append(params, schema, tableName, partitionNames)
}
rows, _, err := util.ExecRows(sctx, query, params...)
if err != nil {
return NoRecord, err
}
// NOTE: if there are no failed analyses, we return 0.
if len(rows) == 0 || rows[0].IsNull(0) {
return NoRecord, nil
}
lastFailedDuration := rows[0].GetUint64(0)
if lastFailedDuration == 0 {
return justFailed, nil
}
return time.Duration(lastFailedDuration) * time.Second, nil
}