github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/scheduler/internal/v3/compat/compat.go (about)

     1  // Copyright 2022 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 compat
    15  
    16  import (
    17  	"github.com/coreos/go-semver/semver"
    18  	"github.com/pingcap/tiflow/cdc/model"
    19  	"github.com/pingcap/tiflow/cdc/processor/tablepb"
    20  	"github.com/pingcap/tiflow/cdc/scheduler/schedulepb"
    21  	"github.com/pingcap/tiflow/pkg/config"
    22  	"github.com/pingcap/tiflow/pkg/spanz"
    23  	"github.com/pingcap/tiflow/pkg/version"
    24  )
    25  
    26  var (
    27  	// SpanReplicationMinVersion is the min version that allows span replication.
    28  	SpanReplicationMinVersion = semver.New("6.6.0-alpha")
    29  	// ChangefeedEpochMinVersion is the min version that enables changefeed epoch.
    30  	ChangefeedEpochMinVersion = semver.New("6.7.0-alpha")
    31  )
    32  
    33  // Compat is a compatibility layer between span replication and table replication.
    34  type Compat struct {
    35  	config      *config.ChangefeedSchedulerConfig
    36  	captureInfo map[model.CaptureID]*model.CaptureInfo
    37  
    38  	spanReplicationHasChecked bool
    39  	spanReplicationEnabled    bool
    40  	changefeedEpoch           map[model.CaptureID]bool
    41  }
    42  
    43  // New returns a new Compat.
    44  func New(
    45  	config *config.SchedulerConfig,
    46  	captureInfo map[model.CaptureID]*model.CaptureInfo,
    47  ) *Compat {
    48  	return &Compat{
    49  		config:          config.ChangefeedSettings,
    50  		captureInfo:     captureInfo,
    51  		changefeedEpoch: make(map[string]bool),
    52  	}
    53  }
    54  
    55  // UpdateCaptureInfo update the latest alive capture info.
    56  // Returns true if capture info has changed.
    57  func (c *Compat) UpdateCaptureInfo(
    58  	aliveCaptures map[model.CaptureID]*model.CaptureInfo,
    59  ) bool {
    60  	if len(aliveCaptures) != len(c.captureInfo) {
    61  		c.captureInfo = aliveCaptures
    62  		c.spanReplicationHasChecked = false
    63  		c.changefeedEpoch = make(map[string]bool, len(aliveCaptures))
    64  		return true
    65  	}
    66  	for id, alive := range aliveCaptures {
    67  		info, ok := c.captureInfo[id]
    68  		if !ok || info.Version != alive.Version {
    69  			c.captureInfo = aliveCaptures
    70  			c.spanReplicationHasChecked = false
    71  			c.changefeedEpoch = make(map[string]bool, len(aliveCaptures))
    72  			return true
    73  		}
    74  	}
    75  	return false
    76  }
    77  
    78  // CheckSpanReplicationEnabled check if the changefeed can enable span replication.
    79  func (c *Compat) CheckSpanReplicationEnabled() bool {
    80  	if c.spanReplicationHasChecked {
    81  		return c.spanReplicationEnabled
    82  	}
    83  	c.spanReplicationHasChecked = true
    84  
    85  	c.spanReplicationEnabled = c.config.EnableTableAcrossNodes
    86  	for _, capture := range c.captureInfo {
    87  		if len(capture.Version) == 0 {
    88  			c.spanReplicationEnabled = false
    89  			break
    90  		}
    91  		captureVer := semver.New(version.SanitizeVersion(capture.Version))
    92  		if captureVer.Compare(*SpanReplicationMinVersion) < 0 {
    93  			c.spanReplicationEnabled = false
    94  			break
    95  		}
    96  	}
    97  
    98  	return c.spanReplicationEnabled
    99  }
   100  
   101  // CheckChangefeedEpochEnabled check if the changefeed enables epoch.
   102  func (c *Compat) CheckChangefeedEpochEnabled(captureID model.CaptureID) bool {
   103  	isEnabled, ok := c.changefeedEpoch[captureID]
   104  	if ok {
   105  		return isEnabled
   106  	}
   107  
   108  	captureInfo, ok := c.captureInfo[captureID]
   109  	if !ok {
   110  		return false
   111  	}
   112  	if len(captureInfo.Version) != 0 {
   113  		captureVer := semver.New(version.SanitizeVersion(captureInfo.Version))
   114  		isEnabled = captureVer.Compare(*ChangefeedEpochMinVersion) >= 0
   115  	} else {
   116  		isEnabled = false
   117  	}
   118  	c.changefeedEpoch[captureID] = isEnabled
   119  	return isEnabled
   120  }
   121  
   122  // BeforeTransportSend modifies messages in place before sending messages,
   123  // makes messages compatible with other end.
   124  func (c *Compat) BeforeTransportSend(msgs []*schedulepb.Message) {
   125  	if c.CheckSpanReplicationEnabled() {
   126  		return
   127  	}
   128  
   129  	// - span agent -> table scheduler
   130  	//   - tableID = span.TableID
   131  	// - span scheduler -> table agent
   132  	//   - tableID = span.TableID
   133  	for i := range msgs {
   134  		switch msgs[i].MsgType {
   135  		case schedulepb.MsgDispatchTableRequest:
   136  			switch req := msgs[i].DispatchTableRequest.Request.(type) {
   137  			case *schedulepb.DispatchTableRequest_AddTable:
   138  				req.AddTable.TableID = req.AddTable.Span.TableID
   139  			case *schedulepb.DispatchTableRequest_RemoveTable:
   140  				req.RemoveTable.TableID = req.RemoveTable.Span.TableID
   141  			}
   142  		case schedulepb.MsgDispatchTableResponse:
   143  			switch resp := msgs[i].DispatchTableResponse.Response.(type) {
   144  			case *schedulepb.DispatchTableResponse_AddTable:
   145  				resp.AddTable.Status.TableID = resp.AddTable.Status.Span.TableID
   146  			case *schedulepb.DispatchTableResponse_RemoveTable:
   147  				resp.RemoveTable.Status.TableID = resp.RemoveTable.Status.Span.TableID
   148  			}
   149  		case schedulepb.MsgHeartbeat:
   150  			tableIDs := make([]model.TableID, 0, len(msgs[i].Heartbeat.Spans))
   151  			for _, span := range msgs[i].Heartbeat.Spans {
   152  				tableIDs = append(tableIDs, span.TableID)
   153  			}
   154  			msgs[i].Heartbeat.TableIDs = tableIDs
   155  		case schedulepb.MsgHeartbeatResponse:
   156  			resp := msgs[i].HeartbeatResponse
   157  			for i := range resp.Tables {
   158  				resp.Tables[i].TableID = resp.Tables[i].Span.TableID
   159  			}
   160  		}
   161  	}
   162  }
   163  
   164  // AfterTransportReceive modifies messages in place after receiving messages,
   165  // makes messages compatible with other end.
   166  func (c *Compat) AfterTransportReceive(msgs []*schedulepb.Message) {
   167  	if c.CheckSpanReplicationEnabled() {
   168  		return
   169  	}
   170  
   171  	// - table scheduler -> span agent
   172  	//   - Fill span based on table ID if span is empty
   173  	// - table agent -> span scheduler
   174  	//   - Fill span based on table ID if span is empty
   175  	for i := range msgs {
   176  		switch msgs[i].MsgType {
   177  		case schedulepb.MsgDispatchTableRequest:
   178  			switch req := msgs[i].DispatchTableRequest.Request.(type) {
   179  			case *schedulepb.DispatchTableRequest_AddTable:
   180  				if req.AddTable.Span.TableID == 0 {
   181  					// Only set span if it is not set before.
   182  					req.AddTable.Span = spanz.TableIDToComparableSpan(
   183  						req.AddTable.TableID)
   184  				}
   185  			case *schedulepb.DispatchTableRequest_RemoveTable:
   186  				if req.RemoveTable.Span.TableID == 0 {
   187  					req.RemoveTable.Span = spanz.TableIDToComparableSpan(
   188  						req.RemoveTable.TableID)
   189  				}
   190  			}
   191  		case schedulepb.MsgDispatchTableResponse:
   192  			switch resp := msgs[i].DispatchTableResponse.Response.(type) {
   193  			case *schedulepb.DispatchTableResponse_AddTable:
   194  				if resp.AddTable.Status.Span.TableID == 0 {
   195  					resp.AddTable.Status.Span = spanz.TableIDToComparableSpan(
   196  						resp.AddTable.Status.TableID)
   197  				}
   198  			case *schedulepb.DispatchTableResponse_RemoveTable:
   199  				if resp.RemoveTable.Status.Span.TableID == 0 {
   200  					resp.RemoveTable.Status.Span = spanz.TableIDToComparableSpan(
   201  						resp.RemoveTable.Status.TableID)
   202  				}
   203  			}
   204  		case schedulepb.MsgHeartbeat:
   205  			if len(msgs[i].Heartbeat.Spans) == 0 {
   206  				spans := make([]tablepb.Span, 0, len(msgs[i].Heartbeat.TableIDs))
   207  				for _, tableID := range msgs[i].Heartbeat.TableIDs {
   208  					spans = append(spans, spanz.TableIDToComparableSpan(tableID))
   209  				}
   210  				msgs[i].Heartbeat.Spans = spans
   211  			}
   212  		case schedulepb.MsgHeartbeatResponse:
   213  			resp := msgs[i].HeartbeatResponse
   214  			for j := range resp.Tables {
   215  				if resp.Tables[j].Span.TableID == 0 {
   216  					resp.Tables[j].Span = spanz.TableIDToComparableSpan(
   217  						resp.Tables[j].TableID)
   218  				}
   219  			}
   220  		}
   221  	}
   222  }