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  }