github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdcv2/metadata/sql/client.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 sql 15 16 import ( 17 "context" 18 "database/sql" 19 "sync" 20 "time" 21 22 "github.com/pingcap/log" 23 "github.com/pingcap/tiflow/cdc/model" 24 "github.com/pingcap/tiflow/cdcv2/metadata" 25 "go.uber.org/zap" 26 "gorm.io/gorm" 27 ) 28 29 const defaultMaxExecTime = 5 * time.Second 30 31 type client[T TxnContext] interface { 32 LeaderChecker[T] 33 checker[T] 34 } 35 36 type clientImpl[T TxnContext] struct { 37 LeaderChecker[T] 38 checker[T] 39 40 options *clientOptions 41 } 42 43 func newClient[T TxnContext]( 44 leaderChecker LeaderChecker[T], 45 checker checker[T], 46 opts ...ClientOptionFunc, 47 ) client[T] { 48 options := setClientOptions(opts...) 49 50 return &clientImpl[T]{ 51 LeaderChecker: leaderChecker, 52 checker: checker, 53 options: options, 54 } 55 } 56 57 func (c *clientImpl[T]) withTimeout(ctx context.Context) (context.Context, context.CancelFunc) { 58 if c.options.maxExecTime <= 0 { 59 return ctx, nil 60 } 61 return context.WithTimeout(ctx, c.options.maxExecTime) 62 } 63 64 func (c *clientImpl[T]) Txn(ctx context.Context, fn TxnAction[T]) error { 65 ctx, cancel := c.withTimeout(ctx) 66 if cancel != nil { 67 defer cancel() 68 } 69 return c.checker.Txn(ctx, fn) 70 } 71 72 func (c *clientImpl[T]) TxnWithOwnerLock(ctx context.Context, uuid metadata.ChangefeedUUID, fn TxnAction[T]) error { 73 ctx, cancel := c.withTimeout(ctx) 74 if cancel != nil { 75 defer cancel() 76 } 77 return c.checker.TxnWithOwnerLock(ctx, uuid, fn) 78 } 79 80 func (c *clientImpl[T]) TxnWithLeaderLock(ctx context.Context, leaderID string, fn func(T) error) error { 81 ctx, cancel := c.withTimeout(ctx) 82 if cancel != nil { 83 defer cancel() 84 } 85 return c.LeaderChecker.TxnWithLeaderLock(ctx, leaderID, fn) 86 } 87 88 // TxnContext is a type set that can be used as the transaction context. 89 type TxnContext interface { 90 *gorm.DB | *sql.Tx 91 } 92 93 // TxnAction is a series of operations that can be executed in a transaction and the 94 // generic type T represents the transaction context. 95 // 96 // Note that in the current implementation the metadata operation and leader check are 97 // always in the same transaction context. 98 type TxnAction[T TxnContext] func(T) error 99 100 // ormTxnAction represents a transaction action that uses gorm.DB as the transaction context. 101 type ormTxnAction = TxnAction[*gorm.DB] 102 103 // sqlTxnAction represents a transaction action that uses sql.Tx as the transaction context. 104 // Note that sqlTxnAction is not implemented yet, it is reserved for future use. 105 // 106 //nolint:unused 107 type sqlTxnAction = TxnAction[*sql.Tx] 108 109 // LeaderChecker enables the controller to ensure its leadership during a series of actions. 110 type LeaderChecker[T TxnContext] interface { 111 TxnWithLeaderLock(ctx context.Context, leaderID string, fn func(T) error) error 112 } 113 114 type checker[T TxnContext] interface { 115 Txn(ctx context.Context, fn TxnAction[T]) error 116 TxnWithOwnerLock(ctx context.Context, uuid metadata.ChangefeedUUID, fn TxnAction[T]) error 117 118 upstreamClient[T] 119 changefeedInfoClient[T] 120 changefeedStateClient[T] 121 scheduleClient[T] 122 progressClient[T] 123 } 124 125 // TODO(CharlesCheung): only update changed fields to reduce the pressure on io and database. 126 type upstreamClient[T TxnContext] interface { 127 createUpstream(tx T, up *UpstreamDO) error 128 deleteUpstream(tx T, up *UpstreamDO) error 129 updateUpstream(tx T, up *UpstreamDO) error 130 queryUpstreams(tx T) ([]*UpstreamDO, error) 131 queryUpstreamsByUpdateAt(tx T, lastUpdateAt time.Time) ([]*UpstreamDO, error) 132 queryUpstreamByID(tx T, id uint64) (*UpstreamDO, error) 133 } 134 135 type changefeedInfoClient[T TxnContext] interface { 136 createChangefeedInfo(tx T, info *ChangefeedInfoDO) error 137 deleteChangefeedInfo(tx T, info *ChangefeedInfoDO) error 138 markChangefeedRemoved(tx T, info *ChangefeedInfoDO) error 139 updateChangefeedInfo(tx T, info *ChangefeedInfoDO) error 140 queryChangefeedInfos(tx T) ([]*ChangefeedInfoDO, error) 141 queryChangefeedInfosByUpdateAt(tx T, lastUpdateAt time.Time) ([]*ChangefeedInfoDO, error) 142 queryChangefeedInfosByUUIDs(tx T, uuids ...uint64) ([]*ChangefeedInfoDO, error) 143 queryChangefeedInfoByUUID(tx T, uuid uint64) (*ChangefeedInfoDO, error) 144 } 145 146 type changefeedStateClient[T TxnContext] interface { 147 createChangefeedState(tx T, state *ChangefeedStateDO) error 148 deleteChangefeedState(tx T, state *ChangefeedStateDO) error 149 updateChangefeedState(tx T, state *ChangefeedStateDO) error 150 queryChangefeedStates(tx T) ([]*ChangefeedStateDO, error) 151 queryChangefeedStatesByUpdateAt(tx T, lastUpdateAt time.Time) ([]*ChangefeedStateDO, error) 152 queryChangefeedStateByUUID(tx T, uuid uint64) (*ChangefeedStateDO, error) 153 queryChangefeedStateByUUIDs(tx T, uuid ...uint64) ([]*ChangefeedStateDO, error) 154 queryChangefeedStateByUUIDWithLock(tx T, uuid uint64) (*ChangefeedStateDO, error) 155 } 156 157 type scheduleClient[T TxnContext] interface { 158 createSchedule(tx T, sc *ScheduleDO) error 159 deleteSchedule(tx T, sc *ScheduleDO) error 160 updateSchedule(tx T, sc *ScheduleDO) error 161 updateScheduleOwnerState(tx T, sc *ScheduleDO) error 162 updateScheduleOwnerStateByOwnerID(tx T, state metadata.SchedState, ownerID model.CaptureID) error 163 querySchedules(tx T) ([]*ScheduleDO, error) 164 querySchedulesByUpdateAt(tx T, lastUpdateAt time.Time) ([]*ScheduleDO, error) 165 querySchedulesByOwnerIDAndUpdateAt(tx T, captureID model.CaptureID, lastUpdateAt time.Time) ([]*ScheduleDO, error) 166 queryScheduleByUUID(tx T, uuid uint64) (*ScheduleDO, error) 167 querySchedulesUinqueOwnerIDs(tx T) ([]model.CaptureID, error) 168 } 169 170 type progressClient[T TxnContext] interface { 171 createProgress(tx T, pr *ProgressDO) error 172 deleteProgress(tx T, pr *ProgressDO) error 173 updateProgress(tx T, pr *ProgressDO) error 174 queryProgresses(tx T) ([]*ProgressDO, error) 175 queryProgressesByUpdateAt(tx T, lastUpdateAt time.Time) ([]*ProgressDO, error) 176 queryProgressByCaptureID(tx T, id string) (*ProgressDO, error) 177 queryProgressByCaptureIDsWithLock(tx T, ids []string) ([]*ProgressDO, error) 178 } 179 180 type clientOptions struct { 181 maxExecTime time.Duration 182 } 183 184 func setClientOptions(opts ...ClientOptionFunc) *clientOptions { 185 o := &clientOptions{ 186 maxExecTime: defaultMaxExecTime, 187 } 188 for _, opt := range opts { 189 opt(o) 190 } 191 return o 192 } 193 194 // ClientOptionFunc is the option function for the client. 195 type ClientOptionFunc func(*clientOptions) 196 197 // WithMaxExecTime sets the maximum execution time of the client. 198 func WithMaxExecTime(d time.Duration) ClientOptionFunc { 199 return func(o *clientOptions) { 200 o.maxExecTime = d 201 } 202 } 203 204 // TODO(CharlesCheung): implement a cache layer to reduce the pressure on io and database. 205 // nolint:unused 206 type clientWithCache[T TxnContext] struct { 207 c client[T] 208 cache *storage 209 } 210 211 type versionedRecord[K uint64 | string] interface { 212 GetKey() K 213 GetVersion() uint64 214 GetUpdateAt() time.Time 215 } 216 217 type entity[K uint64 | string, V versionedRecord[K]] struct { 218 sync.RWMutex 219 // maxExecTime is the maximum insert/update time of the entity. Then, it can be 220 // determined that all data before `lastUpdateAt-maxExecTime` has been pulled locally. 221 maxExecTime time.Duration 222 lastUpdateAt time.Time 223 224 // the data already cached locally. 225 m map[K]V 226 } 227 228 func newEntity[K uint64 | string, V versionedRecord[K]](maxExecTime time.Duration) *entity[K, V] { 229 return &entity[K, V]{ 230 maxExecTime: maxExecTime, 231 lastUpdateAt: time.Time{}, 232 m: make(map[K]V), 233 } 234 } 235 236 // getSafePoint returns the most recent safe timestamp, before which all data has 237 // been pulled locally. 238 func (e *entity[K, V]) getSafePoint() time.Time { 239 e.RLock() 240 defer e.RUnlock() 241 242 if len(e.m) == 0 { 243 // if there is no data, it means that the entity has not been initialized. 244 return time.Time{} 245 } 246 return e.lastUpdateAt.Truncate(e.maxExecTime) 247 } 248 249 // get returns the value of the key. 250 func (e *entity[K, V]) get(key K) V { 251 e.RLock() 252 defer e.RUnlock() 253 254 return e.m[key] 255 } 256 257 // doUpsert inserts or updates the entity upon the incoming data, and 258 // run the onchange callback if data is changed. 259 func (e *entity[K, V]) doUpsert( 260 inComming []V, 261 onchange func(newV V) (skip bool), 262 ) { 263 e.Lock() 264 defer e.Unlock() 265 for _, newV := range inComming { 266 key := newV.GetKey() 267 oldV, ok := e.m[key] 268 if ok { 269 // check the version and update_at consistency. 270 versionEqual := oldV.GetVersion() == newV.GetVersion() 271 updateAtEqual := oldV.GetUpdateAt() == newV.GetUpdateAt() 272 if versionEqual != updateAtEqual { 273 log.Panic("bad version and update_at", zap.Any("old", oldV), zap.Any("new", newV)) 274 } 275 } 276 277 if !ok || oldV.GetVersion() < newV.GetVersion() { 278 if onchange != nil && onchange(newV) { 279 log.Debug("skip update or insert", zap.Any("old", oldV), zap.Any("new", newV)) 280 continue 281 } 282 283 e.m[key] = newV 284 newUpdataAt := newV.GetUpdateAt() 285 if newUpdataAt.After(e.lastUpdateAt) { 286 e.lastUpdateAt = newUpdataAt 287 } 288 } 289 } 290 } 291 292 // upsert inserts or updates the entity with write lock. 293 // nolint:unused 294 func (e *entity[K, V]) upsert(inComming ...V) { 295 e.doUpsert(inComming, nil) 296 } 297 298 // nolint:unused 299 type storage struct { 300 // the key is the upstream id. 301 upsteram *entity[uint64, *UpstreamDO] 302 // the key is the changefeed uuid. 303 info *entity[metadata.ChangefeedUUID, *ChangefeedInfoDO] 304 // the key is the changefeed uuid. 305 state *entity[metadata.ChangefeedUUID, *ChangefeedStateDO] 306 // the key is the changefeed uuid. 307 schedule *entity[metadata.ChangefeedUUID, *ScheduleDO] 308 // the key is the capture id. 309 progress *entity[model.CaptureID, *ProgressDO] 310 }