vitess.io/vitess@v0.16.2/go/vt/vttablet/tabletserver/stream_consolidator_flaky_test.go (about) 1 /* 2 Copyright 2021 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package tabletserver 18 19 import ( 20 "context" 21 "fmt" 22 "sync" 23 "sync/atomic" 24 "testing" 25 "time" 26 27 querypb "vitess.io/vitess/go/vt/proto/query" 28 29 "github.com/stretchr/testify/require" 30 31 "vitess.io/vitess/go/vt/vttablet/tabletserver/tabletenv" 32 33 "vitess.io/vitess/go/sqltypes" 34 ) 35 36 type consolidationResult struct { 37 err error 38 items []*sqltypes.Result 39 duration time.Duration 40 count int64 41 } 42 43 func nocleanup(_ *sqltypes.Result) error { 44 return nil 45 } 46 47 type consolidationTest struct { 48 cc *StreamConsolidator 49 50 streamDelay time.Duration 51 streamItemDelay time.Duration 52 streamItemCount int 53 streamItems []*sqltypes.Result 54 55 leaderCallback func(StreamCallback) error 56 leaderCalls uint64 57 58 results []*consolidationResult 59 } 60 61 func generateResultSizes(size, count int) (r []*sqltypes.Result) { 62 for i := 0; i < count; i++ { 63 rows, _ := sqltypes.NewValue(querypb.Type_BINARY, make([]byte, size)) 64 item := &sqltypes.Result{InsertID: uint64(i), Rows: [][]sqltypes.Value{ 65 {rows}, 66 }} 67 68 r = append(r, item) 69 } 70 return r 71 } 72 73 func (ct *consolidationTest) leader(stream StreamCallback) error { 74 atomic.AddUint64(&ct.leaderCalls, 1) 75 if ct.leaderCallback != nil { 76 return ct.leaderCallback(stream) 77 } 78 79 time.Sleep(ct.streamDelay) 80 81 if ct.streamItems != nil { 82 for _, item := range ct.streamItems { 83 cpy := *item 84 if err := stream(&cpy); err != nil { 85 return err 86 } 87 time.Sleep(ct.streamItemDelay) 88 } 89 } else { 90 for i := 0; i < ct.streamItemCount; i++ { 91 item := &sqltypes.Result{InsertID: uint64(i)} 92 if err := stream(item); err != nil { 93 return err 94 } 95 time.Sleep(ct.streamItemDelay) 96 } 97 } 98 99 return nil 100 } 101 102 func (ct *consolidationTest) waitForResults(worker int, count int64) { 103 for { 104 if atomic.LoadInt64(&ct.results[worker].count) > count { 105 return 106 } 107 time.Sleep(1 * time.Millisecond) 108 } 109 } 110 111 func (ct *consolidationTest) run(workers int, generateCallback func(int) (string, StreamCallback)) { 112 if ct.results == nil { 113 ct.results = make([]*consolidationResult, workers) 114 for i := 0; i < workers; i++ { 115 ct.results[i] = &consolidationResult{} 116 } 117 } 118 119 var wg sync.WaitGroup 120 121 for i := 0; i < workers; i++ { 122 wg.Add(1) 123 124 go func(worker int) { 125 defer wg.Done() 126 logStats := tabletenv.NewLogStats(context.Background(), "StreamConsolidation") 127 query, callback := generateCallback(worker) 128 start := time.Now() 129 err := ct.cc.Consolidate(logStats, query, func(result *sqltypes.Result) error { 130 cr := ct.results[worker] 131 cr.items = append(cr.items, result) 132 atomic.AddInt64(&cr.count, 1) 133 return callback(result) 134 }, ct.leader) 135 136 cr := ct.results[worker] 137 cr.err = err 138 cr.duration = time.Since(start) 139 }(i) 140 } 141 142 wg.Wait() 143 } 144 145 func TestConsolidatorSimple(t *testing.T) { 146 ct := consolidationTest{ 147 cc: NewStreamConsolidator(128*1024, 2*1024, nocleanup), 148 streamItemDelay: 10 * time.Millisecond, 149 streamItemCount: 10, 150 } 151 152 ct.run(10, func(worker int) (string, StreamCallback) { 153 t.Helper() 154 var inserts uint64 155 156 return "select1", func(result *sqltypes.Result) error { 157 require.Equal(t, inserts, result.InsertID) 158 inserts++ 159 return nil 160 } 161 }) 162 163 require.Equal(t, uint64(1), ct.leaderCalls) 164 165 for _, results := range ct.results { 166 require.Len(t, results.items, ct.streamItemCount) 167 require.NoError(t, results.err) 168 } 169 } 170 171 func TestConsolidatorErrorPropagation(t *testing.T) { 172 t.Run("from mysql", func(t *testing.T) { 173 ct := consolidationTest{ 174 cc: NewStreamConsolidator(128*1024, 2*1024, nocleanup), 175 leaderCallback: func(callback StreamCallback) error { 176 time.Sleep(100 * time.Millisecond) 177 return fmt.Errorf("mysqld error") 178 }, 179 } 180 181 ct.run(4, func(worker int) (string, StreamCallback) { 182 return "select 1", func(result *sqltypes.Result) error { 183 return nil 184 } 185 }) 186 187 for _, results := range ct.results { 188 require.Error(t, results.err) 189 } 190 }) 191 192 t.Run("from leader", func(t *testing.T) { 193 ct := consolidationTest{ 194 cc: NewStreamConsolidator(128*1024, 2*1024, nocleanup), 195 streamItemDelay: 10 * time.Millisecond, 196 streamItemCount: 10, 197 } 198 199 ct.run(4, func(worker int) (string, StreamCallback) { 200 if worker == 0 { 201 var rows int 202 return "select 1", func(result *sqltypes.Result) error { 203 if rows > 5 { 204 return fmt.Errorf("leader streaming client disconnected") 205 } 206 rows++ 207 return nil 208 } 209 } 210 time.Sleep(10 * time.Millisecond) 211 return "select 1", func(result *sqltypes.Result) error { 212 return nil 213 } 214 }) 215 216 for worker, results := range ct.results { 217 if worker == 0 { 218 require.Error(t, results.err) 219 } else { 220 require.NoError(t, results.err) 221 require.Len(t, results.items, 10) 222 } 223 } 224 }) 225 226 t.Run("from followers", func(t *testing.T) { 227 ct := consolidationTest{ 228 cc: NewStreamConsolidator(128*1024, 2*1024, nocleanup), 229 streamItemDelay: 10 * time.Millisecond, 230 streamItemCount: 10, 231 } 232 233 ct.run(4, func(worker int) (string, StreamCallback) { 234 if worker == 0 { 235 return "select 1", func(result *sqltypes.Result) error { 236 return nil 237 } 238 } 239 time.Sleep(10 * time.Millisecond) 240 return "select 1", func(result *sqltypes.Result) error { 241 if worker == 3 && result.InsertID == 5 { 242 return fmt.Errorf("follower stream disconnected") 243 } 244 return nil 245 } 246 }) 247 248 for worker, results := range ct.results { 249 switch worker { 250 case 0, 1, 2: 251 require.NoError(t, results.err) 252 require.Len(t, results.items, 10) 253 case 3: 254 require.Error(t, results.err) 255 } 256 } 257 }) 258 } 259 260 func TestConsolidatorDelayedListener(t *testing.T) { 261 ct := consolidationTest{ 262 cc: NewStreamConsolidator(128*1024, 2*1024, nocleanup), 263 streamItemDelay: 1 * time.Millisecond, 264 streamItemCount: 100, 265 } 266 267 ct.run(4, func(worker int) (string, StreamCallback) { 268 switch worker { 269 case 0, 1, 2: 270 return "select 1", func(_ *sqltypes.Result) error { 271 return nil 272 } 273 274 case 3: 275 time.Sleep(10 * time.Millisecond) 276 return "select 1", func(result *sqltypes.Result) error { 277 time.Sleep(100 * time.Millisecond) 278 return nil 279 } 280 default: 281 panic("??") 282 } 283 }) 284 285 require.Equal(t, uint64(1), ct.leaderCalls) 286 287 for worker, results := range ct.results { 288 if worker == 3 { 289 require.Error(t, results.err) 290 } else { 291 if results.duration > 1*time.Second { 292 t.Fatalf("worker %d took too long (%v)", worker, results.duration) 293 } 294 require.Len(t, results.items, ct.streamItemCount) 295 require.NoError(t, results.err) 296 } 297 } 298 } 299 300 func TestConsolidatorMemoryLimits(t *testing.T) { 301 t.Run("rows too large", func(t *testing.T) { 302 ct := consolidationTest{ 303 cc: NewStreamConsolidator(128*1024, 32, nocleanup), 304 streamItemDelay: 1 * time.Millisecond, 305 streamItemCount: 100, 306 } 307 308 ct.run(4, func(worker int) (string, StreamCallback) { 309 time.Sleep(time.Duration(worker) * 10 * time.Millisecond) 310 return "select 1", func(_ *sqltypes.Result) error { 311 return nil 312 } 313 }) 314 315 require.Equal(t, uint64(4), ct.leaderCalls) 316 317 for _, results := range ct.results { 318 require.Len(t, results.items, ct.streamItemCount) 319 require.NoError(t, results.err) 320 } 321 }) 322 323 t.Run("two-phase consolidation (time)", func(t *testing.T) { 324 ct := consolidationTest{ 325 cc: NewStreamConsolidator(128*1024, 2*1024, nocleanup), 326 streamItemDelay: 2 * time.Millisecond, 327 streamItemCount: 10, 328 } 329 330 ct.run(10, func(worker int) (string, StreamCallback) { 331 if worker > 4 { 332 time.Sleep(50 * time.Millisecond) 333 } 334 return "select 1", func(_ *sqltypes.Result) error { 335 return nil 336 } 337 }) 338 339 require.Equal(t, uint64(2), ct.leaderCalls) 340 341 for _, results := range ct.results { 342 require.Len(t, results.items, ct.streamItemCount) 343 require.NoError(t, results.err) 344 } 345 }) 346 347 t.Run("two-phase consolidation (memory)", func(t *testing.T) { 348 const streamsInFirstBatch = 5 349 results := generateResultSizes(100, 10) 350 rsize := results[0].CachedSize(true) 351 352 ct := consolidationTest{ 353 cc: NewStreamConsolidator(128*1024, rsize*streamsInFirstBatch+1, nocleanup), 354 streamItemDelay: 1 * time.Millisecond, 355 streamItems: results, 356 } 357 358 ct.run(10, func(worker int) (string, StreamCallback) { 359 if worker > 4 { 360 ct.waitForResults(0, streamsInFirstBatch) 361 } 362 return "select 1", func(_ *sqltypes.Result) error { return nil } 363 }) 364 365 require.Equal(t, uint64(2), ct.leaderCalls) 366 367 for _, results := range ct.results { 368 require.Len(t, results.items, 10) 369 require.NoError(t, results.err) 370 } 371 }) 372 373 t.Run("multiple phase consolidation", func(t *testing.T) { 374 results := generateResultSizes(100, 10) 375 rsize := results[0].CachedSize(true) 376 377 ct := consolidationTest{ 378 cc: NewStreamConsolidator(128*1024, rsize*2+1, nocleanup), 379 streamItemDelay: 10 * time.Millisecond, 380 streamItems: results, 381 } 382 383 ct.run(20, func(worker int) (string, StreamCallback) { 384 switch { 385 case worker >= 0 && worker <= 4: 386 case worker >= 5 && worker <= 9: 387 ct.waitForResults(0, 2) 388 case worker >= 10 && worker <= 14: 389 ct.waitForResults(5, 2) 390 case worker >= 15 && worker <= 19: 391 ct.waitForResults(10, 2) 392 } 393 return "select 1", func(_ *sqltypes.Result) error { return nil } 394 }) 395 396 require.Equal(t, uint64(4), ct.leaderCalls) 397 398 for _, results := range ct.results { 399 require.Len(t, results.items, 10) 400 require.NoError(t, results.err) 401 } 402 }) 403 }