github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/sink/dmlsink/txn/txn_dml_sink.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 txn 15 16 import ( 17 "context" 18 "net/url" 19 "sync" 20 21 "github.com/pingcap/errors" 22 "github.com/pingcap/tiflow/cdc/model" 23 "github.com/pingcap/tiflow/cdc/sink/dmlsink" 24 "github.com/pingcap/tiflow/cdc/sink/dmlsink/txn/mysql" 25 "github.com/pingcap/tiflow/cdc/sink/metrics" 26 "github.com/pingcap/tiflow/cdc/sink/tablesink/state" 27 "github.com/pingcap/tiflow/pkg/causality" 28 "github.com/pingcap/tiflow/pkg/config" 29 "github.com/pingcap/tiflow/pkg/sink" 30 pmysql "github.com/pingcap/tiflow/pkg/sink/mysql" 31 "golang.org/x/sync/errgroup" 32 ) 33 34 const ( 35 // DefaultConflictDetectorSlots indicates the default slot count of conflict detector. 36 DefaultConflictDetectorSlots uint64 = 16 * 1024 37 ) 38 39 // Assert EventSink[E event.TableEvent] implementation 40 var _ dmlsink.EventSink[*model.SingleTableTxn] = (*dmlSink)(nil) 41 42 // dmlSink is the dmlSink for SingleTableTxn. 43 type dmlSink struct { 44 alive struct { 45 sync.RWMutex 46 conflictDetector *causality.ConflictDetector[*txnEvent] 47 isDead bool 48 } 49 50 workers []*worker 51 cancel func() 52 53 wg sync.WaitGroup 54 dead chan struct{} 55 56 statistics *metrics.Statistics 57 58 scheme string 59 } 60 61 // GetDBConnImpl is the implementation of pmysql.Factory. 62 // Exported for testing. 63 // Maybe we can use a better way to do this. Because this is not thread-safe. 64 // You can use `SetupSuite` and `TearDownSuite` to do this to get a better way. 65 var GetDBConnImpl pmysql.Factory = pmysql.CreateMySQLDBConn 66 67 // NewMySQLSink creates a mysql dmlSink with given parameters. 68 func NewMySQLSink( 69 ctx context.Context, 70 changefeedID model.ChangeFeedID, 71 sinkURI *url.URL, 72 replicaConfig *config.ReplicaConfig, 73 errCh chan<- error, 74 conflictDetectorSlots uint64, 75 ) (*dmlSink, error) { 76 ctx, cancel := context.WithCancel(ctx) 77 statistics := metrics.NewStatistics(ctx, changefeedID, sink.TxnSink) 78 79 backendImpls, err := mysql.NewMySQLBackends(ctx, changefeedID, sinkURI, replicaConfig, GetDBConnImpl, statistics) 80 if err != nil { 81 cancel() 82 return nil, err 83 } 84 85 backends := make([]backend, 0, len(backendImpls)) 86 for _, impl := range backendImpls { 87 backends = append(backends, impl) 88 } 89 90 s := newSink(ctx, changefeedID, backends, errCh, conflictDetectorSlots) 91 s.statistics = statistics 92 s.cancel = cancel 93 s.scheme = sink.GetScheme(sinkURI) 94 95 return s, nil 96 } 97 98 func newSink(ctx context.Context, 99 changefeedID model.ChangeFeedID, 100 backends []backend, 101 errCh chan<- error, conflictDetectorSlots uint64, 102 ) *dmlSink { 103 ctx, cancel := context.WithCancel(ctx) 104 sink := &dmlSink{ 105 workers: make([]*worker, 0, len(backends)), 106 cancel: cancel, 107 dead: make(chan struct{}), 108 } 109 110 sink.alive.conflictDetector = causality.NewConflictDetector[*txnEvent](conflictDetectorSlots, causality.TxnCacheOption{ 111 Count: len(backends), 112 Size: 1024, 113 BlockStrategy: causality.BlockStrategyWaitEmpty, 114 }) 115 116 g, ctx1 := errgroup.WithContext(ctx) 117 for i, backend := range backends { 118 w := newWorker(ctx1, changefeedID, i, backend, len(backends)) 119 txnCh := sink.alive.conflictDetector.GetOutChByCacheID(int64(i)) 120 g.Go(func() error { return w.runLoop(txnCh) }) 121 sink.workers = append(sink.workers, w) 122 } 123 124 sink.wg.Add(1) 125 go func() { 126 defer sink.wg.Done() 127 err := g.Wait() 128 129 sink.alive.Lock() 130 sink.alive.isDead = true 131 sink.alive.conflictDetector.Close() 132 sink.alive.Unlock() 133 close(sink.dead) 134 135 if err != nil && errors.Cause(err) != context.Canceled { 136 select { 137 case <-ctx.Done(): 138 case errCh <- err: 139 } 140 } 141 }() 142 143 return sink 144 } 145 146 // WriteEvents writes events to the dmlSink. 147 func (s *dmlSink) WriteEvents(txnEvents ...*dmlsink.TxnCallbackableEvent) error { 148 s.alive.RLock() 149 defer s.alive.RUnlock() 150 if s.alive.isDead { 151 return errors.Trace(errors.New("dead dmlSink")) 152 } 153 154 for _, txn := range txnEvents { 155 if txn.GetTableSinkState() != state.TableSinkSinking { 156 // The table where the event comes from is in stopping, so it's safe 157 // to drop the event directly. 158 txn.Callback() 159 continue 160 } 161 s.alive.conflictDetector.Add(newTxnEvent(txn)) 162 } 163 return nil 164 } 165 166 // Close closes the dmlSink. It won't wait for all pending items backend handled. 167 func (s *dmlSink) Close() { 168 if s.cancel != nil { 169 s.cancel() 170 } 171 s.wg.Wait() 172 173 if s.statistics != nil { 174 s.statistics.Close() 175 } 176 } 177 178 // Dead checks whether it's dead or not. 179 func (s *dmlSink) Dead() <-chan struct{} { 180 return s.dead 181 } 182 183 func (s *dmlSink) Scheme() string { 184 return s.scheme 185 }