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

     1  // Copyright 2018 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 querycache
    12  
    13  import (
    14  	"fmt"
    15  	"strings"
    16  	"sync"
    17  	"testing"
    18  
    19  	"github.com/cockroachdb/cockroach/pkg/sql/opt/memo"
    20  	"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
    21  	"github.com/cockroachdb/cockroach/pkg/util/randutil"
    22  )
    23  
    24  func toStr(c *C) string {
    25  	c.check()
    26  
    27  	c.mu.Lock()
    28  	defer c.mu.Unlock()
    29  
    30  	var b strings.Builder
    31  	for e := c.mu.used.next; e != &c.mu.used; e = e.next {
    32  		if b.Len() != 0 {
    33  			b.WriteString(",")
    34  		}
    35  		b.WriteString(e.SQL)
    36  	}
    37  	return b.String()
    38  }
    39  
    40  func expect(t *testing.T, c *C, exp string) {
    41  	t.Helper()
    42  	if s := toStr(c); s != exp {
    43  		t.Errorf("expected %s, got %s", exp, s)
    44  	}
    45  }
    46  
    47  func data(sql string, mem *memo.Memo, memEstimate int64) *CachedData {
    48  	cd := &CachedData{SQL: sql, Memo: mem, PrepareMetadata: &sqlbase.PrepareMetadata{}}
    49  	n := memEstimate - cd.memoryEstimate()
    50  	if n < 0 {
    51  		panic(fmt.Sprintf("size %d too small", memEstimate))
    52  	}
    53  	// Add characters to AnonymizedStr which should increase the estimate.
    54  	s := make([]byte, n)
    55  	for i := range s {
    56  		s[i] = 'x'
    57  	}
    58  	cd.PrepareMetadata.AnonymizedStr = string(s)
    59  	if cd.memoryEstimate() != memEstimate {
    60  		panic(fmt.Sprintf("failed to create CachedData of size %d", memEstimate))
    61  	}
    62  	return cd
    63  }
    64  
    65  // TestCache tests the main operations of the cache.
    66  func TestCache(t *testing.T) {
    67  	sa := &memo.Memo{}
    68  	sb := &memo.Memo{}
    69  	sc := &memo.Memo{}
    70  	sd := &memo.Memo{}
    71  
    72  	// In this test, all entries have the same size: avgCachedSize.
    73  	c := New(3 * avgCachedSize)
    74  
    75  	var s Session
    76  	s.Init()
    77  
    78  	expect(t, c, "")
    79  	c.Add(&s, data("a", sa, avgCachedSize))
    80  	expect(t, c, "a")
    81  	c.Add(&s, data("b", sb, avgCachedSize))
    82  	expect(t, c, "b,a")
    83  	c.Add(&s, data("c", sc, avgCachedSize))
    84  	expect(t, c, "c,b,a")
    85  	c.Add(&s, data("d", sd, avgCachedSize))
    86  	expect(t, c, "d,c,b")
    87  	if _, ok := c.Find(&s, "a"); ok {
    88  		t.Errorf("a shouldn't be in the cache")
    89  	}
    90  	if res, ok := c.Find(&s, "c"); !ok {
    91  		t.Errorf("c should be in the cache")
    92  	} else if res.Memo != sc {
    93  		t.Errorf("invalid Memo for c")
    94  	}
    95  	expect(t, c, "c,d,b")
    96  
    97  	if res, ok := c.Find(&s, "b"); !ok {
    98  		t.Errorf("b should be in the cache")
    99  	} else if res.Memo != sb {
   100  		t.Errorf("invalid Memo for b")
   101  	}
   102  	expect(t, c, "b,c,d")
   103  
   104  	c.Add(&s, data("a", sa, avgCachedSize))
   105  	expect(t, c, "a,b,c")
   106  
   107  	c.Purge("b")
   108  	expect(t, c, "a,c")
   109  	if _, ok := c.Find(&s, "b"); ok {
   110  		t.Errorf("b shouldn't be in the cache")
   111  	}
   112  
   113  	c.Purge("c")
   114  	expect(t, c, "a")
   115  
   116  	c.Add(&s, data("b", sb, avgCachedSize))
   117  	expect(t, c, "b,a")
   118  
   119  	c.Clear()
   120  	expect(t, c, "")
   121  	if _, ok := c.Find(&s, "b"); ok {
   122  		t.Errorf("b shouldn't be in the cache")
   123  	}
   124  }
   125  
   126  func TestCacheMemory(t *testing.T) {
   127  	m := &memo.Memo{}
   128  
   129  	c := New(10 * avgCachedSize)
   130  	var s Session
   131  	s.Init()
   132  	expect(t, c, "")
   133  	for i := 0; i < 10; i++ {
   134  		c.Add(&s, data(fmt.Sprintf("%d", i), m, avgCachedSize/2))
   135  	}
   136  	expect(t, c, "9,8,7,6,5,4,3,2,1,0")
   137  
   138  	// Verify handling when we have no more entries.
   139  	c.Add(&s, data("10", m, avgCachedSize/2))
   140  	expect(t, c, "10,9,8,7,6,5,4,3,2,1")
   141  
   142  	// Verify handling when we have larger entries.
   143  	c.Add(&s, data("large", m, avgCachedSize*8))
   144  	expect(t, c, "large,10,9,8,7")
   145  	c.Add(&s, data("verylarge", m, avgCachedSize*10))
   146  	expect(t, c, "verylarge")
   147  
   148  	for i := 0; i < 10; i++ {
   149  		c.Add(&s, data(fmt.Sprintf("%d", i), m, avgCachedSize))
   150  	}
   151  	expect(t, c, "9,8,7,6,5,4,3,2,1,0")
   152  
   153  	// Verify that we don't try to add an entry that's larger than the cache size.
   154  	c.Add(&s, data("large", m, avgCachedSize*11))
   155  	expect(t, c, "9,8,7,6,5,4,3,2,1,0")
   156  
   157  	// Verify handling when we update an existing entry with one that uses more
   158  	// memory.
   159  	c.Add(&s, data("5", m, avgCachedSize*5))
   160  	expect(t, c, "5,9,8,7,6,4")
   161  
   162  	c.Add(&s, data("0", m, avgCachedSize))
   163  	expect(t, c, "0,5,9,8,7,6")
   164  
   165  	// Verify handling when we update an existing entry with one that uses less
   166  	// memory.
   167  	c.Add(&s, data("5", m, avgCachedSize))
   168  	expect(t, c, "5,0,9,8,7,6")
   169  	c.Add(&s, data("1", m, avgCachedSize))
   170  	c.Add(&s, data("2", m, avgCachedSize))
   171  	c.Add(&s, data("3", m, avgCachedSize))
   172  	c.Add(&s, data("4", m, avgCachedSize))
   173  	expect(t, c, "4,3,2,1,5,0,9,8,7,6")
   174  
   175  	// Verify Purge updates the available memory.
   176  	c.Purge("3")
   177  	expect(t, c, "4,2,1,5,0,9,8,7,6")
   178  	c.Add(&s, data("x", m, avgCachedSize))
   179  	expect(t, c, "x,4,2,1,5,0,9,8,7,6")
   180  	c.Add(&s, data("y", m, avgCachedSize))
   181  	expect(t, c, "y,x,4,2,1,5,0,9,8,7")
   182  }
   183  
   184  // TestSynchronization verifies that the cache doesn't crash (or cause a race
   185  // detector error) when multiple goroutines are using it in parallel.
   186  func TestSynchronization(t *testing.T) {
   187  	const size = 100
   188  	c := New(size * avgCachedSize)
   189  
   190  	var wg sync.WaitGroup
   191  	const goroutines = 20
   192  	wg.Add(goroutines)
   193  	for i := 0; i < goroutines; i++ {
   194  		go func() {
   195  			rng, _ := randutil.NewPseudoRand()
   196  			var s Session
   197  			s.Init()
   198  			for j := 0; j < 5000; j++ {
   199  				sql := fmt.Sprintf("%d", rng.Intn(2*size))
   200  				switch r := rng.Intn(100); {
   201  				case r == 0:
   202  					// 1% of the time, clear the entire cache.
   203  					c.Clear()
   204  				case r <= 10:
   205  					// 10% of the time, purge an entry.
   206  					c.Purge(sql)
   207  				case r <= 35:
   208  					// 25% of the time, add an entry.
   209  					c.Add(&s, data(sql, &memo.Memo{}, int64(256+rng.Intn(10*avgCachedSize))))
   210  				default:
   211  					// The rest of the time, find an entry.
   212  					_, _ = c.Find(&s, sql)
   213  				}
   214  				c.check()
   215  			}
   216  			wg.Done()
   217  		}()
   218  	}
   219  	wg.Wait()
   220  }
   221  
   222  func TestSession(t *testing.T) {
   223  	var s Session
   224  	s.Init()
   225  
   226  	// Find the number of misses that are necessary to reach the high miss ratio.
   227  	n := 1
   228  	for {
   229  		n++
   230  		s.registerMiss()
   231  		if s.highMissRatio() {
   232  			break
   233  		}
   234  	}
   235  	if n < 1000 {
   236  		t.Errorf("high miss ratio threshold reached too quickly (n=%d)", n)
   237  	} else if n > 10000 {
   238  		t.Errorf("high miss ratio threshold reached too slowly (n=%d)", n)
   239  	}
   240  
   241  	// Verify we can get the average close to 0.5.
   242  	for i := 0; i < 2000; i++ {
   243  		s.registerHit()
   244  		s.registerMiss()
   245  	}
   246  	v := float64(s.missRatioMMA) / mmaScale
   247  	if v < 0.5 || v > 0.6 {
   248  		t.Errorf("invalid miss ratio %f, expected close to 0.5", v)
   249  	}
   250  	// Verify we can get the average close to 0.
   251  	for i := 0; i < 2000; i++ {
   252  		s.registerHit()
   253  	}
   254  	v = float64(s.missRatioMMA) / mmaScale
   255  	if v > 0.1 {
   256  		t.Errorf("invalid miss ratio %f, expected close to 0", v)
   257  	}
   258  }
   259  
   260  // BenchmarkWorstCase is a worst case benchmark where every session
   261  // misses the cache. The result of the benchmark is the time to do a pair of
   262  // Find, Add operations.
   263  //
   264  // For server-level benchmarks, see BenchmarkQueryCache in pkg/sql.
   265  func BenchmarkWorstCase(b *testing.B) {
   266  	for _, mitigation := range []bool{false, true} {
   267  		name := "NoMitigation"
   268  		if mitigation {
   269  			name = "WithMitigation"
   270  		}
   271  		b.Run(name, func(b *testing.B) {
   272  			for _, numWorkers := range []int{1, 10, 100} {
   273  				b.Run(fmt.Sprintf("%d", numWorkers), func(b *testing.B) {
   274  					const cacheSize = 10
   275  					c := New(cacheSize * maxCachedSize)
   276  					numOpsPerWorker := (b.N + numWorkers - 1) / numWorkers
   277  					var wg sync.WaitGroup
   278  					wg.Add(numWorkers)
   279  					for i := 0; i < numWorkers; i++ {
   280  						workerID := i
   281  						go func() {
   282  							var s Session
   283  							s.Init()
   284  							cd := CachedData{Memo: &memo.Memo{}}
   285  							for j := 0; j < numOpsPerWorker; j++ {
   286  								str := fmt.Sprintf("%d/%d", workerID, j)
   287  								if _, ok := c.Find(&s, str); ok {
   288  									panic("found")
   289  								}
   290  								cd.SQL = str
   291  								if !mitigation {
   292  									// Disable the mitigation mechanism.
   293  									s.missRatioMMA = 0
   294  								}
   295  								c.Add(&s, &cd)
   296  							}
   297  							wg.Done()
   298  						}()
   299  					}
   300  					wg.Wait()
   301  				})
   302  			}
   303  		})
   304  	}
   305  }