github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvserver/closedts/storage/storage_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 storage
    12  
    13  import (
    14  	"fmt"
    15  	"math/rand"
    16  	"testing"
    17  	"time"
    18  
    19  	"github.com/cockroachdb/cockroach/pkg/kv/kvserver/closedts/ctpb"
    20  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    21  	"github.com/cockroachdb/cockroach/pkg/util/hlc"
    22  	"github.com/cockroachdb/cockroach/pkg/util/leaktest"
    23  	"github.com/cockroachdb/cockroach/pkg/util/randutil"
    24  	"github.com/cockroachdb/cockroach/pkg/util/timeutil"
    25  	"github.com/cockroachdb/errors"
    26  	"golang.org/x/sync/errgroup"
    27  )
    28  
    29  func ExampleSingleStorage() {
    30  	s := NewMemStorage(10*time.Second, 4)
    31  	fmt.Println("The empty storage renders as below:")
    32  	fmt.Println(s)
    33  
    34  	fmt.Println("After adding the following entry:")
    35  	e1 := ctpb.Entry{
    36  		Full:            true,
    37  		ClosedTimestamp: hlc.Timestamp{WallTime: 123e9},
    38  		MLAI: map[roachpb.RangeID]ctpb.LAI{
    39  			1: 1000,
    40  			9: 2000,
    41  		},
    42  	}
    43  	fmt.Println(e1)
    44  	s.Add(e1)
    45  	fmt.Println("the result is:")
    46  	fmt.Println(s)
    47  	fmt.Println("Note how the most recent bucket picked up the update.")
    48  
    49  	fmt.Println("A new update comes in only two seconds later:")
    50  	e2 := ctpb.Entry{
    51  		ClosedTimestamp: hlc.Timestamp{WallTime: 125e9},
    52  		MLAI: map[roachpb.RangeID]ctpb.LAI{
    53  			1: 1001,
    54  			7: 12,
    55  		},
    56  	}
    57  	fmt.Println(e2)
    58  	s.Add(e2)
    59  	fmt.Println("The first bucket now contains the union of both updates.")
    60  	fmt.Println("The second bucket holds on to the previous value of the first.")
    61  	fmt.Println("The remaining buckets are unchanged. The best we could do is")
    62  	fmt.Println("give them identical copies of the second, but that's nonsense.")
    63  	fmt.Println(s)
    64  
    65  	fmt.Println("Another update, another eight seconds later:")
    66  	e3 := ctpb.Entry{
    67  		ClosedTimestamp: hlc.Timestamp{WallTime: 133e9},
    68  		MLAI: map[roachpb.RangeID]ctpb.LAI{
    69  			9: 2020,
    70  			1: 999,
    71  		},
    72  	}
    73  	fmt.Println(e3)
    74  	s.Add(e3)
    75  	fmt.Println("Note how the second bucket didn't rotate, for it is not yet")
    76  	fmt.Println("older than 10s. Note also how the first bucket ignores the")
    77  	fmt.Println("downgrade for r1; these can occur in practice.")
    78  	fmt.Println(s)
    79  
    80  	fmt.Println("Half a second later, with the next update, it will rotate:")
    81  	e4 := ctpb.Entry{
    82  		ClosedTimestamp: hlc.Timestamp{WallTime: 133e9 + 1e9/2},
    83  		MLAI: map[roachpb.RangeID]ctpb.LAI{
    84  			7: 17,
    85  			8: 711,
    86  		},
    87  	}
    88  	fmt.Println(e4)
    89  	s.Add(e4)
    90  	fmt.Println("Consequently we now see the third bucket fill up.")
    91  	fmt.Println(s)
    92  
    93  	fmt.Println("Next update arrives a whopping 46.5s later (why not).")
    94  	e5 := ctpb.Entry{
    95  		ClosedTimestamp: hlc.Timestamp{WallTime: 180e9},
    96  		MLAI: map[roachpb.RangeID]ctpb.LAI{
    97  			1: 1004,
    98  			7: 19,
    99  			2: 929922,
   100  		},
   101  	}
   102  	fmt.Println(e5)
   103  	s.Add(e5)
   104  	fmt.Println("The second bucket rotated, but due to the sparseness of updates,")
   105  	fmt.Println("it's still above its target age and will rotate again next time.")
   106  	fmt.Println("The same is true for the remaining buckets.")
   107  	fmt.Println(s)
   108  
   109  	fmt.Println("Another five seconds later, another update:")
   110  	e6 := ctpb.Entry{
   111  		ClosedTimestamp: hlc.Timestamp{WallTime: 185e9},
   112  		MLAI: map[roachpb.RangeID]ctpb.LAI{
   113  			3: 1771,
   114  		},
   115  	}
   116  	fmt.Println(e6)
   117  	s.Add(e6)
   118  	fmt.Println("All buckets rotate, but the third and fourth remain over target age.")
   119  	fmt.Println("This would resolve itself if reasonably spaced updates kept coming in.")
   120  	fmt.Println(s)
   121  
   122  	fmt.Println("Finally, when the storage is cleared, all buckets are reset.")
   123  	s.Clear()
   124  	fmt.Println(s)
   125  
   126  	// Output:
   127  	// The empty storage renders as below:
   128  	// +--+---------------------+----------------------+----------------------+----------------------+
   129  	//      0,0 age=0s (target     0,0 age=0s (target     0,0 age=0s (target     0,0 age=0s (target
   130  	//         ≤0s) epoch=0          ≤10s) epoch=0          ≤20s) epoch=0          ≤40s) epoch=0
   131  	// +--+---------------------+----------------------+----------------------+----------------------+
   132  	// +--+---------------------+----------------------+----------------------+----------------------+
   133  	//
   134  	// After adding the following entry:
   135  	// CT: 123.000000000,0 @ Epoch 0
   136  	// Full: true
   137  	// MLAI: r1: 1000, r9: 2000
   138  	//
   139  	// the result is:
   140  	// +----+---------------------+------------------------+------------------------+------------------------+
   141  	//          123.000000000,0      0,0 age=2m3s (target     0,0 age=2m3s (target     0,0 age=2m3s (target
   142  	//        age=0s (target ≤0s)       ≤10s) epoch=0            ≤20s) epoch=0            ≤40s) epoch=0
   143  	//              epoch=0
   144  	// +----+---------------------+------------------------+------------------------+------------------------+
   145  	//   r1                  1000
   146  	//   r9                  2000
   147  	// +----+---------------------+------------------------+------------------------+------------------------+
   148  	//
   149  	// Note how the most recent bucket picked up the update.
   150  	// A new update comes in only two seconds later:
   151  	// CT: 125.000000000,0 @ Epoch 0
   152  	// Full: false
   153  	// MLAI: r1: 1001, r7: 12
   154  	//
   155  	// The first bucket now contains the union of both updates.
   156  	// The second bucket holds on to the previous value of the first.
   157  	// The remaining buckets are unchanged. The best we could do is
   158  	// give them identical copies of the second, but that's nonsense.
   159  	// +----+---------------------+----------------------+------------------------+------------------------+
   160  	//          125.000000000,0       123.000000000,0       0,0 age=2m5s (target     0,0 age=2m5s (target
   161  	//        age=0s (target ≤0s)   age=2s (target ≤10s)       ≤20s) epoch=0            ≤40s) epoch=0
   162  	//              epoch=0               epoch=0
   163  	// +----+---------------------+----------------------+------------------------+------------------------+
   164  	//   r1                  1001                   1000
   165  	//   r7                    12
   166  	//   r9                  2000                   2000
   167  	// +----+---------------------+----------------------+------------------------+------------------------+
   168  	//
   169  	// Another update, another eight seconds later:
   170  	// CT: 133.000000000,0 @ Epoch 0
   171  	// Full: false
   172  	// MLAI: r1: 999, r9: 2020
   173  	//
   174  	// Note how the second bucket didn't rotate, for it is not yet
   175  	// older than 10s. Note also how the first bucket ignores the
   176  	// downgrade for r1; these can occur in practice.
   177  	// +----+---------------------+-----------------------+-------------------------+-------------------------+
   178  	//          133.000000000,0        123.000000000,0       0,0 age=2m13s (target     0,0 age=2m13s (target
   179  	//        age=0s (target ≤0s)   age=10s (target ≤10s)        ≤20s) epoch=0             ≤40s) epoch=0
   180  	//              epoch=0                epoch=0
   181  	// +----+---------------------+-----------------------+-------------------------+-------------------------+
   182  	//   r1                  1001                    1000
   183  	//   r7                    12
   184  	//   r9                  2020                    2000
   185  	// +----+---------------------+-----------------------+-------------------------+-------------------------+
   186  	//
   187  	// Half a second later, with the next update, it will rotate:
   188  	// CT: 133.500000000,0 @ Epoch 0
   189  	// Full: false
   190  	// MLAI: r7: 17, r8: 711
   191  	//
   192  	// Consequently we now see the third bucket fill up.
   193  	// +----+---------------------+-------------------------+-------------------------+---------------------------+
   194  	//          133.500000000,0         133.000000000,0           123.000000000,0        0,0 age=2m13.5s (target
   195  	//        age=0s (target ≤0s)   age=500ms (target ≤10s)   age=10.5s (target ≤20s)         ≤40s) epoch=0
   196  	//              epoch=0                 epoch=0                   epoch=0
   197  	// +----+---------------------+-------------------------+-------------------------+---------------------------+
   198  	//   r1                  1001                      1001                      1000
   199  	//   r7                    17                        12
   200  	//   r8                   711
   201  	//   r9                  2020                      2020                      2000
   202  	// +----+---------------------+-------------------------+-------------------------+---------------------------+
   203  	//
   204  	// Next update arrives a whopping 46.5s later (why not).
   205  	// CT: 180.000000000,0 @ Epoch 0
   206  	// Full: false
   207  	// MLAI: r1: 1004, r2: 929922, r7: 19
   208  	//
   209  	// The second bucket rotated, but due to the sparseness of updates,
   210  	// it's still above its target age and will rotate again next time.
   211  	// The same is true for the remaining buckets.
   212  	// +----+---------------------+-------------------------+-----------------------+-----------------------+
   213  	//          180.000000000,0         133.500000000,0          133.000000000,0         123.000000000,0
   214  	//        age=0s (target ≤0s)   age=46.5s (target ≤10s)   age=47s (target ≤20s)   age=57s (target ≤40s)
   215  	//              epoch=0                 epoch=0                  epoch=0                 epoch=0
   216  	// +----+---------------------+-------------------------+-----------------------+-----------------------+
   217  	//   r1                  1004                      1001                    1001                    1000
   218  	//   r2                929922
   219  	//   r7                    19                        17                      12
   220  	//   r8                   711                       711
   221  	//   r9                  2020                      2020                    2020                    2000
   222  	// +----+---------------------+-------------------------+-----------------------+-----------------------+
   223  	//
   224  	// Another five seconds later, another update:
   225  	// CT: 185.000000000,0 @ Epoch 0
   226  	// Full: false
   227  	// MLAI: r3: 1771
   228  	//
   229  	// All buckets rotate, but the third and fourth remain over target age.
   230  	// This would resolve itself if reasonably spaced updates kept coming in.
   231  	// +----+---------------------+----------------------+-------------------------+-----------------------+
   232  	//          185.000000000,0       180.000000000,0          133.500000000,0          133.000000000,0
   233  	//        age=0s (target ≤0s)   age=5s (target ≤10s)   age=51.5s (target ≤20s)   age=52s (target ≤40s)
   234  	//              epoch=0               epoch=0                  epoch=0                  epoch=0
   235  	// +----+---------------------+----------------------+-------------------------+-----------------------+
   236  	//   r1                  1004                   1004                      1001                    1001
   237  	//   r2                929922                 929922
   238  	//   r3                  1771
   239  	//   r7                    19                     19                        17                      12
   240  	//   r8                   711                    711                       711
   241  	//   r9                  2020                   2020                      2020                    2020
   242  	// +----+---------------------+----------------------+-------------------------+-----------------------+
   243  	//
   244  	// Finally, when the storage is cleared, all buckets are reset.
   245  	// +--+---------------------+----------------------+----------------------+----------------------+
   246  	//      0,0 age=0s (target     0,0 age=0s (target     0,0 age=0s (target     0,0 age=0s (target
   247  	//         ≤0s) epoch=0          ≤10s) epoch=0          ≤20s) epoch=0          ≤40s) epoch=0
   248  	// +--+---------------------+----------------------+----------------------+----------------------+
   249  	// +--+---------------------+----------------------+----------------------+----------------------+
   250  }
   251  
   252  func ExampleMultiStorage_epoch() {
   253  	ms := NewMultiStorage(func() SingleStorage {
   254  		return NewMemStorage(time.Millisecond, 2)
   255  	})
   256  
   257  	e1 := ctpb.Entry{
   258  		Epoch:           10,
   259  		ClosedTimestamp: hlc.Timestamp{WallTime: 1e9},
   260  		MLAI: map[roachpb.RangeID]ctpb.LAI{
   261  			9: 17,
   262  		},
   263  	}
   264  	fmt.Println("First, the following entry is added:")
   265  	fmt.Println(e1)
   266  	ms.Add(1, e1)
   267  	fmt.Println(ms)
   268  
   269  	fmt.Println("The epoch changes. It can only increase, for we receive Entries in a fixed order.")
   270  	e2 := ctpb.Entry{
   271  		Epoch:           11,
   272  		ClosedTimestamp: hlc.Timestamp{WallTime: 2e9},
   273  		MLAI: map[roachpb.RangeID]ctpb.LAI{
   274  			9:  18,
   275  			10: 99,
   276  		},
   277  	}
   278  	ms.Add(1, e2)
   279  	fmt.Println(e2)
   280  	fmt.Println(ms)
   281  
   282  	fmt.Println("If it *did* decrease, a higher level component should trigger an assertion.")
   283  	fmt.Println("The storage itself will simply ignore such updates:")
   284  	e3 := ctpb.Entry{
   285  		Epoch:           8,
   286  		ClosedTimestamp: hlc.Timestamp{WallTime: 3e9},
   287  		MLAI: map[roachpb.RangeID]ctpb.LAI{
   288  			9:  19,
   289  			10: 199,
   290  		},
   291  	}
   292  	fmt.Println(e3)
   293  	ms.Add(1, e3)
   294  	fmt.Println(ms)
   295  
   296  	// Output:
   297  	// First, the following entry is added:
   298  	// CT: 1.000000000,0 @ Epoch 10
   299  	// Full: false
   300  	// MLAI: r9: 17
   301  	//
   302  	// ***** n1 *****
   303  	// +----+---------------------+----------------------+
   304  	//           1.000000000,0       0,0 age=1s (target
   305  	//        age=0s (target ≤0s)      ≤1ms) epoch=0
   306  	//             epoch=10
   307  	// +----+---------------------+----------------------+
   308  	//   r9                    17
   309  	// +----+---------------------+----------------------+
   310  	//
   311  	// The epoch changes. It can only increase, for we receive Entries in a fixed order.
   312  	// CT: 2.000000000,0 @ Epoch 11
   313  	// Full: false
   314  	// MLAI: r9: 18, r10: 99
   315  	//
   316  	// ***** n1 *****
   317  	// +-----+---------------------+----------------------+
   318  	//            2.000000000,0         1.000000000,0
   319  	//         age=0s (target ≤0s)   age=1s (target ≤1ms)
   320  	//              epoch=11               epoch=10
   321  	// +-----+---------------------+----------------------+
   322  	//   r9                     18                     17
   323  	//   r10                    99
   324  	// +-----+---------------------+----------------------+
   325  	//
   326  	// If it *did* decrease, a higher level component should trigger an assertion.
   327  	// The storage itself will simply ignore such updates:
   328  	// CT: 3.000000000,0 @ Epoch 8
   329  	// Full: false
   330  	// MLAI: r9: 19, r10: 199
   331  	//
   332  	// ***** n1 *****
   333  	// +-----+---------------------+----------------------+
   334  	//            2.000000000,0         2.000000000,0
   335  	//         age=0s (target ≤0s)   age=0s (target ≤1ms)
   336  	//              epoch=11               epoch=11
   337  	// +-----+---------------------+----------------------+
   338  	//   r9                     18                     18
   339  	//   r10                    99                     99
   340  	// +-----+---------------------+----------------------+
   341  }
   342  
   343  func TestZeroValueGetsStored(t *testing.T) {
   344  	defer leaktest.AfterTest(t)()
   345  	// This test ensures that a zero values MLAI is stored for an epoch especially
   346  	// after we've already stored a non-zero MLAI for a different range in the
   347  	// same epoch. See #32904.
   348  	ms := NewMultiStorage(func() SingleStorage {
   349  		return NewMemStorage(time.Millisecond, 10)
   350  	})
   351  	e := ctpb.Entry{
   352  		Epoch:           1,
   353  		ClosedTimestamp: hlc.Timestamp{WallTime: timeutil.Now().UnixNano()},
   354  		MLAI:            map[roachpb.RangeID]ctpb.LAI{1: 1},
   355  	}
   356  	ms.Add(1, e)
   357  	e.ClosedTimestamp.WallTime++
   358  	r := roachpb.RangeID(2)
   359  	e.MLAI = map[roachpb.RangeID]ctpb.LAI{r: 0}
   360  	ms.Add(1, e)
   361  	var seen bool
   362  	ms.VisitDescending(1, func(e ctpb.Entry) (done bool) {
   363  		for rr, mlai := range e.MLAI {
   364  			if rr == r && mlai == 0 {
   365  				seen = true
   366  				return true
   367  			}
   368  		}
   369  		return false
   370  	})
   371  	if !seen {
   372  		t.Fatalf("Failed to see added zero value MLAI for range %v", r)
   373  	}
   374  }
   375  
   376  // TestConcurrent runs a very basic sanity check against a Storage, verifiying
   377  // that the bucketed Entries don't regress in obvious ways.
   378  func TestConcurrent(t *testing.T) {
   379  	defer leaktest.AfterTest(t)()
   380  
   381  	ms := NewMultiStorage(func() SingleStorage {
   382  		return NewMemStorage(time.Millisecond, 10)
   383  	})
   384  
   385  	var g errgroup.Group
   386  
   387  	const (
   388  		iters             = 10
   389  		numNodes          = roachpb.NodeID(2)
   390  		numRanges         = roachpb.RangeID(3)
   391  		numReadersPerNode = 3
   392  		numWritersPerNode = 3
   393  	)
   394  
   395  	// concurrently add and read from storage
   396  	// after add: needs to be visible to future read
   397  	// read ts never regresses
   398  	globalRand, seed := randutil.NewPseudoRand()
   399  	t.Log("seed is", seed)
   400  
   401  	for i := 0; i < numWritersPerNode; i++ {
   402  		for nodeID := roachpb.NodeID(1); nodeID <= numNodes; nodeID++ {
   403  			nodeID := nodeID // goroutine-local copy
   404  			for i := 0; i < iters; i++ {
   405  				r := rand.New(rand.NewSource(globalRand.Int63()))
   406  				m := make(map[roachpb.RangeID]ctpb.LAI)
   407  				for rangeID := roachpb.RangeID(1); rangeID < numRanges; rangeID++ {
   408  					if r.Intn(int(numRanges)) == 0 {
   409  						continue
   410  					}
   411  					m[rangeID] = ctpb.LAI(rand.Intn(100))
   412  				}
   413  				ct := hlc.Timestamp{WallTime: r.Int63n(100), Logical: r.Int31n(10)}
   414  				epo := ctpb.Epoch(r.Int63n(100))
   415  				g.Go(func() error {
   416  					<-time.After(time.Duration(rand.Intn(1e7)))
   417  					ms.Add(nodeID, ctpb.Entry{
   418  						Epoch:           epo,
   419  						ClosedTimestamp: ct,
   420  						MLAI:            m,
   421  					})
   422  					return nil
   423  				})
   424  			}
   425  		}
   426  	}
   427  
   428  	for i := 0; i < numReadersPerNode; i++ {
   429  		for nodeID := roachpb.NodeID(1); nodeID <= numNodes; nodeID++ {
   430  			nodeID := nodeID
   431  			g.Go(func() error {
   432  				epo := ctpb.Epoch(-1)
   433  				var ct hlc.Timestamp
   434  				var mlai map[roachpb.RangeID]ctpb.LAI
   435  				var err error
   436  				var n int
   437  				ms.VisitDescending(nodeID, func(e ctpb.Entry) bool {
   438  					n++
   439  					if n > 1 && e.Epoch > epo {
   440  						err = errors.Errorf("epoch regressed from %d to %d", epo, e.Epoch)
   441  						return true // done
   442  					}
   443  					if n > 1 && ct.Less(e.ClosedTimestamp) {
   444  						err = errors.Errorf("closed timestamp regressed from %s to %s", ct, e.ClosedTimestamp)
   445  						return true // done
   446  					}
   447  					for rangeID := roachpb.RangeID(1); rangeID <= numRanges; rangeID++ {
   448  						if l := mlai[rangeID]; l < e.MLAI[rangeID] && n > 1 {
   449  							err = errors.Errorf("MLAI for r%d regressed: %+v to %+v", rangeID, mlai, e.MLAI)
   450  							return true // done
   451  						}
   452  					}
   453  
   454  					epo = e.Epoch
   455  					ct = e.ClosedTimestamp
   456  					mlai = e.MLAI
   457  					return false // not done
   458  				})
   459  				return err
   460  			})
   461  		}
   462  	}
   463  
   464  	if err := g.Wait(); err != nil {
   465  		t.Fatal(err)
   466  	}
   467  }