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  }