Skip to content

Commit 995d6f9

Browse files
authored
stats: add defensive guard for interval calculation (#65889)
close #65815
1 parent 72d131e commit 995d6f9

File tree

2 files changed

+45
-5
lines changed

2 files changed

+45
-5
lines changed

pkg/statistics/handle/autoanalyze/priorityqueue/interval.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ const LastFailedDurationQueryForPartition = `
8383
`
8484

8585
// GetAverageAnalysisDuration returns the average duration of the last 5 successful analyses for each specified partition.
86-
// If there are no successful analyses, it returns 0.
86+
// If there are no successful analyses, it returns NoRecord.
8787
func GetAverageAnalysisDuration(
8888
sctx sessionctx.Context,
8989
schema, tableName string,
@@ -104,7 +104,7 @@ func GetAverageAnalysisDuration(
104104
return NoRecord, err
105105
}
106106

107-
// NOTE: if there are no successful analyses, we return 0.
107+
// NOTE: if there are no successful analyses, we return NoRecord.
108108
if len(rows) == 0 || rows[0].IsNull(0) {
109109
return NoRecord, nil
110110
}
@@ -113,12 +113,16 @@ func GetAverageAnalysisDuration(
113113
if err != nil {
114114
return NoRecord, err
115115
}
116+
if duration < 0 {
117+
// Defensive: bad records or clock skew could yield negative durations.
118+
return NoRecord, nil
119+
}
116120

117121
return time.Duration(duration) * time.Second, nil
118122
}
119123

120124
// GetLastFailedAnalysisDuration returns the duration since the last failed analysis.
121-
// If there is no failed analysis, it returns 0.
125+
// If there is no failed analysis, it returns NoRecord.
122126
func GetLastFailedAnalysisDuration(
123127
sctx sessionctx.Context,
124128
schema, tableName string,
@@ -139,14 +143,19 @@ func GetLastFailedAnalysisDuration(
139143
return NoRecord, err
140144
}
141145

142-
// NOTE: if there are no failed analyses, we return 0.
146+
// NOTE: if there are no failed analyses, we return NoRecord.
143147
if len(rows) == 0 || rows[0].IsNull(0) {
144148
return NoRecord, nil
145149
}
146-
lastFailedDuration := rows[0].GetUint64(0)
150+
lastFailedDuration := rows[0].GetInt64(0)
147151
if lastFailedDuration == 0 {
148152
return justFailed, nil
149153
}
154+
if lastFailedDuration < 0 {
155+
// Defensive: a bad record or clock skew can make start_time in the future.
156+
// Treat it as a bounded default instead of "no record".
157+
return defaultFailedAnalysisWaitTime, nil
158+
}
150159

151160
return time.Duration(lastFailedDuration) * time.Second, nil
152161
}

pkg/statistics/handle/autoanalyze/priorityqueue/interval_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,22 @@ func TestGetAverageAnalysisDuration(t *testing.T) {
6565
require.Equal(t, time.Duration(3600)*time.Second, avgDuration)
6666
}
6767

68+
func TestGetAverageAnalysisDurationNegativeRecord(t *testing.T) {
69+
store := testkit.CreateMockStore(t)
70+
tk := testkit.NewTestKit(t, store)
71+
tk.MustExec("use test")
72+
tk.MustExec(metadef.CreateAnalyzeJobsTable)
73+
74+
// Insert a finished record with end_time earlier than start_time to force a negative duration.
75+
// This could happen if the system clock is adjusted backward (rare in practice).
76+
insertFinishedJob(tk, "neg_schema", "neg_table", "", "2024-01-01 10:00:00", "2024-01-01 09:00:00")
77+
78+
sctx := tk.Session().(sessionctx.Context)
79+
avgDuration, err := priorityqueue.GetAverageAnalysisDuration(sctx, "neg_schema", "neg_table")
80+
require.NoError(t, err)
81+
require.Equal(t, time.Duration(priorityqueue.NoRecord), avgDuration)
82+
}
83+
6884
func insertMultipleFinishedJobs(tk *testkit.TestKit, tableName string, partitionName string) {
6985
jobs := []struct {
7086
dbName string
@@ -130,6 +146,21 @@ func TestGetLastFailedAnalysisDuration(t *testing.T) {
130146
require.GreaterOrEqual(t, lastFailedDuration, time.Duration(24)*time.Hour)
131147
}
132148

149+
func TestGetLastFailedAnalysisDurationNegativeRecord(t *testing.T) {
150+
store := testkit.CreateMockStore(t)
151+
tk := testkit.NewTestKit(t, store)
152+
tk.MustExec("use test")
153+
tk.MustExec(metadef.CreateAnalyzeJobsTable)
154+
155+
// Insert a failed record whose start_time is in the future to force a negative duration.
156+
insertFailedJobWithStartTime(tk, "neg_schema", "neg_table", "", "2037-01-01 00:00:00")
157+
158+
sctx := tk.Session().(sessionctx.Context)
159+
lastFailedDuration, err := priorityqueue.GetLastFailedAnalysisDuration(sctx, "neg_schema", "neg_table")
160+
require.NoError(t, err)
161+
require.Equal(t, 30*time.Minute, lastFailedDuration)
162+
}
163+
133164
func initJobs(tk *testkit.TestKit) {
134165
tk.MustExec(`
135166
INSERT INTO mysql.analyze_jobs (

0 commit comments

Comments
 (0)