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  }