-
Notifications
You must be signed in to change notification settings - Fork 3.8k
/
gc_job.go
174 lines (155 loc) · 5.12 KB
/
gc_job.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
// Copyright 2020 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
package gcjob
import (
"context"
"time"
"github.com/cockroachdb/cockroach/pkg/jobs"
"github.com/cockroachdb/cockroach/pkg/jobs/jobspb"
"github.com/cockroachdb/cockroach/pkg/settings/cluster"
"github.com/cockroachdb/cockroach/pkg/sql"
"github.com/cockroachdb/cockroach/pkg/sql/catalog/descpb"
"github.com/cockroachdb/cockroach/pkg/sql/catalog/tabledesc"
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
"github.com/cockroachdb/cockroach/pkg/util/log"
"github.com/cockroachdb/cockroach/pkg/util/timeutil"
"github.com/cockroachdb/errors"
)
var (
// MaxSQLGCInterval is the longest the polling interval between checking if
// elements should be GC'd.
MaxSQLGCInterval = 5 * time.Minute
)
// SetSmallMaxGCIntervalForTest sets the MaxSQLGCInterval and then returns a closure
// that resets it.
// This is to be used in tests like:
// defer SetSmallMaxGCIntervalForTest()
func SetSmallMaxGCIntervalForTest() func() {
oldInterval := MaxSQLGCInterval
MaxSQLGCInterval = 500 * time.Millisecond
return func() {
MaxSQLGCInterval = oldInterval
}
}
type schemaChangeGCResumer struct {
jobID int64
}
// performGC GCs any schema elements that are in the DELETING state and returns
// a bool indicating if it GC'd any elements.
func performGC(
ctx context.Context,
execCfg *sql.ExecutorConfig,
details *jobspb.SchemaChangeGCDetails,
progress *jobspb.SchemaChangeGCProgress,
) error {
if details.Indexes != nil {
return errors.Wrap(gcIndexes(ctx, execCfg, details.ParentID, progress), "attempting to GC indexes")
} else if details.Tables != nil {
if err := gcTables(ctx, execCfg, progress); err != nil {
return errors.Wrap(err, "attempting to GC tables")
}
// Drop database zone config when all the tables have been GCed.
if details.ParentID != descpb.InvalidID && isDoneGC(progress) {
if err := deleteDatabaseZoneConfig(ctx, execCfg.DB, execCfg.Codec, details.ParentID); err != nil {
return errors.Wrap(err, "deleting database zone config")
}
}
}
return nil
}
// Resume is part of the jobs.Resumer interface.
func (r schemaChangeGCResumer) Resume(
ctx context.Context, execCtx interface{}, _ chan<- tree.Datums,
) error {
p := execCtx.(sql.JobExecContext)
// TODO(pbardea): Wait for no versions.
execCfg := p.ExecCfg()
if fn := execCfg.GCJobTestingKnobs.RunBeforeResume; fn != nil {
if err := fn(r.jobID); err != nil {
return err
}
}
details, progress, err := initDetailsAndProgress(ctx, execCfg, r.jobID)
if err != nil {
return err
}
// If there are any interleaved indexes to drop as part of a table TRUNCATE
// operation, then drop the indexes before waiting on the GC timer.
if len(details.InterleavedIndexes) > 0 {
// Before deleting any indexes, ensure that old versions of the table
// descriptor are no longer in use.
if err := sql.WaitToUpdateLeases(ctx, execCfg.LeaseManager, details.InterleavedTable.ID); err != nil {
return err
}
if err := sql.TruncateInterleavedIndexes(
ctx,
execCfg,
tabledesc.NewImmutable(*details.InterleavedTable),
details.InterleavedIndexes,
); err != nil {
return err
}
}
tableDropTimes, indexDropTimes := getDropTimes(details)
timer := timeutil.NewTimer()
defer timer.Stop()
timer.Reset(0)
gossipUpdateC, cleanup := execCfg.GCJobNotifier.AddNotifyee(ctx)
defer cleanup()
for {
select {
case <-gossipUpdateC:
if log.V(2) {
log.Info(ctx, "received a new system config")
}
case <-timer.C:
timer.Read = true
if log.V(2) {
log.Info(ctx, "SchemaChangeGC timer triggered")
}
case <-ctx.Done():
return ctx.Err()
}
// Refresh the status of all tables in case any GC TTLs have changed.
remainingTables := getAllTablesWaitingForGC(details, progress)
expired, earliestDeadline := refreshTables(ctx, execCfg, remainingTables, tableDropTimes, indexDropTimes, r.jobID, progress)
timerDuration := time.Until(earliestDeadline)
if expired {
// Some elements have been marked as DELETING so save the progress.
persistProgress(ctx, execCfg, r.jobID, progress)
if err := performGC(ctx, execCfg, details, progress); err != nil {
return err
}
persistProgress(ctx, execCfg, r.jobID, progress)
// Trigger immediate re-run in case of more expired elements.
timerDuration = 0
}
if isDoneGC(progress) {
return nil
}
// Schedule the next check for GC.
if timerDuration > MaxSQLGCInterval {
timerDuration = MaxSQLGCInterval
}
timer.Reset(timerDuration)
}
}
// OnFailOrCancel is part of the jobs.Resumer interface.
func (r schemaChangeGCResumer) OnFailOrCancel(context.Context, interface{}) error {
return nil
}
func init() {
createResumerFn := func(job *jobs.Job, settings *cluster.Settings) jobs.Resumer {
return &schemaChangeGCResumer{
jobID: *job.ID(),
}
}
jobs.RegisterConstructor(jobspb.TypeSchemaChangeGC, createResumerFn)
}