influxdb/tests/server_delete_test.go

680 lines
19 KiB
Go

package tests
import (
"encoding/json"
"fmt"
"math/rand"
"net/url"
"os"
"reflect"
"sort"
"strings"
"sync"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/influxdata/influxdb/models"
)
var db = "db0"
var rp = "rp0"
// Makes it easy to debug times by emitting rfc formatted strings instead
// of numbers.
var tme = func(t int64) string {
// return time.Unix(0, t).UTC().String() + fmt.Sprintf("(%d)", t)
return fmt.Sprint(t) // Just emit the number
}
type Command func() (string, error)
func setupCommands(s *LocalServer, tracker *SeriesTracker) []Command {
var measurementN = 10
var seriesN = 100
var commands []Command
r := rand.New(rand.NewSource(seed))
// Command for inserting some series data.
commands = append(commands, func() (string, error) {
name := fmt.Sprintf("m%d", r.Intn(measurementN))
tags := models.NewTags(map[string]string{fmt.Sprintf("s%d", r.Intn(seriesN)): "a"})
tracker.Lock()
pt := tracker.AddSeries(name, tags)
_, err := s.Write(db, rp, pt, nil)
if err != nil {
return "", err
}
defer tracker.Unlock()
return fmt.Sprintf("INSERT %s", pt), tracker.Verify()
})
// Command for dropping an entire measurement.
commands = append(commands, func() (string, error) {
name := fmt.Sprintf("m%d", r.Intn(measurementN))
tracker.Lock()
tracker.DeleteMeasurement(name)
query := fmt.Sprintf("DROP MEASUREMENT %s", name)
_, err := s.QueryWithParams(query, url.Values{"db": []string{"db0"}})
if err != nil {
return "", err
}
defer tracker.Unlock()
return query, tracker.Verify()
})
// Command for dropping a single series.
commands = append(commands, func() (string, error) {
name := fmt.Sprintf("m%d", r.Intn(measurementN))
tagKey := fmt.Sprintf("s%d", r.Intn(seriesN))
tags := models.NewTags(map[string]string{tagKey: "a"})
tracker.Lock()
tracker.DropSeries(name, tags)
query := fmt.Sprintf("DROP SERIES FROM %q WHERE %q = 'a'", name, tagKey)
_, err := s.QueryWithParams(query, url.Values{"db": []string{"db0"}})
if err != nil {
return "", err
}
defer tracker.Unlock()
return query, tracker.Verify()
})
// Command for dropping a single series.
commands = append(commands, func() (string, error) {
name := fmt.Sprintf("m%d", r.Intn(measurementN))
tracker.Lock()
min, max := tracker.DeleteRandomRange(name)
query := fmt.Sprintf("DELETE FROM %q WHERE time >= %d AND time <= %d ", name, min, max)
_, err := s.QueryWithParams(query, url.Values{"db": []string{"db0"}})
if err != nil {
return "", err
}
defer tracker.Unlock()
query = fmt.Sprintf("DELETE FROM %q WHERE time >= %s AND time <= %s ", name, tme(min), tme(max))
return query, tracker.Verify()
})
return commands
}
// TestServer_Delete_Series sets up a concurrent collection of clients that continuously
// write data and delete series, measurements and shards.
//
// The purpose of this test is to provide a randomised, highly concurrent test
// to shake out bugs involving the interactions between shards, their indexes,
// and a database's series file.
func TestServer_DELETE_DROP_SERIES_DROP_MEASUREMENT(t *testing.T) {
t.Parallel()
if testing.Short() || os.Getenv("GORACE") != "" {
t.Skip("Skipping test in short or race mode.")
}
N := 5000
shardN := 10
r := rand.New(rand.NewSource(seed))
t.Logf("***** Seed set to %d *****\n", seed)
if testing.Short() {
t.Skip("Skipping in short mode")
}
s := OpenDefaultServer(NewConfig())
defer s.Close()
localServer := s.(*LocalServer)
// First initialize some writes such that we end up with 10 shards.
// The first point refers to 1970-01-01 00:00:00.01 +0000 UTC and each subsequent
// point is 7 days into the future.
queries := make([]string, 0, N)
data := make([]string, 0, shardN)
for i := int64(0); i < int64(cap(data)); i++ {
query := fmt.Sprintf(`a val=1 %d`, 10000000+(int64(time.Hour)*24*7*i))
queries = append(queries, fmt.Sprintf("INSERT %s", query))
data = append(data, query)
}
if _, err := s.Write(db, rp, strings.Join(data, "\n"), nil); err != nil {
t.Fatal(err)
}
tracker := NewSeriesTracker(r, localServer, db, rp)
commands := setupCommands(localServer, tracker)
for i := 0; i < N; i++ {
query, err := commands[r.Intn(len(commands))]()
queries = append(queries, query)
if err != nil {
emit := queries
if len(queries) > 1000 {
emit = queries[len(queries)-1000:]
}
t.Logf("Emitting last 1000 queries of %d total:\n%s\n", len(queries), strings.Join(emit, "\n"))
t.Logf("Current points in Series Tracker index:\n%s\n", tracker.DumpPoints())
t.Fatal(err)
}
}
}
// **** The following tests are specific examples discovered using ****
// TestServer_DELETE_DROP_SERIES_DROP_MEASUREMENT. They're added to prevent
// regressions.
// This test never explicitly failed, but it's a special case of
// TestServer_Insert_Delete_1515688266259660938.
func TestServer_Insert_Delete_1515688266259660938_same_shard(t *testing.T) {
// Original seed was 1515688266259660938.
t.Parallel()
if testing.Short() || os.Getenv("GORACE") != "" {
t.Skip("Skipping test in short or race mode.")
}
s := OpenDefaultServer(NewConfig())
defer s.Close()
for i := 0; i < 100; i++ {
mustWrite(s, "m4,s67=a v=1 4",
"m4,s67=a v=1 12",
"m4,s1=a v=1 15")
mustDelete(s, "m4", 1, 10)
// Compare series left in index.
gotSeries := mustGetSeries(s)
expectedSeries := []string{"m4,s1=a", "m4,s67=a"}
if !reflect.DeepEqual(gotSeries, expectedSeries) {
t.Fatalf("got series %v, expected %v", gotSeries, expectedSeries)
}
mustDropCreate(s)
}
}
// This test failed with seed 1515688266259660938.
func TestServer_Insert_Delete_1515688266259660938(t *testing.T) {
// Original seed was 1515688266259660938.
t.Parallel()
if testing.Short() || os.Getenv("GORACE") != "" {
t.Skip("Skipping test in short or race mode.")
}
s := OpenDefaultServer(NewConfig())
defer s.Close()
for i := 0; i < 100; i++ {
mustWrite(s, "m4,s67=a v=1 2127318532111304", // should be deleted
"m4,s67=a v=1 4840422259072956",
"m4,s1=a v=1 4777375719836601")
mustDelete(s, "m4", 1134567692141289, 2233755799041351)
// Compare series left in index.
gotSeries := mustGetSeries(s)
expectedSeries := []string{"m4,s1=a", "m4,s67=a"}
if !reflect.DeepEqual(gotSeries, expectedSeries) {
t.Fatalf("got series %v, expected %v", gotSeries, expectedSeries)
}
mustDropCreate(s)
}
}
// This test failed with seed 1515771752164780713.
func TestServer_Insert_Delete_1515771752164780713(t *testing.T) {
// Original seed was 1515771752164780713.
t.Parallel()
if testing.Short() || os.Getenv("GORACE") != "" {
t.Skip("Skipping test in short or race mode.")
}
s := OpenDefaultServer(NewConfig())
defer s.Close()
mustWrite(s, "m6,s72=a v=1 641480139110750") // series id 257 in shard 1
mustWrite(s, "m6,s32=a v=1 1320128148356150") // series id 259 in shard 2
mustDelete(s, "m6", 1316178840387070, 3095172845699329) // deletes m6,s32=a (259) - shard 2 now empty
mustWrite(s, "m6,s61=a v=1 47161015166211") // series id 261 in shard 3
mustWrite(s, "m6,s67=a v=1 4466443248294177") // series 515 in shard 4
mustDelete(s, "m6", 495574950798826, 2963503790804876) // deletes m6,s72 (257) - shard 1 now empty
// Compare series left in index.
gotSeries := mustGetSeries(s)
expectedSeries := []string{"m6,s61=a", "m6,s67=a"}
if !reflect.DeepEqual(gotSeries, expectedSeries) {
t.Fatalf("got series %v, expected %v", gotSeries, expectedSeries)
}
}
// This test failed with seed 1515777603585914810.
func TestServer_Insert_Delete_1515777603585914810(t *testing.T) {
// Original seed was 1515777603585914810.
t.Parallel()
s := OpenDefaultServer(NewConfig())
defer s.Close()
mustWrite(s, "m5,s99=a v=1 1")
mustDelete(s, "m5", 0, 1)
mustWrite(s, "m5,s99=a v=1 1")
gotSeries := mustGetSeries(s)
expectedSeries := []string{"m5,s99=a"}
if !reflect.DeepEqual(gotSeries, expectedSeries) {
t.Fatalf("got series %v, expected %v", gotSeries, expectedSeries)
}
}
// This test reproduces the issue identified in https://github.com/influxdata/influxdb/issues/10052
func TestServer_Insert_Delete_10052(t *testing.T) {
t.Parallel()
s := OpenDefaultServer(NewConfig())
defer s.Close()
mustWrite(s,
"ping,server=ping a=1,b=2,c=3,d=4,e=5 1",
"ping,server=ping a=1,b=2,c=3,d=4,e=5 2",
"ping,server=ping a=1,b=2,c=3,d=4,e=5 3",
"ping,server=ping a=1,b=2,c=3,d=4,e=5 4",
"ping,server=ping a=1,b=2,c=3,d=4,e=5 5",
"ping,server=ping a=1,b=2,c=3,d=4,e=5 6",
)
mustDropMeasurement(s, "ping")
gotSeries := mustGetSeries(s)
expectedSeries := []string(nil)
if !reflect.DeepEqual(gotSeries, expectedSeries) {
t.Fatalf("got series %v, expected %v", gotSeries, expectedSeries)
}
mustWrite(s, "ping v=1 1")
gotSeries = mustGetSeries(s)
expectedSeries = []string{"ping"}
if !reflect.DeepEqual(gotSeries, expectedSeries) {
t.Fatalf("got series %v, expected %v", gotSeries, expectedSeries)
}
gotSeries = mustGetFieldKeys(s)
expectedSeries = []string{"v"}
if !reflect.DeepEqual(gotSeries, expectedSeries) {
t.Fatalf("got series %v, expected %v", gotSeries, expectedSeries)
}
}
func mustGetSeries(s Server) []string {
// Compare series left in index.
result, err := s.QueryWithParams("SHOW SERIES", url.Values{"db": []string{"db0"}})
if err != nil {
panic(err)
}
gotSeries, err := valuesFromShowQuery(result)
if err != nil {
panic(err)
}
return gotSeries
}
func mustGetFieldKeys(s Server) []string {
// Compare series left in index.
result, err := s.QueryWithParams("SHOW FIELD KEYS", url.Values{"db": []string{"db0"}})
if err != nil {
panic(err)
}
gotSeries, err := valuesFromShowQuery(result)
if err != nil {
panic(err)
}
return gotSeries
}
func mustDropCreate(s Server) {
if err := s.DropDatabase(db); err != nil {
panic(err)
}
if err := s.CreateDatabaseAndRetentionPolicy(db, NewRetentionPolicySpec(rp, 1, 0), true); err != nil {
panic(err)
}
}
func mustWrite(s Server, points ...string) {
if _, err := s.Write(db, rp, strings.Join(points, "\n"), nil); err != nil {
panic(err)
}
}
func mustDelete(s Server, name string, min, max int64) {
query := fmt.Sprintf("DELETE FROM %q WHERE time >= %d AND time <= %d ", name, min, max)
if _, err := s.QueryWithParams(query, url.Values{"db": []string{db}}); err != nil {
panic(err)
}
}
func mustDropMeasurement(s Server, name string) {
query := fmt.Sprintf("DROP MEASUREMENT %q", name)
if _, err := s.QueryWithParams(query, url.Values{"db": []string{db}}); err != nil {
panic(err)
}
}
// SeriesTracker is a lockable tracker of which shards should own which series.
type SeriesTracker struct {
sync.RWMutex
r *rand.Rand
// The testing server
server *LocalServer
// series maps a series key to a value that determines which shards own the
// series.
series map[string]uint64
// seriesPoints maps a series key to all the times that the series is written.
seriesPoints map[string][]int64
// measurements maps a measurement name to a value that determines which
// shards own the measurement.
measurements map[string]uint64
// measurementsSeries maps which series keys belong to which measurement.
measurementsSeries map[string]map[string]struct{}
// shardTimeRanges maintains the time ranges that a shard spans
shardTimeRanges map[uint64][2]int64
shardIDs []uint64
}
func NewSeriesTracker(r *rand.Rand, server *LocalServer, db, rp string) *SeriesTracker {
tracker := &SeriesTracker{
r: r,
series: make(map[string]uint64),
seriesPoints: make(map[string][]int64),
measurements: make(map[string]uint64),
measurementsSeries: make(map[string]map[string]struct{}),
shardTimeRanges: make(map[uint64][2]int64),
server: server,
}
data := server.MetaClient.Data()
sgs, err := data.ShardGroups(db, rp)
if err != nil {
panic(err)
}
for _, sg := range sgs {
tracker.shardTimeRanges[sg.ID] = [2]int64{sg.StartTime.UnixNano(), sg.EndTime.UnixNano()}
tracker.shardIDs = append(tracker.shardIDs, sg.ID)
}
// Add initial series
for i, sid := range tracker.shardIDs {
// Map the shard to the series.
tracker.series["a"] = tracker.series["a"] | (1 << sid)
// Map the shard to the measurement.
tracker.measurements["a"] = tracker.measurements["a"] | (1 << sid)
// Map the timstamp of the point in this shard to the series.
tracker.seriesPoints["a"] = append(tracker.seriesPoints["a"], 10000000+(int64(time.Hour)*24*7*int64(i)))
}
// Map initial series to measurement.
tracker.measurementsSeries["a"] = map[string]struct{}{"a": struct{}{}}
return tracker
}
// AddSeries writes a point for the provided series to the provided shard. The
// exact time of the point is randomised within all the shards' boundaries.
// The point string is returned, to be inserted into the server.
func (s *SeriesTracker) AddSeries(name string, tags models.Tags) string {
// generate a random shard and time within it.
time, shard := s.randomTime()
pt, err := models.NewPoint(name, tags, models.Fields{"v": 1.0}, time)
if err != nil {
panic(err)
}
key := string(pt.Key())
s.series[key] = s.series[key] | (1 << shard)
s.seriesPoints[key] = append(s.seriesPoints[key], time.UnixNano())
// Update measurement map
s.measurements[name] = s.measurements[name] | (1 << shard)
// Update measurement -> series mapping
if _, ok := s.measurementsSeries[name]; !ok {
s.measurementsSeries[name] = map[string]struct{}{string(key): struct{}{}}
} else {
s.measurementsSeries[name][string(key)] = struct{}{}
}
return pt.String()
}
// DeleteMeasurement deletes all series associated with the provided measurement
// from the tracker.
func (s *SeriesTracker) DeleteMeasurement(name string) {
// Delete from shard -> measurement mapping
delete(s.measurements, name)
// Get any series associated with measurement, and delete them.
series := s.measurementsSeries[name]
// Remove all series associated with this measurement.
for key := range series {
delete(s.series, key)
delete(s.seriesPoints, key)
}
delete(s.measurementsSeries, name)
}
// DropSeries deletes a specific series, removing any measurements if no series
// are owned by them any longer.
func (s *SeriesTracker) DropSeries(name string, tags models.Tags) {
pt, err := models.NewPoint(name, tags, models.Fields{"v": 1.0}, time.Now())
if err != nil {
panic(err)
}
key := string(pt.Key())
_, ok := s.series[key]
if ok {
s.cleanupSeries(name, key) // Remove all series data.
}
}
// cleanupSeries removes any traces of series that no longer have any point
// data.
func (s *SeriesTracker) cleanupSeries(name, key string) {
// Remove series references
delete(s.series, key)
delete(s.measurementsSeries[name], key)
delete(s.seriesPoints, key)
// Check if that was the last series for a measurement
if len(s.measurementsSeries[name]) == 0 {
delete(s.measurementsSeries, name) // Remove the measurement
delete(s.measurements, name)
}
}
// DeleteRandomRange deletes all series data within a random time range for the
// provided measurement.
func (s *SeriesTracker) DeleteRandomRange(name string) (int64, int64) {
t1, _ := s.randomTime()
t2, _ := s.randomTime()
min, max := t1.UnixNano(), t2.UnixNano()
if t2.Before(t1) {
min, max = t2.UnixNano(), t1.UnixNano()
}
if min > max {
panic(fmt.Sprintf("min time %d > max %d", min, max))
}
// Get all the series associated with this measurement.
series := s.measurementsSeries[name]
if len(series) == 0 {
// Nothing to do
return min, max
}
// For each series, check for, and remove, any points that fall within the
// time range.
var seriesToDelete []string
for serie := range series {
points := s.seriesPoints[serie]
sort.Sort(sortedInt64(points))
// Find min and max index that fall in range.
var minIdx, maxIdx = -1, -1
for i, p := range points {
if minIdx == -1 && p >= min {
minIdx = i
}
if p <= max {
maxIdx = i
}
}
// If either the minIdx or maxIdx are not set, then none of the points
// fall in that boundary of the domain.
if minIdx == -1 || maxIdx == -1 {
continue
}
// Cut those points from the series points slice.
s.seriesPoints[serie] = append(points[:minIdx], points[maxIdx+1:]...)
// Was that the last point for the series?
if len(s.seriesPoints[serie]) == 0 {
seriesToDelete = append(seriesToDelete, serie)
}
}
// Cleanup any removed series/measurements.
for _, key := range seriesToDelete {
s.cleanupSeries(name, key)
}
return min, max
}
// randomTime generates a random time to insert a point, such that it will fall
// within one of the shards' time boundaries.
func (s *SeriesTracker) randomTime() (time.Time, uint64) {
// Pick a random shard
id := s.shardIDs[s.r.Intn(len(s.shardIDs))]
// Get min and max time range of the shard.
min, max := s.shardTimeRanges[id][0], s.shardTimeRanges[id][1]
if min >= max {
panic(fmt.Sprintf("min %d >= max %d", min, max))
}
tme := s.r.Int63n(max-min) + min // Will result in a range [min, max)
if tme < min || tme >= max {
panic(fmt.Sprintf("generated time %d is out of bounds [%d, %d)", tme, min, max))
}
return time.Unix(0, tme), id
}
// Verify verifies that the server's view of the index/series file matches the
// series tracker's.
func (s *SeriesTracker) Verify() error {
res, err := s.server.QueryWithParams("SHOW SERIES", url.Values{"db": []string{"db0"}})
if err != nil {
return err
}
// Get all series...
gotSeries, err := valuesFromShowQuery(res)
if err != nil {
return err
}
expectedSeries := make([]string, 0, len(s.series))
for series := range s.series {
expectedSeries = append(expectedSeries, series)
}
sort.Strings(expectedSeries)
if !reflect.DeepEqual(gotSeries, expectedSeries) {
return fmt.Errorf("verification failed:\ngot series: %v\nexpected series: %v\ndifference: %s", gotSeries, expectedSeries, cmp.Diff(gotSeries, expectedSeries))
}
return nil
}
// valuesFromShowQuery extracts a lexicographically sorted set of series keys
// from a SHOW SERIES query.
func valuesFromShowQuery(result string) ([]string, error) {
// Get all series...
var results struct {
Results []struct {
Series []struct {
Values [][]string `json:"values"`
} `json:"series"`
} `json:"results"`
}
if err := json.Unmarshal([]byte(result), &results); err != nil {
return nil, err
}
var gotSeries []string
for _, ser := range results.Results {
for _, values := range ser.Series {
for _, v := range values.Values {
gotSeries = append(gotSeries, v[0])
}
}
}
// series are returned sorted by name then tag key then tag value, which is
// not the same as lexicographic order.
sort.Strings(gotSeries)
return gotSeries, nil
}
// DumpPoints returns all the series points.
func (s *SeriesTracker) DumpPoints() string {
keys := make([]string, 0, len(s.seriesPoints))
for key := range s.seriesPoints {
keys = append(keys, key)
}
sort.Strings(keys)
for i, key := range keys {
// We skip key "a" as it's just there to initialise the shards.
if key == "a" {
continue
}
points := s.seriesPoints[key]
sort.Sort(sortedInt64(points))
pointStr := make([]string, 0, len(points))
for _, p := range points {
pointStr = append(pointStr, tme(p))
}
keys[i] = fmt.Sprintf("%s: %v", key, pointStr)
}
return strings.Join(keys, "\n")
}
type sortedInt64 []int64
func (a sortedInt64) Len() int { return len(a) }
func (a sortedInt64) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a sortedInt64) Less(i, j int) bool { return a[i] < a[j] }