vitess.io/vitess@v0.16.2/go/vt/throttler/throttler_test.go (about)

     1  /*
     2  Copyright 2020 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 throttler
    18  
    19  import (
    20  	"runtime"
    21  	"strings"
    22  	"testing"
    23  	"time"
    24  )
    25  
    26  // The main purpose of the benchmarks below is to demonstrate the functionality
    27  // of the throttler in the real-world (using a non-faked time.Now).
    28  // The benchmark values should be as close as possible to the request interval
    29  // (which is the inverse of the QPS).
    30  // For example, 1k QPS should result into 1,000,000 ns/op.
    31  //
    32  // Example for benchmark results on Lenovo Thinkpad X250, i7-5600U, Quadcore.
    33  //
    34  // $ go test -run=XXX -bench=. -cpu=4 --benchtime=30s
    35  // PASS
    36  // BenchmarkThrottler_1kQPS-4          	   50000	   1000040 ns/op
    37  // BenchmarkThrottler_10kQPS-4         	 1000000	     99999 ns/op
    38  // BenchmarkThrottler_100kQPS-4        	 5000000	      9999 ns/op
    39  // BenchmarkThrottlerParallel_1kQPS-4  	   50000	    999903 ns/op
    40  // BenchmarkThrottlerParallel_10kQPS-4 	  500000	    100060 ns/op
    41  // BenchmarkThrottlerParallel_100kQPS-4	 5000000	      9999 ns/op
    42  // BenchmarkThrottlerDisabled-4	500000000	        94.9 ns/op
    43  // ok  	vitess.io/vitess/go/vt/throttler	448.282
    44  
    45  func BenchmarkThrottler_1kQPS(b *testing.B) {
    46  	benchmarkThrottler(b, 1*1000)
    47  }
    48  
    49  func BenchmarkThrottler_10kQPS(b *testing.B) {
    50  	benchmarkThrottler(b, 10*1000)
    51  }
    52  
    53  func BenchmarkThrottler_100kQPS(b *testing.B) {
    54  	benchmarkThrottler(b, 100*1000)
    55  }
    56  
    57  // benchmarkThrottler shows that Throttler actually throttles requests.
    58  func benchmarkThrottler(b *testing.B, qps int64) {
    59  	throttler, _ := NewThrottler("test", "queries", 1, qps, ReplicationLagModuleDisabled)
    60  	defer throttler.Close()
    61  	backoffs := 0
    62  	b.ResetTimer()
    63  
    64  	for i := 0; i < b.N; i++ {
    65  		backedOff := 0
    66  		for {
    67  			backoff := throttler.Throttle(0)
    68  			if backoff == NotThrottled {
    69  				break
    70  			}
    71  			backedOff++
    72  			backoffs++
    73  			if backedOff > 1 {
    74  				b.Logf("did not wait long enough after backoff. n = %v, last backoff = %v", i, backoff)
    75  			}
    76  			time.Sleep(backoff)
    77  		}
    78  	}
    79  }
    80  
    81  func BenchmarkThrottlerParallel_1kQPS(b *testing.B) {
    82  	benchmarkThrottlerParallel(b, 1*1000)
    83  }
    84  
    85  func BenchmarkThrottlerParallel_10kQPS(b *testing.B) {
    86  	benchmarkThrottlerParallel(b, 10*1000)
    87  }
    88  
    89  func BenchmarkThrottlerParallel_100kQPS(b *testing.B) {
    90  	benchmarkThrottlerParallel(b, 100*1000)
    91  }
    92  
    93  // benchmarkThrottlerParallel is the parallel version of benchmarkThrottler.
    94  // Set -cpu to change the number of threads. The QPS should be distributed
    95  // across all threads and the reported benchmark value should be similar
    96  // to the value of benchmarkThrottler.
    97  func benchmarkThrottlerParallel(b *testing.B, qps int64) {
    98  	threadCount := runtime.GOMAXPROCS(0)
    99  	throttler, _ := NewThrottler("test", "queries", threadCount, qps, ReplicationLagModuleDisabled)
   100  	defer throttler.Close()
   101  	threadIDs := make(chan int, threadCount)
   102  	for id := 0; id < threadCount; id++ {
   103  		threadIDs <- id
   104  	}
   105  	close(threadIDs)
   106  	b.ResetTimer()
   107  
   108  	b.RunParallel(func(pb *testing.PB) {
   109  		threadID := <-threadIDs
   110  
   111  		for pb.Next() {
   112  			backedOff := 0
   113  			for {
   114  				backoff := throttler.Throttle(threadID)
   115  				if backoff == NotThrottled {
   116  					break
   117  				}
   118  				backedOff++
   119  				if backedOff > 1 {
   120  					b.Logf("did not wait long enough after backoff. threadID = %v, last backoff = %v", threadID, backoff)
   121  				}
   122  				time.Sleep(backoff)
   123  			}
   124  		}
   125  		throttler.ThreadFinished(threadID)
   126  	})
   127  }
   128  
   129  // BenchmarkThrottlerDisabled is the unthrottled version of
   130  // BenchmarkThrottler. It should report a much lower ns/op value.
   131  func BenchmarkThrottlerDisabled(b *testing.B) {
   132  	throttler, _ := NewThrottler("test", "queries", 1, MaxRateModuleDisabled, ReplicationLagModuleDisabled)
   133  	defer throttler.Close()
   134  	b.ResetTimer()
   135  
   136  	for i := 0; i < b.N; i++ {
   137  		backoff := throttler.Throttle(0)
   138  		if backoff != NotThrottled {
   139  			b.Fatalf("unthrottled throttler should never have throttled us: %v", backoff)
   140  		}
   141  	}
   142  }
   143  
   144  type fakeClock struct {
   145  	nowValue time.Time
   146  }
   147  
   148  func (f *fakeClock) now() time.Time {
   149  	return f.nowValue
   150  }
   151  
   152  func (f *fakeClock) setNow(timeOffset time.Duration) {
   153  	f.nowValue = sinceZero(timeOffset)
   154  }
   155  
   156  func sinceZero(sinceZero time.Duration) time.Time {
   157  	return time.Time{}.Add(sinceZero)
   158  }
   159  
   160  // Due to limitations of golang.org/x/time/rate.Limiter the 'now' parameter of
   161  // threadThrottler.throttle() must be at least 1 second. See the comment in
   162  // threadThrottler.newThreadThrottler() for more details.
   163  
   164  // newThrottlerWithClock should only be used for testing.
   165  func newThrottlerWithClock(name, unit string, threadCount int, maxRate int64, maxReplicationLag int64, nowFunc func() time.Time) (*Throttler, error) {
   166  	return newThrottler(GlobalManager, name, unit, threadCount, maxRate, maxReplicationLag, nowFunc)
   167  }
   168  
   169  func TestThrottle(t *testing.T) {
   170  	fc := &fakeClock{}
   171  	// 1 Thread, 2 QPS.
   172  	throttler, _ := newThrottlerWithClock("test", "queries", 1, 2, ReplicationLagModuleDisabled, fc.now)
   173  	defer throttler.Close()
   174  
   175  	fc.setNow(1000 * time.Millisecond)
   176  	// 2 QPS should divide the current second into two chunks of 500 ms:
   177  	// a) [1s, 1.5s), b) [1.5s, 2s)
   178  	// First call goes through since the chunk is not "used" yet.
   179  	if gotBackoff := throttler.Throttle(0); gotBackoff != NotThrottled {
   180  		t.Fatalf("throttler should not have throttled us: backoff = %v", gotBackoff)
   181  	}
   182  
   183  	// Next call should tell us to backoff until we reach the second chunk.
   184  	fc.setNow(1000 * time.Millisecond)
   185  	wantBackoff := 500 * time.Millisecond
   186  	if gotBackoff := throttler.Throttle(0); gotBackoff != wantBackoff {
   187  		t.Fatalf("throttler should have throttled us. got = %v, want = %v", gotBackoff, wantBackoff)
   188  	}
   189  
   190  	// Some time elpased, but we are still in the first chunk and must backoff.
   191  	fc.setNow(1111 * time.Millisecond)
   192  	wantBackoff2 := 389 * time.Millisecond
   193  	if gotBackoff := throttler.Throttle(0); gotBackoff != wantBackoff2 {
   194  		t.Fatalf("throttler should have still throttled us. got = %v, want = %v", gotBackoff, wantBackoff2)
   195  	}
   196  
   197  	// Enough time elapsed that we are in the second chunk now.
   198  	fc.setNow(1500 * time.Millisecond)
   199  	if gotBackoff := throttler.Throttle(0); gotBackoff != NotThrottled {
   200  		t.Fatalf("throttler should not have throttled us: backoff = %v", gotBackoff)
   201  	}
   202  
   203  	// We're in the third chunk and are allowed to issue the third request.
   204  	fc.setNow(2001 * time.Millisecond)
   205  	if gotBackoff := throttler.Throttle(0); gotBackoff != NotThrottled {
   206  		t.Fatalf("throttler should not have throttled us: backoff = %v", gotBackoff)
   207  	}
   208  }
   209  
   210  func TestThrottle_RateRemainderIsDistributedAcrossThreads(t *testing.T) {
   211  	fc := &fakeClock{}
   212  	// 3 Threads, 5 QPS.
   213  	throttler, _ := newThrottlerWithClock("test", "queries", 3, 5, ReplicationLagModuleDisabled, fc.now)
   214  	defer throttler.Close()
   215  
   216  	fc.setNow(1000 * time.Millisecond)
   217  	// Out of 5 QPS, each thread gets 1 and two threads get 1 query extra.
   218  	for threadID := 0; threadID < 2; threadID++ {
   219  		if gotBackoff := throttler.Throttle(threadID); gotBackoff != NotThrottled {
   220  			t.Fatalf("throttler should not have throttled thread %d: backoff = %v", threadID, gotBackoff)
   221  		}
   222  	}
   223  
   224  	fc.setNow(1500 * time.Millisecond)
   225  	// Find the thread which got one extra query.
   226  	threadsWithMoreThanOneQPS := 0
   227  	for threadID := 0; threadID < 2; threadID++ {
   228  		if gotBackoff := throttler.Throttle(threadID); gotBackoff == NotThrottled {
   229  			threadsWithMoreThanOneQPS++
   230  		} else {
   231  			wantBackoff := 500 * time.Millisecond
   232  			if gotBackoff != wantBackoff {
   233  				t.Fatalf("throttler did throttle us with the wrong backoff time. got = %v, want = %v", gotBackoff, wantBackoff)
   234  			}
   235  		}
   236  	}
   237  	if want := 2; threadsWithMoreThanOneQPS != want {
   238  		t.Fatalf("wrong number of threads were throttled: %v != %v", threadsWithMoreThanOneQPS, want)
   239  	}
   240  
   241  	// Now, all threads are throttled.
   242  	for threadID := 0; threadID < 2; threadID++ {
   243  		wantBackoff := 500 * time.Millisecond
   244  		if gotBackoff := throttler.Throttle(threadID); gotBackoff != wantBackoff {
   245  			t.Fatalf("throttler should have throttled thread %d. got = %v, want = %v", threadID, gotBackoff, wantBackoff)
   246  		}
   247  	}
   248  }
   249  
   250  func TestThreadFinished(t *testing.T) {
   251  	fc := &fakeClock{}
   252  	// 2 Threads, 2 QPS.
   253  	throttler, _ := newThrottlerWithClock("test", "queries", 2, 2, ReplicationLagModuleDisabled, fc.now)
   254  	defer throttler.Close()
   255  
   256  	// [1000ms, 2000ms):  Each thread consumes their 1 QPS.
   257  	fc.setNow(1000 * time.Millisecond)
   258  	for threadID := 0; threadID < 2; threadID++ {
   259  		if gotBackoff := throttler.Throttle(threadID); gotBackoff != NotThrottled {
   260  			t.Fatalf("throttler should not have throttled thread %d: backoff = %v", threadID, gotBackoff)
   261  		}
   262  	}
   263  	// Now they would be throttled.
   264  	wantBackoff := 1000 * time.Millisecond
   265  	for threadID := 0; threadID < 2; threadID++ {
   266  		if gotBackoff := throttler.Throttle(threadID); gotBackoff != wantBackoff {
   267  			t.Fatalf("throttler should have throttled thread %d. got = %v, want = %v", threadID, gotBackoff, wantBackoff)
   268  		}
   269  	}
   270  
   271  	// [2000ms, 3000ms): One thread finishes, other one gets remaining 1 QPS extra.
   272  	fc.setNow(2000 * time.Millisecond)
   273  	throttler.ThreadFinished(1)
   274  
   275  	// Max rate update to threadThrottlers happens asynchronously. Wait for it.
   276  	timer := time.NewTimer(2 * time.Second)
   277  	for {
   278  		if throttler.threadThrottlers[0].getMaxRate() == 2 {
   279  			timer.Stop()
   280  			break
   281  		}
   282  		select {
   283  		case <-timer.C:
   284  			t.Fatalf("max rate was not propapgated to threadThrottler[0] in time: %v", throttler.threadThrottlers[0].getMaxRate())
   285  		default:
   286  			// Timer not up yet. Try again.
   287  		}
   288  	}
   289  
   290  	// Consume 2 QPS.
   291  	if gotBackoff := throttler.Throttle(0); gotBackoff != NotThrottled {
   292  		t.Fatalf("throttler should not have throttled us: backoff = %v", gotBackoff)
   293  	}
   294  	fc.setNow(2500 * time.Millisecond)
   295  	if gotBackoff := throttler.Throttle(0); gotBackoff != NotThrottled {
   296  		t.Fatalf("throttler should not have throttled us: backoff = %v", gotBackoff)
   297  	}
   298  
   299  	// 2 QPS are consumed. Thread 0 should be throttled now.
   300  	wantBackoff2 := 500 * time.Millisecond
   301  	if gotBackoff := throttler.Throttle(0); gotBackoff != wantBackoff2 {
   302  		t.Fatalf("throttler should have throttled us. got = %v, want = %v", gotBackoff, wantBackoff2)
   303  	}
   304  
   305  	// Throttle() from a finished thread will panic.
   306  	defer func() {
   307  		msg := recover()
   308  		if msg == nil {
   309  			t.Fatal("Throttle() from a thread which called ThreadFinished() should panic")
   310  		}
   311  		if !strings.Contains(msg.(string), "already finished") {
   312  			t.Fatalf("Throttle() after ThreadFinished() panic'd for wrong reason: %v", msg)
   313  		}
   314  	}()
   315  	throttler.Throttle(1)
   316  }
   317  
   318  // TestThrottle_MaxRateIsZero tests the behavior if max rate is set to zero.
   319  // In this case, the throttler won't let any requests through until the rate
   320  // changes.
   321  func TestThrottle_MaxRateIsZero(t *testing.T) {
   322  	fc := &fakeClock{}
   323  	// 1 Thread, 0 QPS.
   324  	throttler, _ := newThrottlerWithClock("test", "queries", 1, ZeroRateNoProgess, ReplicationLagModuleDisabled, fc.now)
   325  	defer throttler.Close()
   326  
   327  	fc.setNow(1000 * time.Millisecond)
   328  	wantBackoff := 1000 * time.Millisecond
   329  	if gotBackoff := throttler.Throttle(0); gotBackoff != wantBackoff {
   330  		t.Fatalf("throttler should have throttled us. got = %v, want = %v", gotBackoff, wantBackoff)
   331  	}
   332  	fc.setNow(1111 * time.Millisecond)
   333  	wantBackoff2 := 1000 * time.Millisecond
   334  	if gotBackoff := throttler.Throttle(0); gotBackoff != wantBackoff2 {
   335  		t.Fatalf("throttler should have throttled us. got = %v, want = %v", gotBackoff, wantBackoff2)
   336  	}
   337  	fc.setNow(2000 * time.Millisecond)
   338  	wantBackoff3 := 1000 * time.Millisecond
   339  	if gotBackoff := throttler.Throttle(0); gotBackoff != wantBackoff3 {
   340  		t.Fatalf("throttler should have throttled us. got = %v, want = %v", gotBackoff, wantBackoff3)
   341  	}
   342  }
   343  
   344  func TestThrottle_MaxRateDisabled(t *testing.T) {
   345  	fc := &fakeClock{}
   346  	throttler, _ := newThrottlerWithClock("test", "queries", 1, MaxRateModuleDisabled, ReplicationLagModuleDisabled, fc.now)
   347  	defer throttler.Close()
   348  
   349  	fc.setNow(1000 * time.Millisecond)
   350  	// No QPS set. 10 requests in a row are fine.
   351  	for i := 0; i < 10; i++ {
   352  		if gotBackoff := throttler.Throttle(0); gotBackoff != NotThrottled {
   353  			t.Fatalf("throttler should not have throttled us: request = %v, backoff = %v", i, gotBackoff)
   354  		}
   355  	}
   356  }
   357  
   358  // TestThrottle_MaxRateLowerThanThreadCount tests the behavior that maxRate
   359  // must not be lower than threadCount. If this is the case, maxRate will be
   360  // set to threadCount.
   361  func TestThrottle_MaxRateLowerThanThreadCount(t *testing.T) {
   362  	fc := &fakeClock{}
   363  	// 2 Thread, 1 QPS.
   364  	throttler, _ := newThrottlerWithClock("test", "queries", 2, 1, ReplicationLagModuleDisabled, fc.now)
   365  	defer throttler.Close()
   366  
   367  	// 2 QPS instead of configured 1 QPS allowed since there are 2 threads which
   368  	// must not starve.
   369  	fc.setNow(1000 * time.Millisecond)
   370  	for threadID := 0; threadID < 1; threadID++ {
   371  		if gotBackoff := throttler.Throttle(threadID); gotBackoff != NotThrottled {
   372  			t.Fatalf("throttler should not have throttled thread %d: backoff = %v", threadID, gotBackoff)
   373  		}
   374  	}
   375  	wantBackoff := 1000 * time.Millisecond
   376  	for threadID := 0; threadID < 1; threadID++ {
   377  		if gotBackoff := throttler.Throttle(threadID); gotBackoff != wantBackoff {
   378  			t.Fatalf("throttler should have throttled thread %d: got = %v, want = %v", threadID, gotBackoff, wantBackoff)
   379  		}
   380  	}
   381  }
   382  
   383  func TestUpdateMaxRate_AllThreadsFinished(t *testing.T) {
   384  	fc := &fakeClock{}
   385  	throttler, _ := newThrottlerWithClock("test", "queries", 2, 1e9, ReplicationLagModuleDisabled, fc.now)
   386  	defer throttler.Close()
   387  
   388  	throttler.ThreadFinished(0)
   389  	throttler.ThreadFinished(1)
   390  
   391  	// Make sure that there's no division by zero error (threadsRunning == 0).
   392  	throttler.updateMaxRate()
   393  	// We don't care about the Throttler state at this point.
   394  }
   395  
   396  func TestClose(t *testing.T) {
   397  	fc := &fakeClock{}
   398  	throttler, _ := newThrottlerWithClock("test", "queries", 1, 1, ReplicationLagModuleDisabled, fc.now)
   399  	throttler.Close()
   400  
   401  	defer func() {
   402  		msg := recover()
   403  		if msg == nil {
   404  			t.Fatal("Throttle() after Close() should panic")
   405  		}
   406  		if !strings.Contains(msg.(string), "must not access closed Throttler") {
   407  			t.Fatalf("Throttle() after ThreadFinished() panic'd for wrong reason: %v", msg)
   408  		}
   409  	}()
   410  	throttler.Throttle(0)
   411  }
   412  
   413  func TestThreadFinished_SecondCallPanics(t *testing.T) {
   414  	fc := &fakeClock{}
   415  	throttler, _ := newThrottlerWithClock("test", "queries", 1, 1, ReplicationLagModuleDisabled, fc.now)
   416  	throttler.ThreadFinished(0)
   417  
   418  	defer func() {
   419  		msg := recover()
   420  		if msg == nil {
   421  			t.Fatal("Second ThreadFinished() after ThreadFinished() should panic")
   422  		}
   423  		if !strings.Contains(msg.(string), "already finished") {
   424  			t.Fatalf("ThreadFinished() after ThreadFinished() panic'd for wrong reason: %v", msg)
   425  		}
   426  	}()
   427  	throttler.ThreadFinished(0)
   428  }