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 }