github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/kv/regionlock/region_range_lock_test.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 regionlock 15 16 import ( 17 "context" 18 "encoding/binary" 19 "fmt" 20 "math" 21 "testing" 22 "time" 23 24 "github.com/pingcap/tiflow/cdc/processor/tablepb" 25 "github.com/pingcap/tiflow/pkg/spanz" 26 "github.com/stretchr/testify/require" 27 ) 28 29 func mustSuccess(t *testing.T, res LockRangeResult, expectedResolvedTs uint64) { 30 require.Equal(t, LockRangeStatusSuccess, res.Status) 31 require.Equal(t, expectedResolvedTs, res.LockedRangeState.ResolvedTs.Load()) 32 } 33 34 func mustStale(t *testing.T, res LockRangeResult, expectedRetryRanges ...tablepb.Span) { 35 require.Equal(t, LockRangeStatusStale, res.Status) 36 require.Equal(t, expectedRetryRanges, res.RetryRanges) 37 } 38 39 func mustWaitFn(t *testing.T, res LockRangeResult) func() LockRangeResult { 40 require.Equal(t, LockRangeStatusWait, res.Status) 41 return res.WaitFn 42 } 43 44 func mustLockRangeSuccess( 45 ctx context.Context, 46 t *testing.T, 47 l *RangeLock, 48 startKey, endKey string, 49 regionID, version, expectedResolvedTs uint64, 50 ) { 51 res := l.LockRange(ctx, []byte(startKey), []byte(endKey), regionID, version) 52 mustSuccess(t, res, expectedResolvedTs) 53 } 54 55 // nolint:unparam 56 // NOTICE: For now, regionID always is 1. 57 func mustLockRangeStale( 58 ctx context.Context, 59 t *testing.T, 60 l *RangeLock, 61 startKey, endKey string, 62 regionID, version uint64, 63 expectRetrySpans ...string, 64 ) { 65 res := l.LockRange(ctx, []byte(startKey), []byte(endKey), regionID, version) 66 spans := make([]tablepb.Span, 0) 67 for i := 0; i < len(expectRetrySpans); i += 2 { 68 spans = append(spans, tablepb.Span{ 69 StartKey: []byte(expectRetrySpans[i]), EndKey: []byte(expectRetrySpans[i+1]), 70 }) 71 } 72 mustStale(t, res, spans...) 73 } 74 75 // nolint:unparam 76 // NOTICE: For now, regionID always is 1. 77 func mustLockRangeWait( 78 ctx context.Context, 79 t *testing.T, 80 l *RangeLock, 81 startKey, endKey string, 82 regionID, version uint64, 83 ) func() LockRangeResult { 84 res := l.LockRange(ctx, []byte(startKey), []byte(endKey), regionID, version) 85 return mustWaitFn(t, res) 86 } 87 88 func unlockRange(l *RangeLock, startKey, endKey string, regionID, version uint64, resolvedTs uint64) { 89 l.UnlockRange([]byte(startKey), []byte(endKey), regionID, version, resolvedTs) 90 } 91 92 func TestRegionRangeLock(t *testing.T) { 93 t.Parallel() 94 95 ctx := context.TODO() 96 l := NewRangeLock(1, []byte("a"), []byte("h"), math.MaxUint64, "") 97 mustLockRangeSuccess(ctx, t, l, "a", "e", 1, 1, math.MaxUint64) 98 unlockRange(l, "a", "e", 1, 1, 100) 99 100 mustLockRangeSuccess(ctx, t, l, "a", "e", 1, 2, 100) 101 mustLockRangeStale(ctx, t, l, "a", "e", 1, 2) 102 wait := mustLockRangeWait(ctx, t, l, "a", "h", 1, 3) 103 104 unlockRange(l, "a", "e", 1, 2, 110) 105 res := wait() 106 mustSuccess(t, res, 110) 107 unlockRange(l, "a", "h", 1, 3, 120) 108 } 109 110 func TestRegionRangeLockStale(t *testing.T) { 111 t.Parallel() 112 113 l := NewRangeLock(1, []byte("a"), []byte("z"), math.MaxUint64, "") 114 ctx := context.TODO() 115 mustLockRangeSuccess(ctx, t, l, "c", "g", 1, 10, math.MaxUint64) 116 mustLockRangeSuccess(ctx, t, l, "j", "n", 2, 8, math.MaxUint64) 117 118 mustLockRangeStale(ctx, t, l, "c", "g", 1, 10) 119 mustLockRangeStale(ctx, t, l, "c", "i", 1, 9, "g", "i") 120 mustLockRangeStale(ctx, t, l, "a", "z", 1, 9, "a", "c", "g", "j", "n", "z") 121 mustLockRangeStale(ctx, t, l, "a", "e", 1, 9, "a", "c") 122 mustLockRangeStale(ctx, t, l, "e", "h", 1, 9, "g", "h") 123 mustLockRangeStale(ctx, t, l, "e", "k", 1, 9, "g", "j") 124 mustLockRangeSuccess(ctx, t, l, "g", "j", 3, 1, math.MaxUint64) 125 unlockRange(l, "g", "j", 3, 1, 2) 126 unlockRange(l, "c", "g", 1, 10, 5) 127 unlockRange(l, "j", "n", 2, 8, 8) 128 mustLockRangeSuccess(ctx, t, l, "a", "z", 1, 11, 2) 129 unlockRange(l, "a", "z", 1, 11, 2) 130 } 131 132 func TestRegionRangeLockLockingRegionID(t *testing.T) { 133 t.Parallel() 134 135 ctx := context.TODO() 136 l := NewRangeLock(1, []byte("a"), []byte("z"), math.MaxUint64, "") 137 mustLockRangeSuccess(ctx, t, l, "c", "d", 1, 10, math.MaxUint64) 138 139 mustLockRangeStale(ctx, t, l, "e", "f", 1, 5, "e", "f") 140 mustLockRangeStale(ctx, t, l, "e", "f", 1, 10, "e", "f") 141 wait := mustLockRangeWait(ctx, t, l, "e", "f", 1, 11) 142 unlockRange(l, "c", "d", 1, 10, 10) 143 mustSuccess(t, wait(), math.MaxUint64) 144 // Now ["e", "f") is locked by region 1 at version 11 and ts 11. 145 146 mustLockRangeSuccess(ctx, t, l, "g", "h", 2, 10, math.MaxUint64) 147 wait = mustLockRangeWait(ctx, t, l, "g", "h", 1, 12) 148 ch := make(chan LockRangeResult, 1) 149 go func() { 150 ch <- wait() 151 }() 152 unlockRange(l, "g", "h", 2, 10, 20) 153 // Locking should still be blocked because the regionID 1 is still locked. 154 select { 155 case <-ch: 156 require.FailNow(t, "locking finished unexpectedly") 157 case <-time.After(time.Millisecond * 50): 158 } 159 160 unlockRange(l, "e", "f", 1, 11, 11) 161 res := <-ch 162 // CheckpointTS calculation should still be based on range and do not consider the regionID. So 163 // the result's resolvedTs should be 20 from of range ["g", "h"), instead of 11 from min(11, 20). 164 mustSuccess(t, res, 20) 165 unlockRange(l, "g", "h", 1, 12, 30) 166 } 167 168 func TestRegionRangeLockCanBeCancelled(t *testing.T) { 169 t.Parallel() 170 171 ctx, cancel := context.WithCancel(context.Background()) 172 l := NewRangeLock(1, []byte("a"), []byte("z"), math.MaxUint64, "") 173 mustLockRangeSuccess(ctx, t, l, "g", "h", 1, 10, math.MaxUint64) 174 wait := mustLockRangeWait(ctx, t, l, "g", "h", 1, 12) 175 cancel() 176 lockResult := wait() 177 require.Equal(t, LockRangeStatusCancel, lockResult.Status) 178 } 179 180 func TestRangeTsMapSetUnset(t *testing.T) { 181 t.Parallel() 182 183 m := newRangeTsMap([]byte("a"), []byte("z"), 100) 184 require.Equal(t, 1, m.m.Len()) 185 require.Equal(t, uint64(100), m.getMinTsInRange([]byte("a"), []byte("z"))) 186 187 // Double set, should panic. 188 require.Panics(t, func() { m.clone().set([]byte("a"), []byte("m"), 101) }) 189 190 // Unset and then get other ranges. 191 m.unset([]byte("m"), []byte("x")) 192 require.Equal(t, 3, m.m.Len()) 193 require.Equal(t, uint64(100), m.getMinTsInRange([]byte("a"), []byte("m"))) 194 require.Equal(t, uint64(100), m.getMinTsInRange([]byte("x"), []byte("z"))) 195 196 // Unset and then get, should panic. 197 require.Panics(t, func() { m.clone().getMinTsInRange([]byte("m"), []byte("x")) }) 198 require.Panics(t, func() { m.clone().getMinTsInRange([]byte("n"), []byte("w")) }) 199 require.Panics(t, func() { m.clone().getMinTsInRange([]byte("a"), []byte("z")) }) 200 201 // Unset all ranges. 202 m.unset([]byte("a"), []byte("m")) 203 m.unset([]byte("x"), []byte("z")) 204 require.Equal(t, 1, m.m.Len()) 205 require.Panics(t, func() { m.clone().getMinTsInRange([]byte("a"), []byte("z")) }) 206 } 207 208 func TestRegionRangeLockCollect(t *testing.T) { 209 t.Parallel() 210 211 ctx := context.Background() 212 l := NewRangeLock(1, []byte("a"), []byte("z"), 100, "") 213 attrs := l.IterAll(nil) 214 require.Equal(t, 1, len(attrs.UnLockedRanges)) 215 216 l.LockRange(ctx, []byte("a"), []byte("m"), 1, 1).LockedRangeState.ResolvedTs.Add(1) 217 attrs = l.IterAll(nil) 218 require.Equal(t, 1, len(attrs.UnLockedRanges)) 219 require.Equal(t, uint64(101), attrs.SlowestRegion.ResolvedTs) 220 require.Equal(t, uint64(101), attrs.FastestRegion.ResolvedTs) 221 222 l.LockRange(ctx, []byte("m"), []byte("z"), 2, 1).LockedRangeState.ResolvedTs.Add(2) 223 attrs = l.IterAll(nil) 224 require.Equal(t, 0, len(attrs.UnLockedRanges)) 225 require.Equal(t, uint64(101), attrs.SlowestRegion.ResolvedTs) 226 require.Equal(t, uint64(102), attrs.FastestRegion.ResolvedTs) 227 228 l.UnlockRange([]byte("a"), []byte("m"), 1, 1) 229 attrs = l.IterAll(nil) 230 require.Equal(t, 1, len(attrs.UnLockedRanges)) 231 require.Equal(t, uint64(102), attrs.SlowestRegion.ResolvedTs) 232 require.Equal(t, uint64(102), attrs.FastestRegion.ResolvedTs) 233 234 l.UnlockRange([]byte("m"), []byte("z"), 2, 1) 235 attrs = l.IterAll(nil) 236 require.Equal(t, 1, len(attrs.UnLockedRanges)) 237 } 238 239 func TestCalculateMinResolvedTs(t *testing.T) { 240 l := NewRangeLock(1, []byte("a"), []byte("z"), 100, "") 241 242 res := l.LockRange(context.Background(), []byte("m"), []byte("x"), 1, 1) 243 res.LockedRangeState.ResolvedTs.Store(101) 244 require.Equal(t, LockRangeStatusSuccess, res.Status) 245 require.Equal(t, uint64(100), l.ResolvedTs()) 246 247 res = l.LockRange(context.Background(), []byte("a"), []byte("m"), 2, 1) 248 require.Equal(t, LockRangeStatusSuccess, res.Status) 249 res.LockedRangeState.ResolvedTs.Store(102) 250 res = l.LockRange(context.Background(), []byte("x"), []byte("z"), 3, 1) 251 require.Equal(t, LockRangeStatusSuccess, res.Status) 252 res.LockedRangeState.ResolvedTs.Store(103) 253 require.Equal(t, uint64(101), l.ResolvedTs()) 254 } 255 256 func BenchmarkOneMillionRegions(b *testing.B) { 257 ctx := context.Background() 258 startKey, endKey := spanz.GetTableRange(1) 259 l := NewRangeLock(1, startKey, endKey, 100, "") 260 261 for i := 1; i <= 1000*1000; i++ { 262 var rangeStart, rangeEnd []byte 263 264 rangeStart = make([]byte, len(startKey)+8) 265 copy(rangeStart, startKey) 266 binary.BigEndian.PutUint64(rangeStart[len(startKey):], uint64(i)) 267 268 if i < 1000*1000 { 269 rangeEnd = make([]byte, len(startKey)+8) 270 copy(rangeEnd, startKey) 271 binary.BigEndian.PutUint64(rangeEnd[len(startKey):], uint64(i+1)) 272 } else { 273 rangeEnd = make([]byte, len(endKey)) 274 copy(rangeEnd, endKey) 275 } 276 lockRes := l.LockRange(ctx, rangeStart, rangeEnd, uint64(i), 1) 277 if lockRes.Status != LockRangeStatusSuccess { 278 panic(fmt.Sprintf("bad lock range, i: %d\n", i)) 279 } 280 lockRes.LockedRangeState.ResolvedTs.Store(uint64(100 + i)) 281 } 282 283 b.ResetTimer() 284 for i := 0; i < b.N; i++ { 285 l.ResolvedTs() 286 } 287 }