github.com/pingcap/ticdc@v0.0.0-20220526033649-485a10ef2652/cdc/model/changefeed.go (about) 1 // Copyright 2020 PingCAP, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package model 15 16 import ( 17 "encoding/json" 18 "math" 19 "regexp" 20 "sort" 21 "time" 22 23 "github.com/pingcap/errors" 24 "github.com/pingcap/log" 25 "github.com/pingcap/ticdc/pkg/config" 26 "github.com/pingcap/ticdc/pkg/cyclic/mark" 27 cerror "github.com/pingcap/ticdc/pkg/errors" 28 "github.com/pingcap/tidb/store/tikv/oracle" 29 "go.uber.org/zap" 30 ) 31 32 // SortEngine is the sorter engine 33 type SortEngine = string 34 35 // sort engines 36 const ( 37 SortInMemory SortEngine = "memory" 38 SortInFile SortEngine = "file" 39 SortUnified SortEngine = "unified" 40 ) 41 42 // FeedState represents the running state of a changefeed 43 type FeedState string 44 45 // All FeedStates 46 const ( 47 StateNormal FeedState = "normal" 48 StateError FeedState = "error" 49 StateFailed FeedState = "failed" 50 StateStopped FeedState = "stopped" 51 StateRemoved FeedState = "removed" // deprecated, will be removed in the next version 52 StateFinished FeedState = "finished" 53 ) 54 55 // ToInt return a int for each `FeedState`, only use this for metrics. 56 func (s FeedState) ToInt() int { 57 switch s { 58 case StateNormal: 59 return 0 60 case StateError: 61 return 1 62 case StateFailed: 63 return 2 64 case StateStopped: 65 return 3 66 case StateFinished: 67 return 4 68 case StateRemoved: 69 return 5 70 } 71 // -1 for unknown feed state 72 return -1 73 } 74 75 const ( 76 // errorHistoryGCInterval represents how long we keep error record in changefeed info 77 errorHistoryGCInterval = time.Minute * 10 78 79 // errorHistoryCheckInterval represents time window for failure check 80 errorHistoryCheckInterval = time.Minute * 2 81 82 // ErrorHistoryThreshold represents failure upper limit in time window. 83 // Before a changefeed is initialized, check the the failure count of this 84 // changefeed, if it is less than ErrorHistoryThreshold, then initialize it. 85 ErrorHistoryThreshold = 5 86 ) 87 88 // ChangeFeedInfo describes the detail of a ChangeFeed 89 type ChangeFeedInfo struct { 90 SinkURI string `json:"sink-uri"` 91 Opts map[string]string `json:"opts"` 92 CreateTime time.Time `json:"create-time"` 93 // Start sync at this commit ts if `StartTs` is specify or using the CreateTime of changefeed. 94 StartTs uint64 `json:"start-ts"` 95 // The ChangeFeed will exits until sync to timestamp TargetTs 96 TargetTs uint64 `json:"target-ts"` 97 // used for admin job notification, trigger watch event in capture 98 AdminJobType AdminJobType `json:"admin-job-type"` 99 Engine SortEngine `json:"sort-engine"` 100 // SortDir is deprecated 101 // it cannot be set by user in changefeed level, any assignment to it should be ignored. 102 // but can be fetched for backward compatibility 103 SortDir string `json:"sort-dir"` 104 105 Config *config.ReplicaConfig `json:"config"` 106 State FeedState `json:"state"` 107 ErrorHis []int64 `json:"history"` 108 Error *RunningError `json:"error"` 109 110 SyncPointEnabled bool `json:"sync-point-enabled"` 111 SyncPointInterval time.Duration `json:"sync-point-interval"` 112 CreatorVersion string `json:"creator-version"` 113 } 114 115 const changeFeedIDMaxLen = 128 116 117 var changeFeedIDRe = regexp.MustCompile(`^[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*$`) 118 119 // ValidateChangefeedID returns true if the changefeed ID matches 120 // the pattern "^[a-zA-Z0-9]+(\-[a-zA-Z0-9]+)*$", length no more than "changeFeedIDMaxLen", eg, "simple-changefeed-task". 121 func ValidateChangefeedID(changefeedID string) error { 122 if !changeFeedIDRe.MatchString(changefeedID) || len(changefeedID) > changeFeedIDMaxLen { 123 return cerror.ErrInvalidChangefeedID.GenWithStackByArgs(changeFeedIDMaxLen) 124 } 125 return nil 126 } 127 128 // String implements fmt.Stringer interface, but hide some sensitive information 129 func (info *ChangeFeedInfo) String() (str string) { 130 var err error 131 str, err = info.Marshal() 132 if err != nil { 133 log.Error("failed to marshal changefeed info", zap.Error(err)) 134 return 135 } 136 clone := new(ChangeFeedInfo) 137 err = clone.Unmarshal([]byte(str)) 138 if err != nil { 139 log.Error("failed to unmarshal changefeed info", zap.Error(err)) 140 return 141 } 142 clone.SinkURI = "***" 143 str, err = clone.Marshal() 144 if err != nil { 145 log.Error("failed to marshal changefeed info", zap.Error(err)) 146 } 147 return 148 } 149 150 // GetStartTs returns StartTs if it's specified or using the CreateTime of changefeed. 151 func (info *ChangeFeedInfo) GetStartTs() uint64 { 152 if info.StartTs > 0 { 153 return info.StartTs 154 } 155 156 return oracle.EncodeTSO(info.CreateTime.Unix() * 1000) 157 } 158 159 // GetCheckpointTs returns CheckpointTs if it's specified in ChangeFeedStatus, otherwise StartTs is returned. 160 func (info *ChangeFeedInfo) GetCheckpointTs(status *ChangeFeedStatus) uint64 { 161 if status != nil { 162 return status.CheckpointTs 163 } 164 return info.GetStartTs() 165 } 166 167 // GetTargetTs returns TargetTs if it's specified, otherwise MaxUint64 is returned. 168 func (info *ChangeFeedInfo) GetTargetTs() uint64 { 169 if info.TargetTs > 0 { 170 return info.TargetTs 171 } 172 return uint64(math.MaxUint64) 173 } 174 175 // Marshal returns the json marshal format of a ChangeFeedInfo 176 func (info *ChangeFeedInfo) Marshal() (string, error) { 177 data, err := json.Marshal(info) 178 return string(data), cerror.WrapError(cerror.ErrMarshalFailed, err) 179 } 180 181 // Unmarshal unmarshals into *ChangeFeedInfo from json marshal byte slice 182 func (info *ChangeFeedInfo) Unmarshal(data []byte) error { 183 err := json.Unmarshal(data, &info) 184 if err != nil { 185 return errors.Annotatef( 186 cerror.WrapError(cerror.ErrUnmarshalFailed, err), "Unmarshal data: %v", data) 187 } 188 // TODO(neil) find a better way to let sink know cyclic is enabled. 189 if info.Config != nil && info.Config.Cyclic.IsEnabled() { 190 cyclicCfg, err := info.Config.Cyclic.Marshal() 191 if err != nil { 192 return errors.Annotatef( 193 cerror.WrapError(cerror.ErrMarshalFailed, err), "Marshal data: %v", data) 194 } 195 info.Opts[mark.OptCyclicConfig] = cyclicCfg 196 } 197 return nil 198 } 199 200 // Clone returns a cloned ChangeFeedInfo 201 func (info *ChangeFeedInfo) Clone() (*ChangeFeedInfo, error) { 202 s, err := info.Marshal() 203 if err != nil { 204 return nil, err 205 } 206 cloned := new(ChangeFeedInfo) 207 err = cloned.Unmarshal([]byte(s)) 208 return cloned, err 209 } 210 211 // VerifyAndFix verifies changefeed info and may fillin some fields. 212 // If a must field is not provided, return an error. 213 // If some necessary filed is missing but can use a default value, fillin it. 214 func (info *ChangeFeedInfo) VerifyAndFix() error { 215 defaultConfig := config.GetDefaultReplicaConfig() 216 if info.Engine == "" { 217 info.Engine = SortUnified 218 } 219 if info.Config.Filter == nil { 220 info.Config.Filter = defaultConfig.Filter 221 } 222 if info.Config.Mounter == nil { 223 info.Config.Mounter = defaultConfig.Mounter 224 } 225 if info.Config.Sink == nil { 226 info.Config.Sink = defaultConfig.Sink 227 } 228 if info.Config.Cyclic == nil { 229 info.Config.Cyclic = defaultConfig.Cyclic 230 } 231 if info.Config.Scheduler == nil { 232 info.Config.Scheduler = defaultConfig.Scheduler 233 } 234 return nil 235 } 236 237 // CheckErrorHistory checks error history of a changefeed 238 // if having error record older than GC interval, set needSave to true. 239 // if error counts reach threshold, set canInit to false. 240 func (info *ChangeFeedInfo) CheckErrorHistory() (needSave bool, canInit bool) { 241 i := sort.Search(len(info.ErrorHis), func(i int) bool { 242 ts := info.ErrorHis[i] 243 return time.Since(time.Unix(ts/1e3, (ts%1e3)*1e6)) < errorHistoryGCInterval 244 }) 245 info.ErrorHis = info.ErrorHis[i:] 246 247 if i > 0 { 248 needSave = true 249 } 250 251 i = sort.Search(len(info.ErrorHis), func(i int) bool { 252 ts := info.ErrorHis[i] 253 return time.Since(time.Unix(ts/1e3, (ts%1e3)*1e6)) < errorHistoryCheckInterval 254 }) 255 canInit = len(info.ErrorHis)-i < ErrorHistoryThreshold 256 return 257 } 258 259 // HasFastFailError returns true if the error in changefeed is fast-fail 260 func (info *ChangeFeedInfo) HasFastFailError() bool { 261 if info.Error == nil { 262 return false 263 } 264 return cerror.ChangefeedFastFailErrorCode(errors.RFCErrorCode(info.Error.Code)) 265 } 266 267 // findActiveErrors finds all errors occurring within errorHistoryCheckInterval 268 func (info *ChangeFeedInfo) findActiveErrors() []int64 { 269 i := sort.Search(len(info.ErrorHis), func(i int) bool { 270 ts := info.ErrorHis[i] 271 // ts is a errors occurrence time, here to find all errors occurring within errorHistoryCheckInterval 272 return time.Since(time.Unix(ts/1e3, (ts%1e3)*1e6)) < errorHistoryCheckInterval 273 }) 274 return info.ErrorHis[i:] 275 } 276 277 // ErrorsReachedThreshold checks error history of a changefeed 278 // returns true if error counts reach threshold 279 func (info *ChangeFeedInfo) ErrorsReachedThreshold() bool { 280 return len(info.findActiveErrors()) >= ErrorHistoryThreshold 281 } 282 283 // CleanUpOutdatedErrorHistory cleans up the outdated error history 284 // return true if the ErrorHis changed 285 func (info *ChangeFeedInfo) CleanUpOutdatedErrorHistory() bool { 286 lastLenOfErrorHis := len(info.ErrorHis) 287 info.ErrorHis = info.findActiveErrors() 288 return lastLenOfErrorHis != len(info.ErrorHis) 289 }