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

     1  // Copyright 2016 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 kvserver_test
    12  
    13  import (
    14  	"context"
    15  	"fmt"
    16  	"math"
    17  	"reflect"
    18  	"sort"
    19  	"testing"
    20  	"time"
    21  
    22  	"github.com/cockroachdb/cockroach/pkg/base"
    23  	"github.com/cockroachdb/cockroach/pkg/kv"
    24  	"github.com/cockroachdb/cockroach/pkg/kv/kvserver"
    25  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    26  	"github.com/cockroachdb/cockroach/pkg/server"
    27  	"github.com/cockroachdb/cockroach/pkg/settings/cluster"
    28  	"github.com/cockroachdb/cockroach/pkg/storage"
    29  	"github.com/cockroachdb/cockroach/pkg/testutils"
    30  	"github.com/cockroachdb/cockroach/pkg/testutils/serverutils"
    31  	"github.com/cockroachdb/cockroach/pkg/ts"
    32  	"github.com/cockroachdb/cockroach/pkg/ts/tspb"
    33  	"github.com/cockroachdb/cockroach/pkg/util/hlc"
    34  	"github.com/cockroachdb/cockroach/pkg/util/leaktest"
    35  	"github.com/cockroachdb/cockroach/pkg/util/mon"
    36  	"github.com/cockroachdb/cockroach/pkg/util/stop"
    37  	"github.com/cockroachdb/cockroach/pkg/util/syncutil"
    38  	"github.com/cockroachdb/errors"
    39  	"github.com/kr/pretty"
    40  )
    41  
    42  type modelTimeSeriesDataStore struct {
    43  	syncutil.Mutex
    44  	t                  testing.TB
    45  	containsCalled     int
    46  	pruneCalled        int
    47  	pruneSeenStartKeys []roachpb.Key
    48  	pruneSeenEndKeys   []roachpb.Key
    49  }
    50  
    51  func (m *modelTimeSeriesDataStore) ContainsTimeSeries(start, end roachpb.RKey) bool {
    52  	if !start.Less(end) {
    53  		m.t.Fatalf("ContainsTimeSeries passed start key %v which is not less than end key %v", start, end)
    54  	}
    55  	m.Lock()
    56  	defer m.Unlock()
    57  	m.containsCalled++
    58  
    59  	// We're going to consider some user-space ranges as containing timeseries.
    60  	return roachpb.Key("a").Compare(start.AsRawKey()) <= 0 &&
    61  		roachpb.Key("z").Compare(end.AsRawKey()) > 0
    62  }
    63  
    64  func (m *modelTimeSeriesDataStore) MaintainTimeSeries(
    65  	ctx context.Context,
    66  	snapshot storage.Reader,
    67  	start, end roachpb.RKey,
    68  	db *kv.DB,
    69  	_ *mon.BytesMonitor,
    70  	_ int64,
    71  	now hlc.Timestamp,
    72  ) error {
    73  	if snapshot == nil {
    74  		m.t.Fatal("MaintainTimeSeries was passed a nil snapshot")
    75  	}
    76  	if db == nil {
    77  		m.t.Fatal("MaintainTimeSeries was passed a nil client.DB")
    78  	}
    79  	if !start.Less(end) {
    80  		m.t.Fatalf("MaintainTimeSeries passed start key %v which is not less than end key %v", start, end)
    81  	}
    82  
    83  	m.Lock()
    84  	defer m.Unlock()
    85  	m.pruneCalled++
    86  	m.pruneSeenStartKeys = append(m.pruneSeenStartKeys, start.AsRawKey())
    87  	sort.Slice(m.pruneSeenStartKeys, func(i, j int) bool {
    88  		return m.pruneSeenStartKeys[i].Compare(m.pruneSeenStartKeys[j]) < 0
    89  	})
    90  	m.pruneSeenEndKeys = append(m.pruneSeenEndKeys, end.AsRawKey())
    91  	sort.Slice(m.pruneSeenEndKeys, func(i, j int) bool {
    92  		return m.pruneSeenEndKeys[i].Compare(m.pruneSeenEndKeys[j]) < 0
    93  	})
    94  	return nil
    95  }
    96  
    97  // TestTimeSeriesMaintenanceQueue verifies shouldQueue and process method
    98  // pass the correct data to the store's TimeSeriesData
    99  func TestTimeSeriesMaintenanceQueue(t *testing.T) {
   100  	defer leaktest.AfterTest(t)()
   101  
   102  	ctx := context.Background()
   103  	model := &modelTimeSeriesDataStore{t: t}
   104  
   105  	manual := hlc.NewManualClock(1)
   106  	cfg := kvserver.TestStoreConfig(hlc.NewClock(manual.UnixNano, time.Nanosecond))
   107  	cfg.TimeSeriesDataStore = model
   108  	cfg.TestingKnobs.DisableScanner = true
   109  	cfg.TestingKnobs.DisableSplitQueue = true
   110  	cfg.TestingKnobs.DisableMergeQueue = true
   111  
   112  	stopper := stop.NewStopper()
   113  	defer stopper.Stop(ctx)
   114  	store := createTestStoreWithConfig(t, stopper, cfg)
   115  
   116  	// Generate several splits. The "c"-"zz" range is not going to be considered
   117  	// as containing timeseries.
   118  	splitKeys := []roachpb.Key{roachpb.Key("zz"), roachpb.Key("c"), roachpb.Key("b"), roachpb.Key("a")}
   119  	for _, k := range splitKeys {
   120  		repl := store.LookupReplica(roachpb.RKey(k))
   121  		args := adminSplitArgs(k)
   122  		if _, pErr := kv.SendWrappedWith(ctx, store, roachpb.Header{
   123  			RangeID: repl.RangeID,
   124  		}, args); pErr != nil {
   125  			t.Fatal(pErr)
   126  		}
   127  	}
   128  
   129  	// Generate a list of start/end keys the model should have been passed by
   130  	// the queue. This consists of all split keys, with KeyMin as an additional
   131  	// start and KeyMax as an additional end.
   132  	expectedStartKeys := []roachpb.Key{roachpb.Key("a"), roachpb.Key("b")}
   133  
   134  	expectedEndKeys := []roachpb.Key{roachpb.Key("b"), roachpb.Key("c")}
   135  
   136  	// Force replica scan to run, which will populate the model.
   137  	now := store.Clock().Now()
   138  	if err := store.ForceTimeSeriesMaintenanceQueueProcess(); err != nil {
   139  		t.Fatal(err)
   140  	}
   141  
   142  	// Wait for processing to complete.
   143  	testutils.SucceedsSoon(t, func() error {
   144  		model.Lock()
   145  		defer model.Unlock()
   146  		// containsCalled is dependent on the number of ranges in the cluster, which
   147  		// is larger than the ones we've created.
   148  		if a, e := model.containsCalled, len(expectedStartKeys); a < e {
   149  			return fmt.Errorf("ContainsTimeSeries called %d times; expected %d", a, e)
   150  		}
   151  		if a, e := model.pruneCalled, len(expectedStartKeys); a != e {
   152  			return fmt.Errorf("MaintainTimeSeries called %d times; expected %d", a, e)
   153  		}
   154  		return nil
   155  	})
   156  
   157  	model.Lock()
   158  	if a, e := model.pruneSeenStartKeys, expectedStartKeys; !reflect.DeepEqual(a, e) {
   159  		t.Errorf("start keys seen by MaintainTimeSeries did not match expectation: %s", pretty.Diff(a, e))
   160  	}
   161  	if a, e := model.pruneSeenEndKeys, expectedEndKeys; !reflect.DeepEqual(a, e) {
   162  		t.Errorf("end keys seen by MaintainTimeSeries did not match expectation: %s", pretty.Diff(a, e))
   163  	}
   164  	model.Unlock()
   165  
   166  	testutils.SucceedsSoon(t, func() error {
   167  		for _, key := range expectedStartKeys {
   168  			repl := store.LookupReplica(roachpb.RKey(key))
   169  			ts, err := repl.GetQueueLastProcessed(ctx, "timeSeriesMaintenance")
   170  			if err != nil {
   171  				return err
   172  			}
   173  			if ts.Less(now) {
   174  				return errors.Errorf("expected last processed (%s) %s > %s", repl, ts, now)
   175  			}
   176  		}
   177  		return nil
   178  	})
   179  
   180  	// Force replica scan to run. But because we haven't moved the
   181  	// clock forward, no pruning will take place on second invocation.
   182  	if err := store.ForceTimeSeriesMaintenanceQueueProcess(); err != nil {
   183  		t.Fatal(err)
   184  	}
   185  	model.Lock()
   186  	if a, e := model.containsCalled, len(expectedStartKeys); a < e {
   187  		t.Errorf("ContainsTimeSeries called %d times; expected %d", a, e)
   188  	}
   189  	if a, e := model.pruneCalled, len(expectedStartKeys); a != e {
   190  		t.Errorf("MaintainTimeSeries called %d times; expected %d", a, e)
   191  	}
   192  	model.Unlock()
   193  
   194  	// Move clock forward and force to scan again.
   195  	manual.Increment(kvserver.TimeSeriesMaintenanceInterval.Nanoseconds())
   196  	if err := store.ForceTimeSeriesMaintenanceQueueProcess(); err != nil {
   197  		t.Fatal(err)
   198  	}
   199  	testutils.SucceedsSoon(t, func() error {
   200  		model.Lock()
   201  		defer model.Unlock()
   202  		// containsCalled is dependent on the number of ranges in the cluster, which
   203  		// is larger than the ones we've created.
   204  		if a, e := model.containsCalled, len(expectedStartKeys)*2; a < e {
   205  			return errors.Errorf("ContainsTimeSeries called %d times; expected %d", a, e)
   206  		}
   207  		if a, e := model.pruneCalled, len(expectedStartKeys)*2; a != e {
   208  			return errors.Errorf("MaintainTimeSeries called %d times; expected %d", a, e)
   209  		}
   210  		return nil
   211  	})
   212  }
   213  
   214  // TestTimeSeriesMaintenanceQueueServer verifies that the time series
   215  // maintenance queue runs correctly on a test server.
   216  func TestTimeSeriesMaintenanceQueueServer(t *testing.T) {
   217  	defer leaktest.AfterTest(t)()
   218  
   219  	s, _, db := serverutils.StartServer(t, base.TestServerArgs{
   220  		Knobs: base.TestingKnobs{
   221  			Store: &kvserver.StoreTestingKnobs{
   222  				DisableScanner: true,
   223  			},
   224  		},
   225  	})
   226  	defer s.Stopper().Stop(context.Background())
   227  	tsrv := s.(*server.TestServer)
   228  	tsdb := tsrv.TsDB()
   229  
   230  	// Populate time series data into the server. One time series, with one
   231  	// datapoint at the current time and two datapoints older than the pruning
   232  	// threshold. Datapoint timestamps are set to the midpoint of sample duration
   233  	// periods; this simplifies verification.
   234  	seriesName := "test.metric"
   235  	sourceName := "source1"
   236  	now := tsrv.Clock().PhysicalNow()
   237  	nearPast := now - (tsdb.PruneThreshold(ts.Resolution10s) * 2)
   238  	farPast := now - (tsdb.PruneThreshold(ts.Resolution10s) * 4)
   239  	sampleDuration := ts.Resolution10s.SampleDuration()
   240  	datapoints := []tspb.TimeSeriesDatapoint{
   241  		{
   242  			TimestampNanos: farPast - farPast%sampleDuration,
   243  			Value:          100.0,
   244  		},
   245  		{
   246  			TimestampNanos: nearPast - (nearPast)%sampleDuration,
   247  			Value:          200.0,
   248  		},
   249  		{
   250  			TimestampNanos: now - now%sampleDuration,
   251  			Value:          300.0,
   252  		},
   253  	}
   254  	if err := tsdb.StoreData(context.Background(), ts.Resolution10s, []tspb.TimeSeriesData{
   255  		{
   256  			Name:       seriesName,
   257  			Source:     sourceName,
   258  			Datapoints: datapoints,
   259  		},
   260  	}); err != nil {
   261  		t.Fatal(err)
   262  	}
   263  
   264  	// Generate a split key at a timestamp halfway between near past and far past.
   265  	splitKey := ts.MakeDataKey(
   266  		seriesName, sourceName, ts.Resolution10s, farPast+(nearPast-farPast)/2,
   267  	)
   268  
   269  	// Force a range split in between near past and far past. This guarantees
   270  	// that the pruning operation will issue a DeleteRange which spans ranges.
   271  	if err := db.AdminSplit(context.Background(), splitKey, splitKey, hlc.MaxTimestamp /* expirationTime */); err != nil {
   272  		t.Fatal(err)
   273  	}
   274  
   275  	memMon := mon.MakeMonitor(
   276  		"test",
   277  		mon.MemoryResource,
   278  		nil,           /* curCount */
   279  		nil,           /* maxHist */
   280  		-1,            /* increment: use default block size */
   281  		math.MaxInt64, /* noteworthy */
   282  		cluster.MakeTestingClusterSettings(),
   283  	)
   284  	memMon.Start(context.Background(), nil /* pool */, mon.MakeStandaloneBudget(math.MaxInt64))
   285  	defer memMon.Stop(context.Background())
   286  	memContext := ts.MakeQueryMemoryContext(
   287  		&memMon,
   288  		&memMon,
   289  		ts.QueryMemoryOptions{
   290  			BudgetBytes:             math.MaxInt64 / 8,
   291  			EstimatedSources:        1,
   292  			InterpolationLimitNanos: 0,
   293  		},
   294  	)
   295  	defer memContext.Close(context.Background())
   296  
   297  	// getDatapoints queries all datapoints in the series from the beginning
   298  	// of time to a point in the near future.
   299  	getDatapoints := func() ([]tspb.TimeSeriesDatapoint, error) {
   300  		dps, _, err := tsdb.Query(
   301  			context.Background(),
   302  			tspb.Query{Name: seriesName},
   303  			ts.Resolution10s,
   304  			ts.QueryTimespan{
   305  				SampleDurationNanos: ts.Resolution10s.SampleDuration(),
   306  				StartNanos:          0,
   307  				EndNanos:            now + ts.Resolution10s.SlabDuration(),
   308  				NowNanos:            now + (10 * time.Hour).Nanoseconds(),
   309  			},
   310  			memContext,
   311  		)
   312  		return dps, err
   313  	}
   314  
   315  	// Verify the datapoints are all present.
   316  	actualDatapoints, err := getDatapoints()
   317  	if err != nil {
   318  		t.Fatal(err)
   319  	}
   320  	if a, e := actualDatapoints, datapoints; !reflect.DeepEqual(a, e) {
   321  		t.Fatalf("got datapoints %v, expected %v, diff: %s", a, e, pretty.Diff(a, e))
   322  	}
   323  
   324  	// Force pruning.
   325  	storeID := roachpb.StoreID(1)
   326  	store, err := tsrv.Stores().GetStore(roachpb.StoreID(1))
   327  	if err != nil {
   328  		t.Fatalf("error retrieving store %d: %+v", storeID, err)
   329  	}
   330  	if err := store.ForceTimeSeriesMaintenanceQueueProcess(); err != nil {
   331  		t.Fatal(err)
   332  	}
   333  
   334  	// Verify the older datapoint has been pruned.
   335  	testutils.SucceedsSoon(t, func() error {
   336  		actualDatapoints, err = getDatapoints()
   337  		if err != nil {
   338  			return err
   339  		}
   340  		if a, e := actualDatapoints, datapoints[2:]; !reflect.DeepEqual(a, e) {
   341  			return fmt.Errorf("got datapoints %v, expected %v, diff: %s", a, e, pretty.Diff(a, e))
   342  		}
   343  		return nil
   344  	})
   345  }