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 }