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 }