github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvserver/compactor/compactor_test.go (about)

     1  // Copyright 2017 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 compactor
    12  
    13  import (
    14  	"context"
    15  	"fmt"
    16  	"reflect"
    17  	"sort"
    18  	"sync/atomic"
    19  	"testing"
    20  	"time"
    21  
    22  	"github.com/cockroachdb/cockroach/pkg/keys"
    23  	"github.com/cockroachdb/cockroach/pkg/kv/kvserver/kvserverpb"
    24  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    25  	"github.com/cockroachdb/cockroach/pkg/settings/cluster"
    26  	"github.com/cockroachdb/cockroach/pkg/storage"
    27  	"github.com/cockroachdb/cockroach/pkg/storage/enginepb"
    28  	"github.com/cockroachdb/cockroach/pkg/testutils"
    29  	"github.com/cockroachdb/cockroach/pkg/util/leaktest"
    30  	"github.com/cockroachdb/cockroach/pkg/util/stop"
    31  	"github.com/cockroachdb/cockroach/pkg/util/syncutil"
    32  	"github.com/cockroachdb/cockroach/pkg/util/timeutil"
    33  	"github.com/cockroachdb/errors"
    34  )
    35  
    36  const testCompactionLatency = 1 * time.Millisecond
    37  
    38  type wrappedEngine struct {
    39  	storage.Engine
    40  	mu struct {
    41  		syncutil.Mutex
    42  		compactions []roachpb.Span
    43  	}
    44  }
    45  
    46  func newWrappedEngine() *wrappedEngine {
    47  	return &wrappedEngine{
    48  		Engine: storage.NewDefaultInMem(),
    49  	}
    50  }
    51  
    52  func (we *wrappedEngine) GetSSTables() storage.SSTableInfos {
    53  	key := func(s string) storage.MVCCKey {
    54  		return storage.MakeMVCCMetadataKey([]byte(s))
    55  	}
    56  	ssti := storage.SSTableInfos{
    57  		// Level 0.
    58  		{Level: 0, Size: 20, Start: key("a"), End: key("z")},
    59  		{Level: 0, Size: 15, Start: key("a"), End: key("k")},
    60  		// Level 2.
    61  		{Level: 2, Size: 200, Start: key("a"), End: key("j")},
    62  		{Level: 2, Size: 100, Start: key("k"), End: key("o")},
    63  		{Level: 2, Size: 100, Start: key("r"), End: key("t")},
    64  		// Level 6.
    65  		{Level: 6, Size: 200, Start: key("0"), End: key("9")},
    66  		{Level: 6, Size: 201, Start: key("a"), End: key("c")},
    67  		{Level: 6, Size: 200, Start: key("d"), End: key("f")},
    68  		{Level: 6, Size: 300, Start: key("h"), End: key("r")},
    69  		{Level: 6, Size: 405, Start: key("s"), End: key("z")},
    70  	}
    71  	sort.Sort(ssti)
    72  	return ssti
    73  }
    74  
    75  func (we *wrappedEngine) CompactRange(start, end roachpb.Key, forceBottommost bool) error {
    76  	we.mu.Lock()
    77  	defer we.mu.Unlock()
    78  	time.Sleep(testCompactionLatency)
    79  	we.mu.compactions = append(we.mu.compactions, roachpb.Span{Key: start, EndKey: end})
    80  	return nil
    81  }
    82  
    83  func (we *wrappedEngine) GetCompactions() []roachpb.Span {
    84  	we.mu.Lock()
    85  	defer we.mu.Unlock()
    86  	return append([]roachpb.Span(nil), we.mu.compactions...)
    87  }
    88  
    89  func testSetup(capFn storeCapacityFunc) (*Compactor, *wrappedEngine, *int32, func()) {
    90  	stopper := stop.NewStopper()
    91  	eng := newWrappedEngine()
    92  	stopper.AddCloser(eng)
    93  	compactionCount := new(int32)
    94  	doneFn := func(_ context.Context) { atomic.AddInt32(compactionCount, 1) }
    95  	st := cluster.MakeTestingClusterSettings()
    96  	compactor := NewCompactor(st, eng, capFn, doneFn)
    97  	compactor.Start(context.Background(), stopper)
    98  	return compactor, eng, compactionCount, func() {
    99  		stopper.Stop(context.Background())
   100  	}
   101  }
   102  
   103  func key(s string) roachpb.Key {
   104  	return roachpb.Key([]byte(s))
   105  }
   106  
   107  // TestCompactorThresholds verifies the thresholding logic for the compactor.
   108  func TestCompactorThresholds(t *testing.T) {
   109  	defer leaktest.AfterTest(t)()
   110  
   111  	// This test relies on concurrently waiting for a value to change in the
   112  	// underlying engine(s). Since the teeing engine does not respond well to
   113  	// value mismatches, whether transient or permanent, skip this test if the
   114  	// teeing engine is being used. See
   115  	// https://github.com/cockroachdb/cockroach/issues/42656 for more context.
   116  	if storage.DefaultStorageEngine == enginepb.EngineTypeTeePebbleRocksDB {
   117  		t.Skip("disabled on teeing engine")
   118  	}
   119  
   120  	fractionUsedThresh := thresholdBytesUsedFraction.Default()*float64(thresholdBytes.Default()) + 1
   121  	fractionAvailableThresh := thresholdBytesAvailableFraction.Default()*float64(thresholdBytes.Default()) + 1
   122  	nowNanos := timeutil.Now().UnixNano()
   123  	testCases := []struct {
   124  		name              string
   125  		suggestions       []kvserverpb.SuggestedCompaction
   126  		logicalBytes      int64 // logical byte count to return with store capacity
   127  		availableBytes    int64 // available byte count to return with store capacity
   128  		expBytesCompacted int64
   129  		expCompactions    []roachpb.Span
   130  		expUncompacted    []roachpb.Span
   131  	}{
   132  		// Single suggestion under all thresholds.
   133  		{
   134  			name: "single suggestion under all thresholds",
   135  			suggestions: []kvserverpb.SuggestedCompaction{
   136  				{
   137  					StartKey: key("a"), EndKey: key("b"),
   138  					Compaction: kvserverpb.Compaction{
   139  						Bytes:            thresholdBytes.Default() - 1,
   140  						SuggestedAtNanos: nowNanos,
   141  					},
   142  				},
   143  			},
   144  			logicalBytes:      thresholdBytes.Default() * 100, // not going to trigger fractional threshold
   145  			availableBytes:    thresholdBytes.Default() * 100, // not going to trigger fractional threshold
   146  			expBytesCompacted: 0,
   147  			expCompactions:    nil,
   148  			expUncompacted: []roachpb.Span{
   149  				{Key: key("a"), EndKey: key("b")},
   150  			},
   151  		},
   152  		// Single suggestion which is over absolute bytes threshold should compact.
   153  		{
   154  			name: "single suggestion over absolute threshold",
   155  			suggestions: []kvserverpb.SuggestedCompaction{
   156  				{
   157  					StartKey: key("a"), EndKey: key("b"),
   158  					Compaction: kvserverpb.Compaction{
   159  						Bytes:            thresholdBytes.Default(),
   160  						SuggestedAtNanos: nowNanos,
   161  					},
   162  				},
   163  			},
   164  			logicalBytes:      thresholdBytes.Default() * 100, // not going to trigger fractional threshold
   165  			availableBytes:    thresholdBytes.Default() * 100, // not going to trigger fractional threshold
   166  			expBytesCompacted: thresholdBytes.Default(),
   167  			expCompactions: []roachpb.Span{
   168  				{Key: key("a"), EndKey: key("b")},
   169  			},
   170  		},
   171  		// Single suggestion which is over absolute bytes threshold should not
   172  		// trigger a compaction if the span contains keys.
   173  		{
   174  			name: "outdated single suggestion over absolute threshold",
   175  			suggestions: []kvserverpb.SuggestedCompaction{
   176  				{
   177  					StartKey: key("0"), EndKey: key("9"),
   178  					Compaction: kvserverpb.Compaction{
   179  						Bytes:            thresholdBytes.Default(),
   180  						SuggestedAtNanos: nowNanos,
   181  					},
   182  				},
   183  			},
   184  			logicalBytes:      thresholdBytes.Default() * 100, // not going to trigger fractional threshold
   185  			availableBytes:    thresholdBytes.Default() * 100, // not going to trigger fractional threshold
   186  			expBytesCompacted: 0,
   187  			expCompactions:    nil,
   188  			expUncompacted: []roachpb.Span{
   189  				{Key: key("0"), EndKey: key("9")},
   190  			},
   191  		},
   192  		// Single suggestion over the fractional threshold.
   193  		{
   194  			name: "single suggestion over fractional threshold",
   195  			suggestions: []kvserverpb.SuggestedCompaction{
   196  				{
   197  					StartKey: key("a"), EndKey: key("b"),
   198  					Compaction: kvserverpb.Compaction{
   199  						Bytes:            int64(fractionUsedThresh),
   200  						SuggestedAtNanos: nowNanos,
   201  					},
   202  				},
   203  			},
   204  			logicalBytes:      thresholdBytes.Default(),
   205  			availableBytes:    thresholdBytes.Default() * 100, // not going to trigger fractional threshold
   206  			expBytesCompacted: int64(fractionUsedThresh),
   207  			expCompactions: []roachpb.Span{
   208  				{Key: key("a"), EndKey: key("b")},
   209  			},
   210  		},
   211  		// Single suggestion over the fractional bytes available threshold.
   212  		{
   213  			name: "single suggestion over fractional bytes available threshold",
   214  			suggestions: []kvserverpb.SuggestedCompaction{
   215  				{
   216  					StartKey: key("a"), EndKey: key("b"),
   217  					Compaction: kvserverpb.Compaction{
   218  						Bytes:            int64(fractionAvailableThresh),
   219  						SuggestedAtNanos: nowNanos,
   220  					},
   221  				},
   222  			},
   223  			logicalBytes:      thresholdBytes.Default() * 100, // not going to trigger fractional threshold
   224  			availableBytes:    thresholdBytes.Default(),
   225  			expBytesCompacted: int64(fractionAvailableThresh),
   226  			expCompactions: []roachpb.Span{
   227  				{Key: key("a"), EndKey: key("b")},
   228  			},
   229  		},
   230  		// Double suggestion which in aggregate exceed absolute bytes threshold.
   231  		{
   232  			name: "double suggestion over absolute threshold",
   233  			suggestions: []kvserverpb.SuggestedCompaction{
   234  				{
   235  					StartKey: key("a"), EndKey: key("b"),
   236  					Compaction: kvserverpb.Compaction{
   237  						Bytes:            thresholdBytes.Default() / 2,
   238  						SuggestedAtNanos: nowNanos,
   239  					},
   240  				},
   241  				{
   242  					StartKey: key("b"), EndKey: key("c"),
   243  					Compaction: kvserverpb.Compaction{
   244  						Bytes:            thresholdBytes.Default() - (thresholdBytes.Default() / 2),
   245  						SuggestedAtNanos: nowNanos,
   246  					},
   247  				},
   248  			},
   249  			logicalBytes:      thresholdBytes.Default() * 100, // not going to trigger fractional threshold
   250  			availableBytes:    thresholdBytes.Default() * 100, // not going to trigger fractional threshold
   251  			expBytesCompacted: thresholdBytes.Default(),
   252  			expCompactions: []roachpb.Span{
   253  				{Key: key("a"), EndKey: key("c")},
   254  			},
   255  		},
   256  		// Double suggestion to same span.
   257  		{
   258  			name: "double suggestion to same span over absolute threshold",
   259  			suggestions: []kvserverpb.SuggestedCompaction{
   260  				{
   261  					StartKey: key("a"), EndKey: key("b"),
   262  					Compaction: kvserverpb.Compaction{
   263  						Bytes:            thresholdBytes.Default() / 2,
   264  						SuggestedAtNanos: nowNanos,
   265  					},
   266  				},
   267  				{
   268  					StartKey: key("a"), EndKey: key("b"),
   269  					Compaction: kvserverpb.Compaction{
   270  						Bytes:            thresholdBytes.Default() - (thresholdBytes.Default() / 2),
   271  						SuggestedAtNanos: nowNanos,
   272  					},
   273  				},
   274  			},
   275  			logicalBytes:      thresholdBytes.Default() * 100, // not going to trigger fractional threshold
   276  			availableBytes:    thresholdBytes.Default() * 100, // not going to trigger fractional threshold
   277  			expBytesCompacted: thresholdBytes.Default(),
   278  			expCompactions: []roachpb.Span{
   279  				{Key: key("a"), EndKey: key("b")},
   280  			},
   281  		},
   282  		// Double suggestion overlapping.
   283  		{
   284  			name: "double suggestion overlapping over absolute threshold",
   285  			suggestions: []kvserverpb.SuggestedCompaction{
   286  				{
   287  					StartKey: key("a"), EndKey: key("c"),
   288  					Compaction: kvserverpb.Compaction{
   289  						Bytes:            thresholdBytes.Default() / 2,
   290  						SuggestedAtNanos: nowNanos,
   291  					},
   292  				},
   293  				{
   294  					StartKey: key("b"), EndKey: key("d"),
   295  					Compaction: kvserverpb.Compaction{
   296  						Bytes:            thresholdBytes.Default() - (thresholdBytes.Default() / 2),
   297  						SuggestedAtNanos: nowNanos,
   298  					},
   299  				},
   300  			},
   301  			logicalBytes:      thresholdBytes.Default() * 100, // not going to trigger fractional threshold
   302  			availableBytes:    thresholdBytes.Default() * 100, // not going to trigger fractional threshold
   303  			expBytesCompacted: thresholdBytes.Default(),
   304  			expCompactions: []roachpb.Span{
   305  				{Key: key("a"), EndKey: key("d")},
   306  			},
   307  		},
   308  		// Double suggestion which in aggregate exceeds fractional bytes threshold.
   309  		{
   310  			name: "double suggestion over fractional threshold",
   311  			suggestions: []kvserverpb.SuggestedCompaction{
   312  				{
   313  					StartKey: key("a"), EndKey: key("b"),
   314  					Compaction: kvserverpb.Compaction{
   315  						Bytes:            int64(fractionUsedThresh / 2),
   316  						SuggestedAtNanos: nowNanos,
   317  					},
   318  				},
   319  				{
   320  					StartKey: key("b"), EndKey: key("c"),
   321  					Compaction: kvserverpb.Compaction{
   322  						Bytes:            int64(fractionUsedThresh) - int64(fractionUsedThresh/2),
   323  						SuggestedAtNanos: nowNanos,
   324  					},
   325  				},
   326  			},
   327  			logicalBytes:      thresholdBytes.Default(),
   328  			availableBytes:    thresholdBytes.Default() * 100, // not going to trigger fractional threshold
   329  			expBytesCompacted: int64(fractionUsedThresh),
   330  			expCompactions: []roachpb.Span{
   331  				{Key: key("a"), EndKey: key("c")},
   332  			},
   333  		},
   334  		// Double suggestion without excessive gap.
   335  		{
   336  			name: "double suggestion without excessive gap",
   337  			suggestions: []kvserverpb.SuggestedCompaction{
   338  				{
   339  					StartKey: key("a"), EndKey: key("b"),
   340  					Compaction: kvserverpb.Compaction{
   341  						Bytes:            thresholdBytes.Default() / 2,
   342  						SuggestedAtNanos: nowNanos,
   343  					},
   344  				},
   345  				// There are only two sstables between ("b", "e") at the max level.
   346  				{
   347  					StartKey: key("e"), EndKey: key("f"),
   348  					Compaction: kvserverpb.Compaction{
   349  						Bytes:            thresholdBytes.Default() - (thresholdBytes.Default() / 2),
   350  						SuggestedAtNanos: nowNanos,
   351  					},
   352  				},
   353  			},
   354  			logicalBytes:      thresholdBytes.Default() * 100, // not going to trigger fractional threshold
   355  			availableBytes:    thresholdBytes.Default() * 100, // not going to trigger fractional threshold
   356  			expBytesCompacted: thresholdBytes.Default(),
   357  			expCompactions: []roachpb.Span{
   358  				{Key: key("a"), EndKey: key("f")},
   359  			},
   360  		},
   361  		// Double suggestion with non-excessive gap, but there are live keys in the
   362  		// gap.
   363  		//
   364  		// NOTE: when a suggestion itself contains live keys, we skip the compaction
   365  		// because amounts of data may have been added to the span since the
   366  		// compaction was proposed. When only the gap contains live keys, however,
   367  		// it's still desirable to compact: the individual suggestions are empty, so
   368  		// we can assume there's lots of data to reclaim by compacting, and the
   369  		// aggregator is very careful not to jump gaps that span too many SSTs.
   370  		{
   371  			name: "double suggestion over gap with live keys",
   372  			suggestions: []kvserverpb.SuggestedCompaction{
   373  				{
   374  					StartKey: key("0"), EndKey: key("4"),
   375  					Compaction: kvserverpb.Compaction{
   376  						Bytes:            thresholdBytes.Default() / 2,
   377  						SuggestedAtNanos: nowNanos,
   378  					},
   379  				},
   380  				{
   381  					StartKey: key("6"), EndKey: key("9"),
   382  					Compaction: kvserverpb.Compaction{
   383  						Bytes:            thresholdBytes.Default() - (thresholdBytes.Default() / 2),
   384  						SuggestedAtNanos: nowNanos,
   385  					},
   386  				},
   387  			},
   388  			logicalBytes:      thresholdBytes.Default() * 100, // not going to trigger fractional threshold
   389  			availableBytes:    thresholdBytes.Default() * 100, // not going to trigger fractional threshold
   390  			expBytesCompacted: thresholdBytes.Default(),
   391  			expCompactions: []roachpb.Span{
   392  				{Key: key("0"), EndKey: key("9")},
   393  			},
   394  		},
   395  		// Double suggestion with excessive gap.
   396  		{
   397  			name: "double suggestion with excessive gap",
   398  			suggestions: []kvserverpb.SuggestedCompaction{
   399  				{
   400  					StartKey: key("a"), EndKey: key("b"),
   401  					Compaction: kvserverpb.Compaction{
   402  						Bytes:            thresholdBytes.Default() / 2,
   403  						SuggestedAtNanos: nowNanos,
   404  					},
   405  				},
   406  				// There are three sstables between ("b", "h0") at the max level.
   407  				{
   408  					StartKey: key("h0"), EndKey: key("i"),
   409  					Compaction: kvserverpb.Compaction{
   410  						Bytes:            thresholdBytes.Default() - (thresholdBytes.Default() / 2),
   411  						SuggestedAtNanos: nowNanos,
   412  					},
   413  				},
   414  			},
   415  			logicalBytes:      thresholdBytes.Default() * 100, // not going to trigger fractional threshold
   416  			availableBytes:    thresholdBytes.Default() * 100, // not going to trigger fractional threshold
   417  			expBytesCompacted: 0,
   418  			expCompactions:    nil,
   419  			expUncompacted: []roachpb.Span{
   420  				{Key: key("a"), EndKey: key("b")},
   421  				{Key: key("h0"), EndKey: key("i")},
   422  			},
   423  		},
   424  		// Double suggestion with excessive gap, but both over absolute threshold.
   425  		{
   426  			name: "double suggestion with excessive gap but both over threshold",
   427  			suggestions: []kvserverpb.SuggestedCompaction{
   428  				{
   429  					StartKey: key("a"), EndKey: key("b"),
   430  					Compaction: kvserverpb.Compaction{
   431  						Bytes:            thresholdBytes.Default(),
   432  						SuggestedAtNanos: nowNanos,
   433  					},
   434  				},
   435  				// There are three sstables between ("b", "h0") at the max level.
   436  				{
   437  					StartKey: key("h0"), EndKey: key("i"),
   438  					Compaction: kvserverpb.Compaction{
   439  						Bytes:            thresholdBytes.Default(),
   440  						SuggestedAtNanos: nowNanos,
   441  					},
   442  				},
   443  			},
   444  			logicalBytes:      thresholdBytes.Default() * 100, // not going to trigger fractional threshold
   445  			availableBytes:    thresholdBytes.Default() * 100, // not going to trigger fractional threshold
   446  			expBytesCompacted: thresholdBytes.Default() * 2,
   447  			expCompactions: []roachpb.Span{
   448  				{Key: key("a"), EndKey: key("b")},
   449  				{Key: key("h0"), EndKey: key("i")},
   450  			},
   451  		},
   452  		// Double suggestion with excessive gap, with just one over absolute threshold.
   453  		{
   454  			name: "double suggestion with excessive gap but one over threshold",
   455  			suggestions: []kvserverpb.SuggestedCompaction{
   456  				{
   457  					StartKey: key("a"), EndKey: key("b"),
   458  					Compaction: kvserverpb.Compaction{
   459  						Bytes:            thresholdBytes.Default(),
   460  						SuggestedAtNanos: nowNanos,
   461  					},
   462  				},
   463  				// There are three sstables between ("b", "h0") at the max level.
   464  				{
   465  					StartKey: key("h0"), EndKey: key("i"),
   466  					Compaction: kvserverpb.Compaction{
   467  						Bytes:            thresholdBytes.Default() - 1,
   468  						SuggestedAtNanos: nowNanos,
   469  					},
   470  				},
   471  			},
   472  			logicalBytes:      thresholdBytes.Default() * 100, // not going to trigger fractional threshold
   473  			availableBytes:    thresholdBytes.Default() * 100, // not going to trigger fractional threshold
   474  			expBytesCompacted: thresholdBytes.Default(),
   475  			expCompactions: []roachpb.Span{
   476  				{Key: key("a"), EndKey: key("b")},
   477  			},
   478  			expUncompacted: []roachpb.Span{
   479  				{Key: key("h0"), EndKey: key("i")},
   480  			},
   481  		},
   482  		// Quadruple suggestion which can be aggregated into a single compaction.
   483  		{
   484  			name: "quadruple suggestion which aggregates",
   485  			suggestions: []kvserverpb.SuggestedCompaction{
   486  				{
   487  					StartKey: key("a"), EndKey: key("b"),
   488  					Compaction: kvserverpb.Compaction{
   489  						Bytes:            thresholdBytes.Default() / 4,
   490  						SuggestedAtNanos: nowNanos,
   491  					},
   492  				},
   493  				{
   494  					StartKey: key("e"), EndKey: key("f0"),
   495  					Compaction: kvserverpb.Compaction{
   496  						Bytes:            thresholdBytes.Default() / 4,
   497  						SuggestedAtNanos: nowNanos,
   498  					},
   499  				},
   500  				{
   501  					StartKey: key("g"), EndKey: key("q"),
   502  					Compaction: kvserverpb.Compaction{
   503  						Bytes:            thresholdBytes.Default() / 4,
   504  						SuggestedAtNanos: nowNanos,
   505  					},
   506  				},
   507  				{
   508  					StartKey: key("y"), EndKey: key("zzz"),
   509  					Compaction: kvserverpb.Compaction{
   510  						Bytes:            thresholdBytes.Default() - 3*(thresholdBytes.Default()/4),
   511  						SuggestedAtNanos: nowNanos,
   512  					},
   513  				},
   514  			},
   515  			logicalBytes:      thresholdBytes.Default() * 100, // not going to trigger fractional threshold
   516  			availableBytes:    thresholdBytes.Default() * 100, // not going to trigger fractional threshold
   517  			expBytesCompacted: thresholdBytes.Default(),
   518  			expCompactions: []roachpb.Span{
   519  				{Key: key("a"), EndKey: key("zzz")},
   520  			},
   521  		},
   522  	}
   523  
   524  	for _, test := range testCases {
   525  		t.Run(test.name, func(t *testing.T) {
   526  			capacityFn := func() (roachpb.StoreCapacity, error) {
   527  				return roachpb.StoreCapacity{
   528  					LogicalBytes: test.logicalBytes,
   529  					Available:    test.availableBytes,
   530  				}, nil
   531  			}
   532  			compactor, we, compactionCount, cleanup := testSetup(capacityFn)
   533  			defer cleanup()
   534  			// Shorten wait times for compactor processing.
   535  			minInterval.Override(&compactor.st.SV, time.Millisecond)
   536  
   537  			// Add a key so we can test that suggestions that span live data are
   538  			// ignored.
   539  			if err := we.Put(storage.MakeMVCCMetadataKey(key("5")), nil); err != nil {
   540  				t.Fatal(err)
   541  			}
   542  
   543  			for _, sc := range test.suggestions {
   544  				compactor.Suggest(context.Background(), sc)
   545  			}
   546  
   547  			// If we expect no compaction, pause to ensure test will fail if
   548  			// a compaction happens. Note that 10ms is not enough to make
   549  			// this fail all the time, but it will surely trigger a failure
   550  			// most of the time.
   551  			if len(test.expCompactions) == 0 {
   552  				time.Sleep(10 * time.Millisecond)
   553  			}
   554  			testutils.SucceedsSoon(t, func() error {
   555  				comps := we.GetCompactions()
   556  				if !reflect.DeepEqual(test.expCompactions, comps) {
   557  					return fmt.Errorf("expected %+v; got %+v", test.expCompactions, comps)
   558  				}
   559  				if a, e := compactor.Metrics.BytesCompacted.Count(), test.expBytesCompacted; a != e {
   560  					return fmt.Errorf("expected bytes compacted %d; got %d", e, a)
   561  				}
   562  				if a, e := compactor.Metrics.CompactionSuccesses.Count(), int64(len(test.expCompactions)); a != e {
   563  					return fmt.Errorf("expected compactions %d; got %d", e, a)
   564  				}
   565  				if a, e := atomic.LoadInt32(compactionCount), int32(len(test.expCompactions)); a != e {
   566  					return fmt.Errorf("expected compactions %d; got %d", e, a)
   567  				}
   568  				if len(test.expCompactions) == 0 {
   569  					if cn := compactor.Metrics.CompactingNanos.Count(); cn > 0 {
   570  						return fmt.Errorf("expected compaction time to be 0; got %d", cn)
   571  					}
   572  				} else {
   573  					expNanos := int64(len(test.expCompactions)) * int64(testCompactionLatency)
   574  					if a, e := compactor.Metrics.CompactingNanos.Count(), expNanos; a < e {
   575  						return fmt.Errorf("expected compacting nanos > %d; got %d", e, a)
   576  					}
   577  				}
   578  				// Read the remaining suggestions in the queue; verify compacted
   579  				// spans have been cleared and uncompacted spans remain.
   580  				var idx int
   581  				return we.Iterate(
   582  					keys.LocalStoreSuggestedCompactionsMin,
   583  					keys.LocalStoreSuggestedCompactionsMax,
   584  					func(kv storage.MVCCKeyValue) (bool, error) {
   585  						start, end, err := keys.DecodeStoreSuggestedCompactionKey(kv.Key.Key)
   586  						if err != nil {
   587  							t.Fatalf("failed to decode suggested compaction key: %+v", err)
   588  						}
   589  						if idx >= len(test.expUncompacted) {
   590  							return true, fmt.Errorf("found unexpected uncompacted span %s-%s", start, end)
   591  						}
   592  						if !start.Equal(test.expUncompacted[idx].Key) || !end.Equal(test.expUncompacted[idx].EndKey) {
   593  							return true, fmt.Errorf("found unexpected uncompacted span %s-%s; expected %s-%s",
   594  								start, end, test.expUncompacted[idx].Key, test.expUncompacted[idx].EndKey)
   595  						}
   596  						idx++
   597  						return false, nil // continue iteration
   598  					},
   599  				)
   600  			})
   601  		})
   602  	}
   603  }
   604  
   605  // TestCompactorDeadlockOnStart prevents regression of an issue that
   606  // could cause nodes to lock up during the boot sequence. The
   607  // compactor may receive suggestions before starting the goroutine,
   608  // yet starting the goroutine could block on the suggestions channel,
   609  // deadlocking the call to (Compactor).Start and thus the main node
   610  // boot goroutine. This was observed in practice.
   611  func TestCompactorDeadlockOnStart(t *testing.T) {
   612  	stopper := stop.NewStopper()
   613  	defer stopper.Stop(context.Background())
   614  
   615  	eng := newWrappedEngine()
   616  	stopper.AddCloser(eng)
   617  	capFn := func() (roachpb.StoreCapacity, error) {
   618  		return roachpb.StoreCapacity{}, errors.New("never called")
   619  	}
   620  	doneFn := func(_ context.Context) {}
   621  	st := cluster.MakeTestingClusterSettings()
   622  	compactor := NewCompactor(st, eng, capFn, doneFn)
   623  
   624  	compactor.ch <- struct{}{}
   625  
   626  	compactor.Start(context.Background(), stopper)
   627  }
   628  
   629  // TestCompactorProcessingInitialization verifies that a compactor gets
   630  // started with processing if the queue is non-empty.
   631  func TestCompactorProcessingInitialization(t *testing.T) {
   632  	defer leaktest.AfterTest(t)()
   633  
   634  	capacityFn := func() (roachpb.StoreCapacity, error) {
   635  		return roachpb.StoreCapacity{LogicalBytes: 100 * thresholdBytes.Default()}, nil
   636  	}
   637  	compactor, we, compactionCount, cleanup := testSetup(capacityFn)
   638  	defer cleanup()
   639  
   640  	// Add a suggested compaction -- this won't get processed by this
   641  	// compactor for an hour.
   642  	minInterval.Override(&compactor.st.SV, time.Hour)
   643  	compactor.Suggest(context.Background(), kvserverpb.SuggestedCompaction{
   644  		StartKey: key("a"), EndKey: key("b"),
   645  		Compaction: kvserverpb.Compaction{
   646  			Bytes:            thresholdBytes.Default(),
   647  			SuggestedAtNanos: timeutil.Now().UnixNano(),
   648  		},
   649  	})
   650  
   651  	// Create a new fast compactor with a short wait time for processing,
   652  	// using the same engine so that it sees a non-empty queue.
   653  	stopper := stop.NewStopper()
   654  	doneFn := func(_ context.Context) { atomic.AddInt32(compactionCount, 1) }
   655  	st := cluster.MakeTestingClusterSettings()
   656  	fastCompactor := NewCompactor(st, we, capacityFn, doneFn)
   657  	minInterval.Override(&fastCompactor.st.SV, time.Millisecond)
   658  	fastCompactor.Start(context.Background(), stopper)
   659  	defer stopper.Stop(context.Background())
   660  
   661  	testutils.SucceedsSoon(t, func() error {
   662  		comps := we.GetCompactions()
   663  		expComps := []roachpb.Span{{Key: key("a"), EndKey: key("b")}}
   664  		if !reflect.DeepEqual(expComps, comps) {
   665  			return fmt.Errorf("expected %+v; got %+v", expComps, comps)
   666  		}
   667  		if a, e := atomic.LoadInt32(compactionCount), int32(1); a != e {
   668  			return fmt.Errorf("expected %d; got %d", e, a)
   669  		}
   670  		return nil
   671  	})
   672  }
   673  
   674  // TestCompactorCleansUpOldRecords verifies that records which exceed
   675  // the maximum age are deleted if they cannot be compacted.
   676  func TestCompactorCleansUpOldRecords(t *testing.T) {
   677  	defer leaktest.AfterTest(t)()
   678  
   679  	capacityFn := func() (roachpb.StoreCapacity, error) {
   680  		return roachpb.StoreCapacity{
   681  			LogicalBytes: 100 * thresholdBytes.Default(),
   682  			Available:    100 * thresholdBytes.Default(),
   683  		}, nil
   684  	}
   685  	compactor, we, compactionCount, cleanup := testSetup(capacityFn)
   686  	minInterval.Override(&compactor.st.SV, time.Millisecond)
   687  	// NB: The compactor had a bug where it would never revisit skipped compactions
   688  	// alone when there wasn't also a new suggestion. Making the max age larger
   689  	// than the min interval exercises that code path (flakily).
   690  	maxSuggestedCompactionRecordAge.Override(&compactor.st.SV, 5*time.Millisecond)
   691  	defer cleanup()
   692  
   693  	// Add a suggested compaction that won't get processed because it's
   694  	// not over any of the thresholds.
   695  	compactor.Suggest(context.Background(), kvserverpb.SuggestedCompaction{
   696  		StartKey: key("a"), EndKey: key("b"),
   697  		Compaction: kvserverpb.Compaction{
   698  			Bytes:            thresholdBytes.Default() - 1,
   699  			SuggestedAtNanos: timeutil.Now().UnixNano(),
   700  		},
   701  	})
   702  
   703  	// Verify that the record is deleted without a compaction and that the
   704  	// bytes are recorded as having been skipped.
   705  	testutils.SucceedsSoon(t, func() error {
   706  		comps := we.GetCompactions()
   707  		if !reflect.DeepEqual([]roachpb.Span(nil), comps) {
   708  			return fmt.Errorf("expected nil compactions; got %+v", comps)
   709  		}
   710  		if a, e := compactor.Metrics.BytesSkipped.Count(), thresholdBytes.Get(&compactor.st.SV)-1; a != e {
   711  			return fmt.Errorf("expected skipped bytes %d; got %d", e, a)
   712  		}
   713  		if a, e := atomic.LoadInt32(compactionCount), int32(0); a != e {
   714  			return fmt.Errorf("expected compactions processed %d; got %d", e, a)
   715  		}
   716  		// Verify compaction queue is empty.
   717  		if bytesQueued, err := compactor.examineQueue(context.Background()); err != nil || bytesQueued > 0 {
   718  			return fmt.Errorf("compaction queue not empty (%d bytes) or err %v", bytesQueued, err)
   719  		}
   720  		return nil
   721  	})
   722  }
   723  
   724  // TestCompactorDisabled that a disabled compactor throws away past and future
   725  // suggestions.
   726  func TestCompactorDisabled(t *testing.T) {
   727  	defer leaktest.AfterTest(t)()
   728  
   729  	threshold := int64(10)
   730  
   731  	capacityFn := func() (roachpb.StoreCapacity, error) {
   732  		return roachpb.StoreCapacity{
   733  			LogicalBytes: 100 * threshold,
   734  			Available:    100 * threshold,
   735  		}, nil
   736  	}
   737  	compactor, we, compactionCount, cleanup := testSetup(capacityFn)
   738  	minInterval.Override(&compactor.st.SV, time.Millisecond)
   739  	maxSuggestedCompactionRecordAge.Override(&compactor.st.SV, 24*time.Hour) // large
   740  	thresholdBytesAvailableFraction.Override(&compactor.st.SV, 0.0)          // disable
   741  	thresholdBytesUsedFraction.Override(&compactor.st.SV, 0.0)               // disable
   742  	defer cleanup()
   743  
   744  	compactor.Suggest(context.Background(), kvserverpb.SuggestedCompaction{
   745  		StartKey: key("a"), EndKey: key("b"),
   746  		Compaction: kvserverpb.Compaction{
   747  			// Suggest so little that this suggestion plus the one below stays below
   748  			// the threshold. Otherwise this test gets racy and difficult to fix
   749  			// without remodeling the compactor.
   750  			Bytes:            threshold / 3,
   751  			SuggestedAtNanos: timeutil.Now().UnixNano(),
   752  		},
   753  	})
   754  
   755  	enabled.Override(&compactor.st.SV, false)
   756  
   757  	compactor.Suggest(context.Background(), kvserverpb.SuggestedCompaction{
   758  		// Note that we don't reuse the same interval above or we hit another race,
   759  		// in which the compactor discards the first suggestion and wipes out the
   760  		// second one with it, without incrementing the discarded metric.
   761  		StartKey: key("b"), EndKey: key("c"),
   762  		Compaction: kvserverpb.Compaction{
   763  			Bytes:            threshold / 3,
   764  			SuggestedAtNanos: timeutil.Now().UnixNano(),
   765  		},
   766  	})
   767  
   768  	// Verify that the record is deleted without a compaction and that the
   769  	// bytes are recorded as having been skipped.
   770  	testutils.SucceedsSoon(t, func() error {
   771  		if a, e := atomic.LoadInt32(compactionCount), int32(0); a != e {
   772  			t.Fatalf("expected compactions processed %d; got %d", e, a)
   773  		}
   774  
   775  		comps := we.GetCompactions()
   776  		if !reflect.DeepEqual([]roachpb.Span(nil), comps) {
   777  			return fmt.Errorf("expected nil compactions; got %+v", comps)
   778  		}
   779  		if a, e := compactor.Metrics.BytesSkipped.Count(), 2*(threshold/3); a != e {
   780  			return fmt.Errorf("expected skipped bytes %d; got %d", e, a)
   781  		}
   782  
   783  		// Verify compaction queue is empty.
   784  		if bytesQueued, err := compactor.examineQueue(context.Background()); err != nil || bytesQueued > 0 {
   785  			return fmt.Errorf("compaction queue not empty (%d bytes) or err %v", bytesQueued, err)
   786  		}
   787  		return nil
   788  	})
   789  }