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  }