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  }