github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/scheduler/internal/v3/keyspan/reconciler_test.go (about)

     1  // Copyright 2022 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 keyspan
    15  
    16  import (
    17  	"context"
    18  	"testing"
    19  
    20  	"github.com/pingcap/tiflow/cdc/model"
    21  	"github.com/pingcap/tiflow/cdc/processor/tablepb"
    22  	"github.com/pingcap/tiflow/cdc/scheduler/internal/v3/compat"
    23  	"github.com/pingcap/tiflow/cdc/scheduler/internal/v3/member"
    24  	"github.com/pingcap/tiflow/cdc/scheduler/internal/v3/replication"
    25  	"github.com/pingcap/tiflow/pkg/config"
    26  	"github.com/pingcap/tiflow/pkg/spanz"
    27  	"github.com/stretchr/testify/require"
    28  )
    29  
    30  func prepareSpanCache(
    31  	t *testing.T, ss [][3]uint8, // table ID, start key suffix, end key suffix.
    32  ) ([]tablepb.Span, *mockCache) {
    33  	cache := NewMockRegionCache()
    34  	allSpan := make([]tablepb.Span, 0)
    35  	for i, s := range ss {
    36  		tableSpan := spanz.TableIDToComparableSpan(int64(s[0]))
    37  		span := tableSpan
    38  		if s[1] != 0 {
    39  			span.StartKey = append(tableSpan.StartKey, s[1])
    40  		}
    41  		if s[2] != 4 {
    42  			span.EndKey = append(tableSpan.StartKey, s[2])
    43  		}
    44  		t.Logf("insert span %s", &span)
    45  		cache.regions.ReplaceOrInsert(span, uint64(i+1))
    46  		allSpan = append(allSpan, span)
    47  	}
    48  	return allSpan, cache
    49  }
    50  
    51  func TestReconcile(t *testing.T) {
    52  	t.Parallel()
    53  	// 1. Changefeed initialization or owner switch.
    54  	// 2. Owner switch after some captures fail.
    55  	// 3. Add table by DDL.
    56  	// 4. Drop table by DDL.
    57  	// 5. Some captures fail, does NOT affect spans.
    58  
    59  	allSpan, cache := prepareSpanCache(t, [][3]uint8{
    60  		{1, 0, 1}, // table ID, start key suffix, end key suffix.
    61  		{1, 1, 2},
    62  		{1, 2, 3},
    63  		{1, 3, 4},
    64  		{2, 0, 2},
    65  		{2, 2, 4},
    66  	})
    67  
    68  	cfg := &config.SchedulerConfig{
    69  		ChangefeedSettings: &config.ChangefeedSchedulerConfig{
    70  			EnableTableAcrossNodes: true,
    71  			RegionThreshold:        1,
    72  		},
    73  	}
    74  	compat := compat.New(cfg, map[string]*model.CaptureInfo{})
    75  	captures := map[model.CaptureID]*member.CaptureStatus{
    76  		"1": nil,
    77  		"2": nil,
    78  		"3": nil,
    79  		"4": nil,
    80  	}
    81  	ctx := context.Background()
    82  
    83  	// Test 1. changefeed initialization.
    84  	reps := spanz.NewBtreeMap[*replication.ReplicationSet]()
    85  	reconciler := NewReconcilerForTests(cache, cfg.ChangefeedSettings)
    86  	currentTables := &replication.TableRanges{}
    87  	currentTables.UpdateTables([]model.TableID{1})
    88  	spans := reconciler.Reconcile(ctx, currentTables, reps, captures, compat)
    89  	require.Equal(t, allSpan[:4], spans)
    90  	require.Equal(t, allSpan[:4], reconciler.tableSpans[1].spans)
    91  	require.Equal(t, 1, len(reconciler.tableSpans))
    92  
    93  	// Test 1. owner switch no capture fails.
    94  	for _, span := range reconciler.tableSpans[1].spans {
    95  		reps.ReplaceOrInsert(span, nil)
    96  	}
    97  	reconciler = NewReconcilerForTests(cache, cfg.ChangefeedSettings)
    98  	currentTables.UpdateTables([]model.TableID{1})
    99  	spans = reconciler.Reconcile(ctx, currentTables, reps, captures, compat)
   100  	require.Equal(t, allSpan[:4], spans)
   101  	require.Equal(t, allSpan[:4], reconciler.tableSpans[1].spans)
   102  	require.Equal(t, 1, len(reconciler.tableSpans))
   103  
   104  	// Test 3. add table 2.
   105  	currentTables.UpdateTables([]model.TableID{1, 2})
   106  	spans = reconciler.Reconcile(ctx, currentTables, reps, captures, compat)
   107  	spanz.Sort(spans)
   108  	require.Equal(t, allSpan, spans)
   109  	require.Equal(t, allSpan[:4], reconciler.tableSpans[1].spans)
   110  	require.Equal(t, allSpan[4:], reconciler.tableSpans[2].spans)
   111  	require.Equal(t, 2, len(reconciler.tableSpans))
   112  
   113  	// Test 4. drop table 2.
   114  	for _, span := range reconciler.tableSpans[2].spans {
   115  		reps.ReplaceOrInsert(span, nil)
   116  	}
   117  	currentTables.UpdateTables([]model.TableID{1})
   118  	spans = reconciler.Reconcile(ctx, currentTables, reps, captures, compat)
   119  	require.Equal(t, allSpan[:4], spans)
   120  	require.Equal(t, allSpan[:4], reconciler.tableSpans[1].spans)
   121  	require.Equal(t, 1, len(reconciler.tableSpans))
   122  
   123  	// Test 2. Owner switch and some captures fail.
   124  	// Start span is missing.
   125  	reps.Delete(allSpan[0])
   126  	currentTables.UpdateTables([]model.TableID{1})
   127  	spans = reconciler.Reconcile(ctx, currentTables, reps, captures, compat)
   128  	spanz.Sort(spans)
   129  	require.Equal(t, allSpan[:4], spans)
   130  	spanz.Sort(reconciler.tableSpans[1].spans)
   131  	require.Equal(t, allSpan[:4], reconciler.tableSpans[1].spans)
   132  	require.Equal(t, 1, len(reconciler.tableSpans))
   133  
   134  	// End spans is missing.
   135  	reps.ReplaceOrInsert(allSpan[0], nil)
   136  	reps.Delete(allSpan[3])
   137  	currentTables.UpdateTables([]model.TableID{1})
   138  	spans = reconciler.Reconcile(ctx, currentTables, reps, captures, compat)
   139  	spanz.Sort(spans)
   140  	require.Equal(t, allSpan[:4], spans)
   141  	spanz.Sort(reconciler.tableSpans[1].spans)
   142  	require.Equal(t, allSpan[:4], reconciler.tableSpans[1].spans)
   143  	require.Equal(t, 1, len(reconciler.tableSpans))
   144  
   145  	// 2 middle spans are missing.
   146  	reps.ReplaceOrInsert(allSpan[3], nil)
   147  	reps.Delete(allSpan[1])
   148  	reps.Delete(allSpan[2])
   149  	currentTables.UpdateTables([]model.TableID{1})
   150  	spans = reconciler.Reconcile(ctx, currentTables, reps, captures, compat)
   151  	expectedSpan := allSpan[:1]
   152  	expectedSpan = append(expectedSpan, tablepb.Span{
   153  		TableID:  1,
   154  		StartKey: allSpan[1].StartKey,
   155  		EndKey:   allSpan[2].EndKey,
   156  	})
   157  	expectedSpan = append(expectedSpan, allSpan[3])
   158  	spanz.Sort(spans)
   159  	require.Equal(t, expectedSpan, spans)
   160  	spanz.Sort(reconciler.tableSpans[1].spans)
   161  	require.Equal(t, expectedSpan, reconciler.tableSpans[1].spans)
   162  	require.Equal(t, 1, len(reconciler.tableSpans))
   163  }
   164  
   165  func TestCompatDisable(t *testing.T) {
   166  	t.Parallel()
   167  
   168  	allSpan, cache := prepareSpanCache(t, [][3]uint8{
   169  		{1, 0, 1}, // table ID, start key suffix, end key suffix.
   170  		{1, 1, 2},
   171  		{1, 2, 3},
   172  		{1, 3, 4},
   173  		{2, 0, 2},
   174  		{2, 2, 4},
   175  	})
   176  
   177  	// changefeed initialization with span replication disabled.
   178  	cfg := &config.SchedulerConfig{
   179  		ChangefeedSettings: &config.ChangefeedSchedulerConfig{
   180  			EnableTableAcrossNodes: true,
   181  			RegionThreshold:        1,
   182  		},
   183  	}
   184  	cm := compat.New(cfg, map[string]*model.CaptureInfo{
   185  		"1": {Version: "4.0.0"},
   186  	})
   187  	captures := map[model.CaptureID]*member.CaptureStatus{
   188  		"1": nil,
   189  	}
   190  	require.False(t, cm.CheckSpanReplicationEnabled())
   191  	ctx := context.Background()
   192  	reps := spanz.NewBtreeMap[*replication.ReplicationSet]()
   193  	reconciler := NewReconcilerForTests(cache, cfg.ChangefeedSettings)
   194  	currentTables := &replication.TableRanges{}
   195  	currentTables.UpdateTables([]model.TableID{1})
   196  	spans := reconciler.Reconcile(ctx, currentTables, reps, captures, cm)
   197  	require.Equal(t, []tablepb.Span{spanz.TableIDToComparableSpan(1)}, spans)
   198  	require.Equal(t, 1, len(reconciler.tableSpans))
   199  	reps.ReplaceOrInsert(spanz.TableIDToComparableSpan(1), nil)
   200  
   201  	// add table 2 after span replication is enabled.
   202  	cm.UpdateCaptureInfo(map[string]*model.CaptureInfo{
   203  		"2": {Version: compat.SpanReplicationMinVersion.String()},
   204  	})
   205  	captures["2"] = nil
   206  	require.True(t, cm.CheckSpanReplicationEnabled())
   207  	currentTables.UpdateTables([]model.TableID{1, 2})
   208  	spans = reconciler.Reconcile(ctx, currentTables, reps, captures, cm)
   209  	spanz.Sort(spans)
   210  	require.Equal(t, spanz.TableIDToComparableSpan(1), spans[0])
   211  	require.Equal(t, allSpan[4:], spans[1:])
   212  	require.Len(t, spans, 3)
   213  }
   214  
   215  func TestBatchAddRateLimit(t *testing.T) {
   216  	t.Parallel()
   217  
   218  	allSpan, cache := prepareSpanCache(t, [][3]uint8{
   219  		{2, 0, 2},
   220  		{2, 2, 3},
   221  		{2, 3, 4},
   222  	})
   223  
   224  	cfg := &config.SchedulerConfig{
   225  		ChangefeedSettings: &config.ChangefeedSchedulerConfig{
   226  			EnableTableAcrossNodes: true,
   227  			RegionThreshold:        1,
   228  		},
   229  	}
   230  	compat := compat.New(cfg, map[string]*model.CaptureInfo{})
   231  	captures := map[model.CaptureID]*member.CaptureStatus{
   232  		"1": nil,
   233  		"2": nil,
   234  		"3": nil,
   235  	}
   236  	ctx := context.Background()
   237  
   238  	// Add table 2.
   239  	reps := spanz.NewBtreeMap[*replication.ReplicationSet]()
   240  	reconciler := NewReconcilerForTests(cache, cfg.ChangefeedSettings)
   241  	currentTables := &replication.TableRanges{}
   242  	currentTables.UpdateTables([]model.TableID{2})
   243  	spans := reconciler.Reconcile(ctx, currentTables, reps, captures, compat)
   244  	require.Equal(t, allSpan, spans)
   245  	require.Equal(t, allSpan, reconciler.tableSpans[2].spans)
   246  	require.Equal(t, 1, len(reconciler.tableSpans))
   247  
   248  	// Simulate batch add rate limited
   249  	currentTables.UpdateTables([]model.TableID{2})
   250  	spans = reconciler.Reconcile(ctx, currentTables, reps, captures, compat)
   251  	require.Equal(t, allSpan, spans)
   252  	require.Equal(t, allSpan, reconciler.tableSpans[2].spans)
   253  	require.Equal(t, 1, len(reconciler.tableSpans))
   254  
   255  	reps.ReplaceOrInsert(allSpan[0], nil)
   256  	currentTables.UpdateTables([]model.TableID{2})
   257  	spans = reconciler.Reconcile(ctx, currentTables, reps, captures, compat)
   258  	require.Equal(t, allSpan, spans)
   259  	require.Equal(t, allSpan, reconciler.tableSpans[2].spans)
   260  	require.Equal(t, 1, len(reconciler.tableSpans))
   261  }
   262  
   263  func TestGetSpansNumber(t *testing.T) {
   264  	tc := []struct {
   265  		regionCount int
   266  		captureNum  int
   267  		expected    int
   268  	}{
   269  		{1, 10, 1},
   270  		{100, 2, 6},
   271  		{100, 3, 9},
   272  		{100, 5, 20},
   273  		{10000, 11, 100},
   274  	}
   275  	for _, c := range tc {
   276  		require.Equal(t, c.expected, getSpansNumber(c.regionCount, c.captureNum))
   277  	}
   278  }