vitess.io/vitess@v0.16.2/go/vt/vttablet/tabletserver/txserializer/tx_serializer_test.go (about) 1 /* 2 Copyright 2019 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 txserializer 18 19 import ( 20 "fmt" 21 "net/http" 22 "net/http/httptest" 23 "strings" 24 "sync" 25 "testing" 26 "time" 27 28 "context" 29 30 "vitess.io/vitess/go/streamlog" 31 "vitess.io/vitess/go/vt/vterrors" 32 "vitess.io/vitess/go/vt/vttablet/tabletserver/tabletenv" 33 34 vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc" 35 ) 36 37 func resetVariables(txs *TxSerializer) { 38 txs.waits.ResetAll() 39 txs.waitsDryRun.ResetAll() 40 txs.queueExceeded.ResetAll() 41 txs.queueExceededDryRun.ResetAll() 42 txs.globalQueueExceeded.Reset() 43 txs.globalQueueExceededDryRun.Reset() 44 } 45 46 func TestTxSerializer_NoHotRow(t *testing.T) { 47 config := tabletenv.NewDefaultConfig() 48 config.HotRowProtection.MaxQueueSize = 1 49 config.HotRowProtection.MaxGlobalQueueSize = 1 50 config.HotRowProtection.MaxConcurrency = 5 51 txs := New(tabletenv.NewEnv(config, "TxSerializerTest")) 52 resetVariables(txs) 53 54 done, waited, err := txs.Wait(context.Background(), "t1 where1", "t1") 55 if err != nil { 56 t.Error(err) 57 } 58 if waited { 59 t.Error("non-parallel tx must never wait") 60 } 61 done() 62 63 // No hot row was recoded. 64 if err := testHTTPHandler(txs, 0, false); err != nil { 65 t.Error(err) 66 } 67 // No transaction had to wait. 68 if got, want := txs.waits.Counts()["t1"], int64(0); got != want { 69 t.Errorf("wrong Waits variable: got = %v, want = %v", got, want) 70 } 71 } 72 73 func TestTxSerializerRedactDebugUI(t *testing.T) { 74 streamlog.SetRedactDebugUIQueries(true) 75 defer func() { 76 streamlog.SetRedactDebugUIQueries(false) 77 }() 78 79 config := tabletenv.NewDefaultConfig() 80 config.HotRowProtection.MaxQueueSize = 1 81 config.HotRowProtection.MaxGlobalQueueSize = 1 82 config.HotRowProtection.MaxConcurrency = 5 83 txs := New(tabletenv.NewEnv(config, "TxSerializerTest")) 84 resetVariables(txs) 85 86 done, waited, err := txs.Wait(context.Background(), "t1 where1", "t1") 87 if err != nil { 88 t.Error(err) 89 } 90 if waited { 91 t.Error("non-parallel tx must never wait") 92 } 93 done() 94 95 // No hot row was recoded. 96 if err := testHTTPHandler(txs, 0, true); err != nil { 97 t.Error(err) 98 } 99 // No transaction had to wait. 100 if got, want := txs.waits.Counts()["t1"], int64(0); got != want { 101 t.Errorf("wrong Waits variable: got = %v, want = %v", got, want) 102 } 103 } 104 105 func TestKeySanitization(t *testing.T) { 106 config := tabletenv.NewDefaultConfig() 107 txs := New(tabletenv.NewEnv(config, "TxSerializerTest")) 108 // with a where clause 109 key := "t1 where c1='foo'" 110 want := "t1 ... [REDACTED]" 111 got := txs.sanitizeKey(key) 112 if got != want { 113 t.Errorf("key sanitization error: got = %v, want = %v", got, want) 114 } 115 // without a where clause 116 key = "t1" 117 want = "t1" 118 got = txs.sanitizeKey(key) 119 if got != want { 120 t.Errorf("key sanitization error: got = %v, want = %v", got, want) 121 } 122 } 123 124 func TestTxSerializer(t *testing.T) { 125 config := tabletenv.NewDefaultConfig() 126 config.HotRowProtection.MaxQueueSize = 2 127 config.HotRowProtection.MaxGlobalQueueSize = 3 128 config.HotRowProtection.MaxConcurrency = 1 129 txs := New(tabletenv.NewEnv(config, "TxSerializerTest")) 130 resetVariables(txs) 131 132 // tx1. 133 done1, waited1, err1 := txs.Wait(context.Background(), "t1 where1", "t1") 134 if err1 != nil { 135 t.Error(err1) 136 } 137 if waited1 { 138 t.Errorf("tx1 must never wait: %v", waited1) 139 } 140 141 // tx2 (gets queued and must wait). 142 wg := sync.WaitGroup{} 143 wg.Add(1) 144 go func() { 145 defer wg.Done() 146 147 done2, waited2, err2 := txs.Wait(context.Background(), "t1 where1", "t1") 148 if err2 != nil { 149 t.Error(err2) 150 } 151 if !waited2 { 152 t.Errorf("tx2 must wait: %v", waited2) 153 } 154 if got, want := txs.waits.Counts()["t1"], int64(1); got != want { 155 t.Errorf("variable not incremented: got = %v, want = %v", got, want) 156 } 157 158 done2() 159 }() 160 // Wait until tx2 is waiting before we try tx3. 161 if err := waitForPending(txs, "t1 where1", 2); err != nil { 162 t.Error(err) 163 } 164 165 // tx3 (gets rejected because it would exceed the local queue). 166 _, _, err3 := txs.Wait(context.Background(), "t1 where1", "t1") 167 if got, want := vterrors.Code(err3), vtrpcpb.Code_RESOURCE_EXHAUSTED; got != want { 168 t.Errorf("wrong error code: got = %v, want = %v", got, want) 169 } 170 if got, want := err3.Error(), "hot row protection: too many queued transactions (2 >= 2) for the same row (table + WHERE clause: 't1 where1')"; got != want { 171 t.Errorf("transaction rejected with wrong error: got = %v, want = %v", got, want) 172 } 173 174 done1() 175 // tx2 must have been unblocked. 176 wg.Wait() 177 178 if txs.queues["t1 where1"] != nil { 179 t.Error("queue object was not deleted after last transaction") 180 } 181 182 // 2 transactions were recorded. 183 if err := testHTTPHandler(txs, 2, false); err != nil { 184 t.Error(err) 185 } 186 // 1 of them had to wait. 187 if got, want := txs.waits.Counts()["t1"], int64(1); got != want { 188 t.Errorf("variable not incremented: got = %v, want = %v", got, want) 189 } 190 // 1 (the third one) was rejected because the queue was exceeded. 191 if got, want := txs.queueExceeded.Counts()["t1"], int64(1); got != want { 192 t.Errorf("variable not incremented: got = %v, want = %v", got, want) 193 } 194 } 195 196 func TestTxSerializer_ConcurrentTransactions(t *testing.T) { 197 // Allow up to 2 concurrent transactions per hot row. 198 config := tabletenv.NewDefaultConfig() 199 config.HotRowProtection.MaxQueueSize = 3 200 config.HotRowProtection.MaxGlobalQueueSize = 3 201 config.HotRowProtection.MaxConcurrency = 2 202 txs := New(tabletenv.NewEnv(config, "TxSerializerTest")) 203 resetVariables(txs) 204 205 // tx1. 206 done1, waited1, err1 := txs.Wait(context.Background(), "t1 where1", "t1") 207 if err1 != nil { 208 t.Error(err1) 209 } 210 if waited1 { 211 t.Errorf("tx1 must never wait: %v", waited1) 212 } 213 214 // tx2. 215 done2, waited2, err2 := txs.Wait(context.Background(), "t1 where1", "t1") 216 if err2 != nil { 217 t.Error(err1) 218 } 219 if waited2 { 220 t.Errorf("tx2 must not wait: %v", waited1) 221 } 222 223 // tx3 (gets queued and must wait). 224 wg := sync.WaitGroup{} 225 wg.Add(1) 226 go func() { 227 defer wg.Done() 228 229 done3, waited3, err3 := txs.Wait(context.Background(), "t1 where1", "t1") 230 if err3 != nil { 231 t.Error(err3) 232 } 233 if !waited3 { 234 t.Errorf("tx3 must wait: %v", waited2) 235 } 236 if got, want := txs.waits.Counts()["t1"], int64(1); got != want { 237 t.Errorf("variable not incremented: got = %v, want = %v", got, want) 238 } 239 240 done3() 241 }() 242 243 // Wait until tx3 is waiting before we finish tx2 and unblock tx3. 244 if err := waitForPending(txs, "t1 where1", 3); err != nil { 245 t.Error(err) 246 } 247 // Finish tx2 before tx1 to test that the "finish-order" does not matter. 248 // Unblocks tx3. 249 done2() 250 // Wait for tx3 to finish. 251 wg.Wait() 252 // Finish tx1 to delete the queue object. 253 done1() 254 255 if txs.queues["t1 where1"] != nil { 256 t.Error("queue object was not deleted after last transaction") 257 } 258 259 // 3 transactions were recorded. 260 if err := testHTTPHandler(txs, 3, false); err != nil { 261 t.Error(err) 262 } 263 // 1 of them had to wait. 264 if got, want := txs.waits.Counts()["t1"], int64(1); got != want { 265 t.Errorf("variable not incremented: got = %v, want = %v", got, want) 266 } 267 } 268 269 func waitForPending(txs *TxSerializer, key string, i int) error { 270 start := time.Now() 271 for { 272 got, want := txs.Pending(key), i 273 if got == want { 274 return nil 275 } 276 277 if time.Since(start) > 10*time.Second { 278 return fmt.Errorf("wait for TxSerializer.Pending() = %d timed out: got = %v, want = %v", i, got, want) 279 } 280 time.Sleep(1 * time.Millisecond) 281 } 282 } 283 284 func testHTTPHandler(txs *TxSerializer, count int, redacted bool) error { 285 req, err := http.NewRequest("GET", "/path-is-ignored-in-test", nil) 286 if err != nil { 287 return err 288 } 289 rr := httptest.NewRecorder() 290 txs.ServeHTTP(rr, req) 291 292 if got, want := rr.Code, http.StatusOK; got != want { 293 return fmt.Errorf("wrong status code: got = %v, want = %v", got, want) 294 } 295 296 if redacted { 297 if !strings.Contains(rr.Body.String(), "/debug/hotrows has been redacted for your protection") { 298 return fmt.Errorf("expected /debug/hotrows to be redacted") 299 } 300 return nil 301 } 302 303 want := fmt.Sprintf(`Length: 1 304 %d: t1 where1 305 `, count) 306 if count == 0 { 307 want = `Length: 0 308 ` 309 } 310 if got := rr.Body.String(); got != want { 311 return fmt.Errorf("wrong content: got = \n%v\n want = \n%v", got, want) 312 } 313 314 return nil 315 } 316 317 // TestTxSerializerCancel runs 4 pending transactions. 318 // tx1 and tx2 are allowed to run concurrently while tx3 and tx4 are queued. 319 // tx3 will get canceled and tx4 will be unblocked once tx1 is done. 320 func TestTxSerializerCancel(t *testing.T) { 321 config := tabletenv.NewDefaultConfig() 322 config.HotRowProtection.MaxQueueSize = 4 323 config.HotRowProtection.MaxGlobalQueueSize = 4 324 config.HotRowProtection.MaxConcurrency = 2 325 txs := New(tabletenv.NewEnv(config, "TxSerializerTest")) 326 resetVariables(txs) 327 328 // tx3 and tx4 will record their number once they're done waiting. 329 txDone := make(chan int) 330 331 // tx1. 332 done1, waited1, err1 := txs.Wait(context.Background(), "t1 where1", "t1") 333 if err1 != nil { 334 t.Error(err1) 335 } 336 if waited1 { 337 t.Errorf("tx1 must never wait: %v", waited1) 338 } 339 // tx2. 340 done2, waited2, err2 := txs.Wait(context.Background(), "t1 where1", "t1") 341 if err2 != nil { 342 t.Error(err2) 343 } 344 if waited2 { 345 t.Errorf("tx2 must not wait: %v", waited2) 346 } 347 348 // tx3 (gets queued and must wait). 349 ctx3, cancel3 := context.WithCancel(context.Background()) 350 wg := sync.WaitGroup{} 351 wg.Add(1) 352 go func() { 353 defer wg.Done() 354 355 _, _, err3 := txs.Wait(ctx3, "t1 where1", "t1") 356 if err3 != context.Canceled { 357 t.Error(err3) 358 } 359 360 txDone <- 3 361 }() 362 // Wait until tx3 is waiting before we try tx4. 363 if err := waitForPending(txs, "t1 where1", 3); err != nil { 364 t.Error(err) 365 } 366 367 // tx4 (gets queued and must wait as well). 368 wg.Add(1) 369 go func() { 370 defer wg.Done() 371 372 done4, waited4, err4 := txs.Wait(context.Background(), "t1 where1", "t1") 373 if err4 != nil { 374 t.Error(err4) 375 } 376 if !waited4 { 377 t.Errorf("tx4 must have waited: %v", waited4) 378 } 379 380 txDone <- 4 381 382 done4() 383 }() 384 // Wait until tx4 is waiting before we start to cancel tx3. 385 if err := waitForPending(txs, "t1 where1", 4); err != nil { 386 t.Error(err) 387 } 388 389 // Cancel tx3. 390 cancel3() 391 if got := <-txDone; got != 3 { 392 t.Errorf("tx3 should have been unblocked after the cancel: %v", got) 393 } 394 // Finish tx1. 395 done1() 396 // Wait for tx4. 397 if got := <-txDone; got != 4 { 398 t.Errorf("wrong tx was unblocked after tx1: %v", got) 399 } 400 wg.Wait() 401 // Finish tx2 (the last transaction) which will delete the queue object. 402 done2() 403 404 if txs.queues["t1 where1"] != nil { 405 t.Error("queue object was not deleted after last transaction") 406 } 407 408 // 4 total transactions get recorded. 409 if err := testHTTPHandler(txs, 4, false); err != nil { 410 t.Error(err) 411 } 412 // 2 of them had to wait. 413 if got, want := txs.waits.Counts()["t1"], int64(2); got != want { 414 t.Errorf("variable not incremented: got = %v, want = %v", got, want) 415 } 416 } 417 418 // TestTxSerializerDryRun verifies that the dry-run mode does not serialize 419 // the two concurrent transactions for the same key. 420 func TestTxSerializerDryRun(t *testing.T) { 421 config := tabletenv.NewDefaultConfig() 422 config.HotRowProtection.Mode = tabletenv.Dryrun 423 config.HotRowProtection.MaxQueueSize = 1 424 config.HotRowProtection.MaxGlobalQueueSize = 2 425 config.HotRowProtection.MaxConcurrency = 1 426 txs := New(tabletenv.NewEnv(config, "TxSerializerTest")) 427 resetVariables(txs) 428 429 // tx1. 430 done1, waited1, err1 := txs.Wait(context.Background(), "t1 where1", "t1") 431 if err1 != nil { 432 t.Error(err1) 433 } 434 if waited1 { 435 t.Errorf("first transaction must never wait: %v", waited1) 436 } 437 438 // tx2 (would wait and exceed the local queue). 439 done2, waited2, err2 := txs.Wait(context.Background(), "t1 where1", "t1") 440 if err2 != nil { 441 t.Error(err2) 442 } 443 if waited2 { 444 t.Errorf("second transaction must never wait in dry-run mode: %v", waited2) 445 } 446 if got, want := txs.waitsDryRun.Counts()["t1"], int64(1); got != want { 447 t.Errorf("variable not incremented: got = %v, want = %v", got, want) 448 } 449 if got, want := txs.queueExceededDryRun.Counts()["t1"], int64(1); got != want { 450 t.Errorf("variable not incremented: got = %v, want = %v", got, want) 451 } 452 453 // tx3 (would wait and exceed the global queue). 454 done3, waited3, err3 := txs.Wait(context.Background(), "t1 where1", "t1") 455 if err3 != nil { 456 t.Error(err3) 457 } 458 if waited3 { 459 t.Errorf("any transaction must never wait in dry-run mode: %v", waited3) 460 } 461 if got, want := txs.waitsDryRun.Counts()["t1"], int64(2); got != want { 462 t.Errorf("variable not incremented: got = %v, want = %v", got, want) 463 } 464 if got, want := txs.globalQueueExceededDryRun.Get(), int64(1); got != want { 465 t.Errorf("variable not incremented: got = %v, want = %v", got, want) 466 } 467 468 if got, want := txs.Pending("t1 where1"), 3; got != want { 469 t.Errorf("wrong number of pending transactions: got = %v, want = %v", got, want) 470 } 471 472 done1() 473 done2() 474 done3() 475 476 if txs.queues["t1 where1"] != nil { 477 t.Error("queue object was not deleted after last transaction") 478 } 479 480 if err := testHTTPHandler(txs, 3, false); err != nil { 481 t.Error(err) 482 } 483 } 484 485 // TestTxSerializerGlobalQueueOverflow shows that the global queue can exceed 486 // its limit without rejecting errors. This is the case when all transactions 487 // are the first one for their row range. 488 // This is done on purpose to avoid that a too low global queue limit would 489 // reject transactions although they may succeed within the txpool constraints 490 // and RPC deadline. 491 func TestTxSerializerGlobalQueueOverflow(t *testing.T) { 492 config := tabletenv.NewDefaultConfig() 493 config.HotRowProtection.MaxQueueSize = 1 494 config.HotRowProtection.MaxGlobalQueueSize = 1 495 config.HotRowProtection.MaxConcurrency = 1 496 txs := New(tabletenv.NewEnv(config, "TxSerializerTest")) 497 498 // tx1. 499 done1, waited1, err1 := txs.Wait(context.Background(), "t1 where1", "t1") 500 if err1 != nil { 501 t.Error(err1) 502 } 503 if waited1 { 504 t.Errorf("first transaction must never wait: %v", waited1) 505 } 506 507 // tx2. 508 done2, waited2, err2 := txs.Wait(context.Background(), "t1 where2", "t1") 509 if err2 != nil { 510 t.Error(err2) 511 } 512 if waited2 { 513 t.Errorf("second transaction for different row range must not wait: %v", waited2) 514 } 515 516 // tx3 (same row range as tx1). 517 _, _, err3 := txs.Wait(context.Background(), "t1 where1", "t1") 518 if got, want := vterrors.Code(err3), vtrpcpb.Code_RESOURCE_EXHAUSTED; got != want { 519 t.Errorf("wrong error code: got = %v, want = %v", got, want) 520 } 521 if got, want := err3.Error(), "hot row protection: too many queued transactions (2 >= 1)"; got != want { 522 t.Errorf("transaction rejected with wrong error: got = %v, want = %v", got, want) 523 } 524 if got, want := txs.globalQueueExceeded.Get(), int64(1); got != want { 525 t.Errorf("variable not incremented: got = %v, want = %v", got, want) 526 } 527 528 done1() 529 done2() 530 } 531 532 func TestTxSerializerPending(t *testing.T) { 533 config := tabletenv.NewDefaultConfig() 534 config.HotRowProtection.MaxQueueSize = 1 535 config.HotRowProtection.MaxGlobalQueueSize = 1 536 config.HotRowProtection.MaxConcurrency = 1 537 txs := New(tabletenv.NewEnv(config, "TxSerializerTest")) 538 if got, want := txs.Pending("t1 where1"), 0; got != want { 539 t.Errorf("there should be no pending transaction: got = %v, want = %v", got, want) 540 } 541 } 542 543 func BenchmarkTxSerializer_NoHotRow(b *testing.B) { 544 config := tabletenv.NewDefaultConfig() 545 config.HotRowProtection.MaxQueueSize = 1 546 config.HotRowProtection.MaxGlobalQueueSize = 1 547 config.HotRowProtection.MaxConcurrency = 5 548 txs := New(tabletenv.NewEnv(config, "TxSerializerTest")) 549 550 b.ResetTimer() 551 552 for i := 0; i < b.N; i++ { 553 done, waited, err := txs.Wait(context.Background(), "t1 where1", "t1") 554 if err != nil { 555 b.Error(err) 556 } 557 if waited { 558 b.Error("non-parallel tx must never wait") 559 } 560 done() 561 } 562 }