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  }