github.com/pingcap/ticdc@v0.0.0-20220526033649-485a10ef2652/cdc/sink/common/common.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 common 15 16 import ( 17 "sort" 18 "sync" 19 "sync/atomic" 20 21 "github.com/pingcap/log" 22 "github.com/pingcap/ticdc/cdc/model" 23 "github.com/pingcap/ticdc/pkg/filter" 24 "go.uber.org/zap" 25 ) 26 27 type txnsWithTheSameCommitTs struct { 28 txns map[model.Ts]*model.SingleTableTxn 29 commitTs model.Ts 30 } 31 32 func (t *txnsWithTheSameCommitTs) Append(row *model.RowChangedEvent) { 33 if row.CommitTs != t.commitTs { 34 log.Panic("unexpected row change event", 35 zap.Uint64("commitTs of txn", t.commitTs), 36 zap.Any("row", row)) 37 } 38 if t.txns == nil { 39 t.txns = make(map[model.Ts]*model.SingleTableTxn) 40 } 41 txn, exist := t.txns[row.StartTs] 42 if !exist { 43 txn = &model.SingleTableTxn{ 44 StartTs: row.StartTs, 45 CommitTs: row.CommitTs, 46 Table: row.Table, 47 ReplicaID: row.ReplicaID, 48 } 49 t.txns[row.StartTs] = txn 50 } 51 txn.Append(row) 52 } 53 54 // UnresolvedTxnCache caches unresolved txns 55 type UnresolvedTxnCache struct { 56 unresolvedTxnsMu sync.Mutex 57 unresolvedTxns map[model.TableID][]*txnsWithTheSameCommitTs 58 checkpointTs uint64 59 } 60 61 // NewUnresolvedTxnCache returns a new UnresolvedTxnCache 62 func NewUnresolvedTxnCache() *UnresolvedTxnCache { 63 return &UnresolvedTxnCache{ 64 unresolvedTxns: make(map[model.TableID][]*txnsWithTheSameCommitTs), 65 } 66 } 67 68 // Append adds unresolved rows to cache 69 // the rows inputed into this function will go through the following handling logic 70 // 1. group by tableID from one input stream 71 // 2. for each tableID stream, the callers of this function should **make sure** that the CommitTs of rows is **strictly increasing** 72 // 3. group by CommitTs, according to CommitTs cut the rows into many group of rows in the same CommitTs 73 // 4. group by StartTs, cause the StartTs is the unique identifier of the transaction, according to StartTs cut the rows into many txns 74 func (c *UnresolvedTxnCache) Append(filter *filter.Filter, rows ...*model.RowChangedEvent) int { 75 c.unresolvedTxnsMu.Lock() 76 defer c.unresolvedTxnsMu.Unlock() 77 appendRows := 0 78 for _, row := range rows { 79 if filter != nil && filter.ShouldIgnoreDMLEvent(row.StartTs, row.Table.Schema, row.Table.Table) { 80 log.Info("Row changed event ignored", zap.Uint64("start-ts", row.StartTs)) 81 continue 82 } 83 txns := c.unresolvedTxns[row.Table.TableID] 84 if len(txns) == 0 || txns[len(txns)-1].commitTs != row.CommitTs { 85 // fail-fast check 86 if len(txns) != 0 && txns[len(txns)-1].commitTs > row.CommitTs { 87 log.Panic("the commitTs of the emit row is less than the received row", 88 zap.Stringer("table", row.Table), 89 zap.Uint64("emit row startTs", row.StartTs), 90 zap.Uint64("emit row commitTs", row.CommitTs), 91 zap.Uint64("last received row commitTs", txns[len(txns)-1].commitTs)) 92 } 93 txns = append(txns, &txnsWithTheSameCommitTs{ 94 commitTs: row.CommitTs, 95 }) 96 c.unresolvedTxns[row.Table.TableID] = txns 97 } 98 txns[len(txns)-1].Append(row) 99 appendRows++ 100 } 101 return appendRows 102 } 103 104 // Resolved returns resolved txns according to resolvedTs 105 // The returned map contains many txns grouped by tableID. for each table, the each commitTs of txn in txns slice is strictly increasing 106 func (c *UnresolvedTxnCache) Resolved(resolvedTs uint64) map[model.TableID][]*model.SingleTableTxn { 107 if resolvedTs <= atomic.LoadUint64(&c.checkpointTs) { 108 return nil 109 } 110 111 c.unresolvedTxnsMu.Lock() 112 defer c.unresolvedTxnsMu.Unlock() 113 if len(c.unresolvedTxns) == 0 { 114 return nil 115 } 116 117 _, resolvedTxnsMap := splitResolvedTxn(resolvedTs, c.unresolvedTxns) 118 return resolvedTxnsMap 119 } 120 121 // UpdateCheckpoint updates the checkpoint ts 122 func (c *UnresolvedTxnCache) UpdateCheckpoint(checkpointTs uint64) { 123 atomic.StoreUint64(&c.checkpointTs, checkpointTs) 124 } 125 126 func splitResolvedTxn( 127 resolvedTs uint64, unresolvedTxns map[model.TableID][]*txnsWithTheSameCommitTs, 128 ) (minTs uint64, resolvedRowsMap map[model.TableID][]*model.SingleTableTxn) { 129 resolvedRowsMap = make(map[model.TableID][]*model.SingleTableTxn, len(unresolvedTxns)) 130 minTs = resolvedTs 131 for tableID, txns := range unresolvedTxns { 132 i := sort.Search(len(txns), func(i int) bool { 133 return txns[i].commitTs > resolvedTs 134 }) 135 if i == 0 { 136 continue 137 } 138 var resolvedTxnsWithTheSameCommitTs []*txnsWithTheSameCommitTs 139 if i == len(txns) { 140 resolvedTxnsWithTheSameCommitTs = txns 141 delete(unresolvedTxns, tableID) 142 } else { 143 resolvedTxnsWithTheSameCommitTs = txns[:i] 144 unresolvedTxns[tableID] = txns[i:] 145 } 146 var txnsLength int 147 for _, txns := range resolvedTxnsWithTheSameCommitTs { 148 txnsLength += len(txns.txns) 149 } 150 resolvedTxns := make([]*model.SingleTableTxn, 0, txnsLength) 151 for _, txns := range resolvedTxnsWithTheSameCommitTs { 152 for _, txn := range txns.txns { 153 resolvedTxns = append(resolvedTxns, txn) 154 } 155 } 156 resolvedRowsMap[tableID] = resolvedTxns 157 if len(resolvedTxnsWithTheSameCommitTs) > 0 && resolvedTxnsWithTheSameCommitTs[0].commitTs < minTs { 158 minTs = resolvedTxnsWithTheSameCommitTs[0].commitTs 159 } 160 } 161 return 162 }