github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdcv2/metadata/model.go (about) 1 // Copyright 2023 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 metadata 15 16 import ( 17 "database/sql/driver" 18 "encoding/json" 19 "errors" 20 "fmt" 21 "strings" 22 23 "github.com/pingcap/log" 24 "github.com/pingcap/tiflow/cdc/model" 25 "github.com/pingcap/tiflow/pkg/config" 26 "go.uber.org/zap" 27 ) 28 29 // ChangefeedUUID is the unique identifier of a changefeed. 30 type ChangefeedUUID = uint64 31 32 // ChangefeedIdent identifies a changefeed. 33 type ChangefeedIdent struct { 34 // UUID is generated internally by TiCDC to distinguish between changefeeds with the same ID. 35 // Note that it can't be specified by the user. 36 UUID ChangefeedUUID `gorm:"column:uuid;type:bigint(20) unsigned;primaryKey" json:"uuid"` 37 38 // Namespace and ID pair is unique in one ticdc cluster. And in the current implementation, 39 // Namespace can only be set to `default`. 40 Namespace string `gorm:"column:namespace;type:varchar(128);not null;uniqueIndex:namespace,priority:1" json:"namespace"` 41 ID string `gorm:"column:id;type:varchar(128);not null;uniqueIndex:namespace,priority:2" json:"id"` 42 } 43 44 // ToChangefeedID converts ChangefeedUUID to model.ChangeFeedID. 45 func (c ChangefeedIdent) ToChangefeedID() model.ChangeFeedID { 46 return model.ChangeFeedID{ 47 Namespace: c.Namespace, 48 ID: c.ID, 49 } 50 } 51 52 // String implements fmt.Stringer interface 53 func (c ChangefeedIdent) String() string { 54 return fmt.Sprintf("%d(%s/%s)", c.UUID, c.Namespace, c.ID) 55 } 56 57 // Compare compares two ChangefeedIDWithEpoch base on their string representation. 58 func (c *ChangefeedIdent) Compare(other ChangefeedIdent) int { 59 return strings.Compare(c.String(), other.String()) 60 } 61 62 // ChangefeedInfo is a minimal info collection to describe a changefeed. 63 type ChangefeedInfo struct { 64 ChangefeedIdent 65 66 UpstreamID uint64 `gorm:"column:upstream_id;type:bigint(20) unsigned;not null;index:upstream_id,priority:1" json:"upstream_id"` 67 SinkURI string `gorm:"column:sink_uri;type:text;not null" json:"sink_uri"` 68 StartTs uint64 `gorm:"column:start_ts;type:bigint(20) unsigned;not null" json:"start_ts"` 69 TargetTs uint64 `gorm:"column:target_ts;type:bigint(20) unsigned;not null" json:"target_ts"` 70 // Note that pointer type is used here for compatibility with the old model, and config should never be nil in practice. 71 Config *config.ReplicaConfig `gorm:"column:config;type:longtext;not null" json:"config"` 72 } 73 74 // ChangefeedState is the status of a changefeed. 75 type ChangefeedState struct { 76 ChangefeedUUID ChangefeedUUID `gorm:"column:changefeed_uuid;type:bigint(20) unsigned;primaryKey" json:"changefeed_uuid"` 77 State model.FeedState `gorm:"column:state;type:text;not null" json:"state"` 78 Warning *model.RunningError `gorm:"column:warning;type:text" json:"warning"` 79 Error *model.RunningError `gorm:"column:error;type:text" json:"error"` 80 } 81 82 // ChangefeedProgress is for changefeed progress. Use ChangeFeedStatus to maintain compatibility with older versions of the code. 83 type ChangefeedProgress model.ChangeFeedStatus 84 85 // Value implements the driver.Valuer interface. 86 func (cp ChangefeedProgress) Value() (driver.Value, error) { 87 return json.Marshal(cp) 88 } 89 90 // Scan implements the sql.Scanner interface. 91 func (cp *ChangefeedProgress) Scan(value interface{}) error { 92 b, ok := value.([]byte) 93 if !ok { 94 return errors.New("type assertion to []byte failed") 95 } 96 97 return json.Unmarshal(b, cp) 98 } 99 100 // CaptureProgress stores the progress of all ChangeFeeds on single capture. 101 type CaptureProgress map[ChangefeedUUID]ChangefeedProgress 102 103 // Value implements the driver.Valuer interface. 104 func (cp CaptureProgress) Value() (driver.Value, error) { 105 return json.Marshal(cp) 106 } 107 108 // Scan implements the sql.Scanner interface. 109 func (cp *CaptureProgress) Scan(value interface{}) error { 110 b, ok := value.([]byte) 111 if !ok { 112 return errors.New("type assertion to []byte failed") 113 } 114 115 return json.Unmarshal(b, cp) 116 } 117 118 // SchedState is the type of state to schedule owners and processors. 119 type SchedState int 120 121 const ( 122 // SchedInvalid should never be used. 123 SchedInvalid SchedState = SchedState(0) 124 // SchedRemoved means the owner or processor is removed. 125 SchedRemoved SchedState = SchedState(1) 126 // SchedLaunched means the owner or processor is launched. 127 SchedLaunched SchedState = SchedState(2) 128 // SchedRemoving means the owner or processor is in removing. 129 SchedRemoving SchedState = SchedState(3) 130 ) 131 132 // String implements the fmt.Stringer interface. 133 func (s SchedState) String() string { 134 return s.toString() 135 } 136 137 func (s SchedState) toString() string { 138 switch s { 139 case SchedLaunched: 140 return "Launched" 141 case SchedRemoving: 142 return "Removing" 143 case SchedRemoved: 144 return "Removed" 145 default: 146 return "unreachable" 147 } 148 } 149 150 // nolint 151 func (s *SchedState) fromString(str string) error { 152 switch str { 153 case "Launched": 154 *s = SchedLaunched 155 case "Removing": 156 *s = SchedRemoving 157 case "Removed": 158 *s = SchedRemoved 159 default: 160 *s = SchedInvalid 161 return errors.New("unreachable") 162 } 163 return nil 164 } 165 166 // Value implements the driver.Valuer interface. 167 func (s SchedState) Value() (driver.Value, error) { 168 return []byte(s.toString()), nil 169 } 170 171 // Scan implements the sql.Scanner interface. 172 func (s *SchedState) Scan(value interface{}) error { 173 b, ok := value.([]byte) 174 if !ok { 175 return errors.New("type assertion to []byte failed") 176 } 177 178 return s.fromString(string(b)) 179 } 180 181 // ScheduledChangefeed is for owner and processor schedule. 182 type ScheduledChangefeed struct { 183 ChangefeedUUID ChangefeedUUID `gorm:"column:changefeed_uuid;type:bigint(20) unsigned;primaryKey" json:"changefeed_uuid"` 184 Owner *model.CaptureID `gorm:"column:owner;type:varchar(128)" json:"owner"` 185 OwnerState SchedState `gorm:"column:owner_state;type:text;not null" json:"owner_state"` 186 // Processors is always equal to the owner in the current implementation. 187 Processors *model.CaptureID `gorm:"column:processors;type:text" json:"processors"` 188 // TaskPosition is used to initialize changefeed owner on the capture. 189 TaskPosition ChangefeedProgress `gorm:"column:task_position;type:text;not null" json:"task_position"` 190 } 191 192 // CheckScheduleState checks whether the origin and target schedule state is valid. 193 func CheckScheduleState(origin ScheduledChangefeed, target ScheduledChangefeed) error { 194 if origin.ChangefeedUUID != target.ChangefeedUUID { 195 log.Panic("bad schedule: changefeed id not match", 196 zap.Any("origin", origin), zap.Any("target", target)) 197 } 198 if origin.OwnerState == SchedInvalid || target.OwnerState == SchedInvalid { 199 return NewBadScheduleError(origin, target) 200 } 201 202 if origin.Owner != target.Owner { 203 // NOTE: owner id can be changed only when the old owner is removed. 204 if origin.OwnerState == SchedRemoved && target.OwnerState == SchedLaunched { 205 return nil 206 } 207 return NewBadScheduleError(origin, target) 208 } 209 210 switch origin.OwnerState { 211 case SchedRemoved: 212 // case 1: SchedRemoved -> SchedLaunched, this means a changefeed owner is rescheduled 213 // to the same capture. 214 if target.OwnerState != SchedLaunched { 215 return NewBadScheduleError(origin, target) 216 } 217 case SchedLaunched: 218 // case 1: SchedLaunched -> SchedRemoving, this is the normal scenario. 219 // case 2: SchedLaunched -> SchedRemoved, this is the capture offline scenario. 220 return nil 221 case SchedRemoving: 222 if target.OwnerState != SchedRemoved { 223 return NewBadScheduleError(origin, target) 224 } 225 } 226 return nil 227 } 228 229 // DiffScheduledChangefeeds gets difference between origin and target. 230 // 231 // Both origin and target should be sorted by the given rule. 232 func DiffScheduledChangefeeds( 233 origin, target []ScheduledChangefeed, 234 sortedBy func(a, b ScheduledChangefeed) int, 235 ) ([]ScheduledChangefeed, error) { 236 badSchedule := func() error { 237 originStrs := make([]string, 0, len(origin)) 238 targetStrs := make([]string, 0, len(target)) 239 for _, s := range origin { 240 originStrs = append(originStrs, s.toString()) 241 } 242 for _, s := range target { 243 targetStrs = append(targetStrs, s.toString()) 244 } 245 msg := fmt.Sprintf("bad schedule: [%s]->[%s]", strings.Join(originStrs, ","), strings.Join(targetStrs, ",")) 246 return NewScheduleError(msg) 247 } 248 249 var diffs []ScheduledChangefeed 250 if len(origin) <= len(target) { 251 diffs = make([]ScheduledChangefeed, 0, len(target)) 252 } else { 253 diffs = make([]ScheduledChangefeed, 0, len(origin)) 254 } 255 256 for i, j := 0, 0; i < len(origin) && j < len(target); { 257 if i == len(origin) || sortedBy(origin[i], target[j]) > 0 { 258 unexist := target[j] 259 unexist.OwnerState = SchedRemoved 260 if err := CheckScheduleState(unexist, target[j]); err != nil { 261 return nil, badSchedule() 262 } 263 diffs = append(diffs, target[j]) 264 j += 1 265 } else if j == len(target) || sortedBy(origin[i], target[j]) < 0 { 266 unexist := origin[i] 267 unexist.OwnerState = SchedRemoved 268 if err := CheckScheduleState(origin[i], unexist); err != nil { 269 return nil, badSchedule() 270 } 271 diffs = append(diffs, unexist) 272 i += 1 273 } else { 274 if origin[i].OwnerState != target[j].OwnerState { 275 if err := CheckScheduleState(origin[i], target[j]); err != nil { 276 return nil, badSchedule() 277 } 278 diffs = append(diffs, target[j]) 279 } 280 i += 1 281 j += 1 282 } 283 } 284 return diffs, nil 285 } 286 287 func (s ScheduledChangefeed) toString() string { 288 return fmt.Sprintf("%s.%s", *s.Owner, s.OwnerState.String()) 289 }