github.com/pingcap/ticdc@v0.0.0-20220526033649-485a10ef2652/cdc/sink/causality.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 sink
    15  
    16  import (
    17  	"encoding/binary"
    18  
    19  	"github.com/pingcap/log"
    20  	"go.uber.org/zap"
    21  
    22  	"github.com/pingcap/ticdc/cdc/model"
    23  )
    24  
    25  // causality provides a simple mechanism to improve the concurrency of SQLs execution under the premise of ensuring correctness.
    26  // causality groups sqls that maybe contain causal relationships, and syncer executes them linearly.
    27  // if some conflicts exist in more than one groups, then syncer waits all SQLs that are grouped be executed and reset causality.
    28  // this mechanism meets quiescent consistency to ensure correctness.
    29  type causality struct {
    30  	relations map[string]int
    31  }
    32  
    33  func newCausality() *causality {
    34  	return &causality{
    35  		relations: make(map[string]int),
    36  	}
    37  }
    38  
    39  func (c *causality) add(keys [][]byte, idx int) {
    40  	if len(keys) == 0 {
    41  		return
    42  	}
    43  
    44  	for _, key := range keys {
    45  		c.relations[string(key)] = idx
    46  	}
    47  }
    48  
    49  func (c *causality) reset() {
    50  	c.relations = make(map[string]int)
    51  }
    52  
    53  // detectConflict detects whether there is a conflict
    54  func (c *causality) detectConflict(keys [][]byte) (bool, int) {
    55  	if len(keys) == 0 {
    56  		return false, 0
    57  	}
    58  
    59  	firstIdx := -1
    60  	for _, key := range keys {
    61  		if idx, ok := c.relations[string(key)]; ok {
    62  			if firstIdx == -1 {
    63  				firstIdx = idx
    64  			} else if firstIdx != idx {
    65  				return true, -1
    66  			}
    67  		}
    68  	}
    69  
    70  	return firstIdx != -1, firstIdx
    71  }
    72  
    73  func genTxnKeys(txn *model.SingleTableTxn) [][]byte {
    74  	if len(txn.Rows) == 0 {
    75  		return nil
    76  	}
    77  	keysSet := make(map[string]struct{}, len(txn.Rows))
    78  	for _, row := range txn.Rows {
    79  		rowKeys := genRowKeys(row)
    80  		for _, key := range rowKeys {
    81  			keysSet[string(key)] = struct{}{}
    82  		}
    83  	}
    84  	keys := make([][]byte, 0, len(keysSet))
    85  	for key := range keysSet {
    86  		keys = append(keys, []byte(key))
    87  	}
    88  	return keys
    89  }
    90  
    91  func genRowKeys(row *model.RowChangedEvent) [][]byte {
    92  	var keys [][]byte
    93  	if len(row.Columns) != 0 {
    94  		for iIdx, idxCol := range row.IndexColumns {
    95  			key := genKeyList(row.Columns, iIdx, idxCol, row.Table.TableID)
    96  			if len(key) == 0 {
    97  				continue
    98  			}
    99  			keys = append(keys, key)
   100  		}
   101  	}
   102  	if len(row.PreColumns) != 0 {
   103  		for iIdx, idxCol := range row.IndexColumns {
   104  			key := genKeyList(row.PreColumns, iIdx, idxCol, row.Table.TableID)
   105  			if len(key) == 0 {
   106  				continue
   107  			}
   108  			keys = append(keys, key)
   109  		}
   110  	}
   111  	if len(keys) == 0 {
   112  		// use table ID as key if no key generated (no PK/UK),
   113  		// no concurrence for rows in the same table.
   114  		log.Debug("use table id as the key", zap.Int64("tableID", row.Table.TableID))
   115  		tableKey := make([]byte, 8)
   116  		binary.BigEndian.PutUint64(tableKey, uint64(row.Table.TableID))
   117  		keys = [][]byte{tableKey}
   118  	}
   119  	return keys
   120  }
   121  
   122  func genKeyList(columns []*model.Column, iIdx int, colIdx []int, tableID int64) []byte {
   123  	var key []byte
   124  	for _, i := range colIdx {
   125  		// if a column value is null, we can ignore this index
   126  		// If the index contain generated column, we can't use this key to detect conflict with other DML,
   127  		// Because such as insert can't specified the generated value.
   128  		if columns[i] == nil || columns[i].Value == nil || columns[i].Flag.IsGeneratedColumn() {
   129  			return nil
   130  		}
   131  		key = append(key, []byte(model.ColumnValueString(columns[i].Value))...)
   132  		key = append(key, 0)
   133  	}
   134  	if len(key) == 0 {
   135  		return nil
   136  	}
   137  	tableKey := make([]byte, 16)
   138  	binary.BigEndian.PutUint64(tableKey[:8], uint64(iIdx))
   139  	binary.BigEndian.PutUint64(tableKey[8:], uint64(tableID))
   140  	key = append(key, tableKey...)
   141  	return key
   142  }