github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/create_stats_test.go (about)

     1  // Copyright 2019 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package sql_test
    12  
    13  import (
    14  	"context"
    15  	gosql "database/sql"
    16  	"net/url"
    17  	"sync"
    18  	"testing"
    19  	"time"
    20  
    21  	"github.com/cockroachdb/cockroach/pkg/base"
    22  	"github.com/cockroachdb/cockroach/pkg/security"
    23  	"github.com/cockroachdb/cockroach/pkg/sql/rowexec"
    24  	"github.com/cockroachdb/cockroach/pkg/testutils"
    25  	"github.com/cockroachdb/cockroach/pkg/testutils/serverutils"
    26  	"github.com/cockroachdb/cockroach/pkg/testutils/sqlutils"
    27  	"github.com/cockroachdb/cockroach/pkg/util"
    28  	"github.com/cockroachdb/cockroach/pkg/util/leaktest"
    29  	"github.com/cockroachdb/cockroach/pkg/util/randutil"
    30  )
    31  
    32  // TestStatsWithLowTTL simulates a CREATE STATISTICS run that takes longer than
    33  // the TTL of a table; the purpose is to test the timestamp-advancing mechanism.
    34  func TestStatsWithLowTTL(t *testing.T) {
    35  	defer leaktest.AfterTest(t)()
    36  
    37  	if util.RaceEnabled {
    38  		// The test requires a bunch of data to be inserted, which is much slower in
    39  		// race mode.
    40  		t.Skip("skipping under race")
    41  	}
    42  
    43  	s, db, _ := serverutils.StartServer(t, base.TestServerArgs{})
    44  	defer s.Stopper().Stop(context.Background())
    45  
    46  	r := sqlutils.MakeSQLRunner(db)
    47  	r.Exec(t, `
    48  		SET CLUSTER SETTING sql.stats.automatic_collection.enabled = false;
    49  		CREATE DATABASE test;
    50  		USE test;
    51  		CREATE TABLE t (k INT PRIMARY KEY, a INT, b INT);
    52  	`)
    53  	const numRows = 50000
    54  	r.Exec(t, `INSERT INTO t SELECT k, 2*k, 3*k FROM generate_series(0, $1) AS g(k)`, numRows-1)
    55  
    56  	pgURL, cleanupFunc := sqlutils.PGUrl(t,
    57  		s.ServingSQLAddr(),
    58  		"TestStatsWithLowTTL",
    59  		url.User(security.RootUser),
    60  	)
    61  	defer cleanupFunc()
    62  
    63  	// Start a goroutine that keeps updating rows in the table and issues
    64  	// GCRequests simulating a 1 second TTL. While this is running, reading at a
    65  	// timestamp older than 1 second will error out.
    66  	var goroutineErr error
    67  	var wg sync.WaitGroup
    68  	wg.Add(1)
    69  	stopCh := make(chan struct{})
    70  
    71  	go func() {
    72  		defer wg.Done()
    73  
    74  		// Open a separate connection to the database.
    75  		db2, err := gosql.Open("postgres", pgURL.String())
    76  		if err != nil {
    77  			goroutineErr = err
    78  			return
    79  		}
    80  		defer db2.Close()
    81  
    82  		_, err = db2.Exec("USE test")
    83  		if err != nil {
    84  			goroutineErr = err
    85  			return
    86  		}
    87  		rng, _ := randutil.NewPseudoRand()
    88  		for {
    89  			select {
    90  			case <-stopCh:
    91  				return
    92  			default:
    93  			}
    94  			k := rng.Intn(numRows)
    95  			if _, err := db2.Exec(`UPDATE t SET a=a+1, b=b+2 WHERE k=$1`, k); err != nil {
    96  				goroutineErr = err
    97  				return
    98  			}
    99  			// Force a table GC of values older than 1 second.
   100  			if err := s.ForceTableGC(
   101  				context.Background(), "test", "t", s.Clock().Now().Add(-int64(1*time.Second), 0),
   102  			); err != nil {
   103  				goroutineErr = err
   104  				return
   105  			}
   106  			time.Sleep(10 * time.Millisecond)
   107  		}
   108  	}()
   109  
   110  	// Sleep 500ms after every 10k scanned rows, to simulate a long-running
   111  	// operation.
   112  	rowexec.TestingSamplerSleep = 500 * time.Millisecond
   113  	defer func() { rowexec.TestingSamplerSleep = 0 }()
   114  
   115  	// Sleep enough to ensure the table descriptor existed at AOST.
   116  	time.Sleep(100 * time.Millisecond)
   117  
   118  	// Creating statistics should fail now because the timestamp will get older
   119  	// than 1s. In theory, we could get really lucky (wrt scheduling of the
   120  	// goroutine above), so we try multiple times.
   121  	for i := 0; ; i++ {
   122  		_, err := db.Exec(`CREATE STATISTICS foo FROM t AS OF SYSTEM TIME '-0.1s'`)
   123  		if err != nil {
   124  			if !testutils.IsError(err, "batch timestamp .* must be after replica GC threshold") {
   125  				// Unexpected error.
   126  				t.Error(err)
   127  			}
   128  			break
   129  		}
   130  		if i > 5 {
   131  			t.Error("expected CREATE STATISTICS to fail")
   132  			break
   133  		}
   134  		t.Log("expected CREATE STATISTICS to fail, trying again")
   135  	}
   136  
   137  	// Set up timestamp advance to keep timestamps no older than 0.3s.
   138  	r.Exec(t, `SET CLUSTER SETTING sql.stats.max_timestamp_age = '0.3s'`)
   139  
   140  	_, err := db.Exec(`CREATE STATISTICS foo FROM t AS OF SYSTEM TIME '-0.1s'`)
   141  	if err != nil {
   142  		t.Error(err)
   143  	}
   144  
   145  	close(stopCh)
   146  	wg.Wait()
   147  	if goroutineErr != nil {
   148  		t.Fatal(goroutineErr)
   149  	}
   150  }