github.com/pingcap/ticdc@v0.0.0-20220526033649-485a10ef2652/cdc/kv/region_worker_test.go (about) 1 // Copyright 2021 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 kv 15 16 import ( 17 "context" 18 "math/rand" 19 "runtime" 20 "sync" 21 "testing" 22 23 "github.com/pingcap/check" 24 "github.com/pingcap/kvproto/pkg/cdcpb" 25 "github.com/pingcap/ticdc/cdc/model" 26 "github.com/pingcap/ticdc/pkg/config" 27 "github.com/pingcap/ticdc/pkg/regionspan" 28 "github.com/pingcap/ticdc/pkg/util/testleak" 29 "github.com/pingcap/tidb/store/tikv" 30 "github.com/stretchr/testify/require" 31 ) 32 33 type regionWorkerSuite struct{} 34 35 var _ = check.Suite(®ionWorkerSuite{}) 36 37 func (s *regionWorkerSuite) TestRegionStateManager(c *check.C) { 38 defer testleak.AfterTest(c)() 39 rsm := newRegionStateManager(4) 40 41 regionID := uint64(1000) 42 _, ok := rsm.getState(regionID) 43 c.Assert(ok, check.IsFalse) 44 45 rsm.setState(regionID, ®ionFeedState{requestID: 2}) 46 state, ok := rsm.getState(regionID) 47 c.Assert(ok, check.IsTrue) 48 c.Assert(state.requestID, check.Equals, uint64(2)) 49 } 50 51 func (s *regionWorkerSuite) TestRegionStateManagerThreadSafe(c *check.C) { 52 defer testleak.AfterTest(c)() 53 rsm := newRegionStateManager(4) 54 regionCount := 100 55 regionIDs := make([]uint64, regionCount) 56 for i := 0; i < regionCount; i++ { 57 regionID := uint64(1000 + i) 58 regionIDs[i] = regionID 59 rsm.setState(regionID, ®ionFeedState{requestID: uint64(i + 1), lastResolvedTs: uint64(1000)}) 60 } 61 62 var wg sync.WaitGroup 63 concurrency := 20 64 wg.Add(concurrency * 2) 65 for j := 0; j < concurrency; j++ { 66 go func() { 67 defer wg.Done() 68 for i := 0; i < 10000; i++ { 69 idx := rand.Intn(regionCount) 70 regionID := regionIDs[idx] 71 s, ok := rsm.getState(regionID) 72 c.Assert(ok, check.IsTrue) 73 s.lock.RLock() 74 c.Assert(s.requestID, check.Equals, uint64(idx+1)) 75 s.lock.RUnlock() 76 } 77 }() 78 } 79 for j := 0; j < concurrency; j++ { 80 go func() { 81 defer wg.Done() 82 for i := 0; i < 10000; i++ { 83 // simulate write less than read 84 if i%5 != 0 { 85 continue 86 } 87 regionID := regionIDs[rand.Intn(regionCount)] 88 s, ok := rsm.getState(regionID) 89 c.Assert(ok, check.IsTrue) 90 s.lock.Lock() 91 s.lastResolvedTs += 10 92 s.lock.Unlock() 93 rsm.setState(regionID, s) 94 } 95 }() 96 } 97 wg.Wait() 98 99 totalResolvedTs := uint64(0) 100 for _, regionID := range regionIDs { 101 s, ok := rsm.getState(regionID) 102 c.Assert(ok, check.IsTrue) 103 c.Assert(s.lastResolvedTs, check.Greater, uint64(1000)) 104 totalResolvedTs += s.lastResolvedTs 105 } 106 // 100 regions, initial resolved ts 1000; 107 // 2000 * resolved ts forward, increased by 10 each time, routine number is `concurrency`. 108 c.Assert(totalResolvedTs, check.Equals, uint64(100*1000+2000*10*concurrency)) 109 } 110 111 func (s *regionWorkerSuite) TestRegionStateManagerBucket(c *check.C) { 112 defer testleak.AfterTest(c)() 113 rsm := newRegionStateManager(-1) 114 c.Assert(rsm.bucket, check.GreaterEqual, minRegionStateBucket) 115 c.Assert(rsm.bucket, check.LessEqual, maxRegionStateBucket) 116 117 bucket := rsm.bucket * 2 118 rsm = newRegionStateManager(bucket) 119 c.Assert(rsm.bucket, check.Equals, bucket) 120 } 121 122 func (s *regionWorkerSuite) TestRegionWorkerPoolSize(c *check.C) { 123 defer testleak.AfterTest(c)() 124 125 conf := config.GetDefaultServerConfig() 126 conf.KVClient.WorkerPoolSize = 0 127 config.StoreGlobalServerConfig(conf) 128 size := getWorkerPoolSize() 129 min := func(a, b int) int { 130 if a < b { 131 return a 132 } 133 return b 134 } 135 c.Assert(size, check.Equals, min(runtime.NumCPU()*2, maxWorkerPoolSize)) 136 137 conf.KVClient.WorkerPoolSize = 5 138 size = getWorkerPoolSize() 139 c.Assert(size, check.Equals, 5) 140 141 conf.KVClient.WorkerPoolSize = maxWorkerPoolSize + 1 142 size = getWorkerPoolSize() 143 c.Assert(size, check.Equals, maxWorkerPoolSize) 144 } 145 146 type mockRouter struct { 147 LimitRegionRouter 148 } 149 150 func (*mockRouter) Release(id string) {} 151 152 func TestRegionWokerHandleEventEntryEventOutOfOrder(t *testing.T) { 153 // For UPDATE SQL, its prewrite event has both value and old value. 154 // It is possible that TiDB prewrites multiple times for the same row when 155 // there are other transactions it conflicts with. For this case, 156 // if the value is not "short", only the first prewrite contains the value. 157 // 158 // TiKV may output events for the UPDATE SQL as following: 159 // 160 // TiDB: [Prwrite1] [Prewrite2] [Commit] 161 // v v v Time 162 // ----------------------------------------------------------------------------> 163 // ^ ^ ^ ^ ^ ^ ^ ^ ^ 164 // TiKV: [Scan Start] [Send Prewrite2] [Send Commit] [Send Prewrite1] [Send Init] 165 // TiCDC: [Recv Prewrite2] [Recv Commit] [Recv Prewrite1] [Recv Init] 166 167 ctx, cancel := context.WithCancel(context.Background()) 168 defer cancel() 169 eventCh := make(chan model.RegionFeedEvent, 2) 170 s := &eventFeedSession{ 171 regionRouter: &mockRouter{}, 172 eventCh: eventCh, 173 } 174 span := regionspan.Span{}.Hack() 175 state := newRegionFeedState(newSingleRegionInfo( 176 tikv.RegionVerID{}, 177 regionspan.ToComparableSpan(span), 178 0, &tikv.RPCContext{}), 0) 179 state.start() 180 worker := newRegionWorker(s, nil, "") 181 worker.enableOldValue = true 182 worker.initMetrics(ctx) 183 require.Equal(t, 2, cap(worker.outputCh)) 184 185 // Receive prewrite2 with empty value. 186 events := &cdcpb.Event_Entries_{ 187 Entries: &cdcpb.Event_Entries{ 188 Entries: []*cdcpb.Event_Row{{ 189 StartTs: 1, 190 Type: cdcpb.Event_PREWRITE, 191 OpType: cdcpb.Event_Row_PUT, 192 Key: []byte("key"), 193 Value: nil, 194 OldValue: []byte("oldvalue"), 195 }}, 196 }, 197 } 198 err := worker.handleEventEntry(ctx, events, state) 199 require.Nil(t, err) 200 201 // Receive commit. 202 events = &cdcpb.Event_Entries_{ 203 Entries: &cdcpb.Event_Entries{ 204 Entries: []*cdcpb.Event_Row{{ 205 StartTs: 1, 206 CommitTs: 2, 207 Type: cdcpb.Event_COMMIT, 208 OpType: cdcpb.Event_Row_PUT, 209 Key: []byte("key"), 210 }}, 211 }, 212 } 213 err = worker.handleEventEntry(context.Background(), events, state) 214 require.Nil(t, err) 215 216 // Must not output event. 217 var event model.RegionFeedEvent 218 var ok bool 219 select { 220 case event, ok = <-eventCh: 221 default: 222 } 223 require.Falsef(t, ok, "%v", event) 224 require.EqualValuesf(t, model.RegionFeedEvent{}, event, "%v", event) 225 226 // Receive prewrite1 with actual value. 227 events = &cdcpb.Event_Entries_{ 228 Entries: &cdcpb.Event_Entries{ 229 Entries: []*cdcpb.Event_Row{{ 230 StartTs: 1, 231 Type: cdcpb.Event_PREWRITE, 232 OpType: cdcpb.Event_Row_PUT, 233 Key: []byte("key"), 234 Value: []byte("value"), 235 OldValue: []byte("oldvalue"), 236 }}, 237 }, 238 } 239 err = worker.handleEventEntry(ctx, events, state) 240 require.Nil(t, err) 241 242 // Must not output event. 243 select { 244 case event, ok = <-eventCh: 245 default: 246 } 247 require.Falsef(t, ok, "%v", event) 248 require.EqualValuesf(t, model.RegionFeedEvent{}, event, "%v", event) 249 250 // Receive prewrite1 with actual value. 251 events = &cdcpb.Event_Entries_{ 252 Entries: &cdcpb.Event_Entries{ 253 Entries: []*cdcpb.Event_Row{ 254 { 255 Type: cdcpb.Event_INITIALIZED, 256 }, 257 }, 258 }, 259 } 260 err = worker.handleEventEntry(ctx, events, state) 261 require.Nil(t, err) 262 263 // Must output event. 264 select { 265 case event, ok = <-eventCh: 266 default: 267 } 268 require.Truef(t, ok, "%v", event) 269 require.EqualValuesf(t, model.RegionFeedEvent{ 270 RegionID: 0, 271 Val: &model.RawKVEntry{ 272 OpType: model.OpTypePut, 273 Key: []byte("key"), 274 Value: []byte("value"), 275 StartTs: 1, 276 CRTs: 2, 277 RegionID: 0, 278 OldValue: []byte("oldvalue"), 279 }, 280 }, event, "%v", event) 281 }