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 }