github.com/thanos-io/thanos@v0.32.5/pkg/receive/writer_test.go (about)

     1  // Copyright (c) The Thanos Authors.
     2  // Licensed under the Apache License 2.0.
     3  
     4  package receive
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"strings"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/efficientgo/core/testutil"
    14  	"github.com/go-kit/log"
    15  	"github.com/pkg/errors"
    16  	"github.com/prometheus/client_golang/prometheus"
    17  	"github.com/prometheus/common/model"
    18  	"github.com/prometheus/prometheus/model/exemplar"
    19  	"github.com/prometheus/prometheus/model/labels"
    20  	"github.com/prometheus/prometheus/storage"
    21  	"github.com/prometheus/prometheus/tsdb"
    22  	"github.com/prometheus/prometheus/tsdb/tsdbutil"
    23  
    24  	"github.com/thanos-io/thanos/pkg/block/metadata"
    25  	"github.com/thanos-io/thanos/pkg/runutil"
    26  	"github.com/thanos-io/thanos/pkg/store/labelpb"
    27  	"github.com/thanos-io/thanos/pkg/store/storepb/prompb"
    28  	"github.com/thanos-io/thanos/pkg/tenancy"
    29  )
    30  
    31  func TestWriter(t *testing.T) {
    32  	now := model.Now()
    33  	lbls := []labelpb.ZLabel{{Name: "__name__", Value: "test"}}
    34  	tests := map[string]struct {
    35  		reqs             []*prompb.WriteRequest
    36  		expectedErr      error
    37  		expectedIngested []prompb.TimeSeries
    38  		maxExemplars     int64
    39  		opts             *WriterOptions
    40  	}{
    41  		"should error out on series with no labels": {
    42  			reqs: []*prompb.WriteRequest{
    43  				{
    44  					Timeseries: []prompb.TimeSeries{
    45  						{
    46  							Samples: []prompb.Sample{{Value: 1, Timestamp: 10}},
    47  						},
    48  						{
    49  							Labels:  []labelpb.ZLabel{{Name: "__name__", Value: ""}},
    50  							Samples: []prompb.Sample{{Value: 1, Timestamp: 10}},
    51  						},
    52  					},
    53  				},
    54  			},
    55  			expectedErr: errors.Wrapf(labelpb.ErrEmptyLabels, "add 2 series"),
    56  		},
    57  		"should succeed on series with valid labels": {
    58  			reqs: []*prompb.WriteRequest{
    59  				{
    60  					Timeseries: []prompb.TimeSeries{
    61  						{
    62  							Labels:  append(lbls, labelpb.ZLabel{Name: "a", Value: "1"}, labelpb.ZLabel{Name: "b", Value: "2"}),
    63  							Samples: []prompb.Sample{{Value: 1, Timestamp: 10}},
    64  						},
    65  					},
    66  				},
    67  			},
    68  			expectedErr: nil,
    69  			expectedIngested: []prompb.TimeSeries{
    70  				{
    71  					Labels:  append(lbls, labelpb.ZLabel{Name: "a", Value: "1"}, labelpb.ZLabel{Name: "b", Value: "2"}),
    72  					Samples: []prompb.Sample{{Value: 1, Timestamp: 10}},
    73  				},
    74  			},
    75  		},
    76  		"should error out and skip series with out-of-order labels": {
    77  			reqs: []*prompb.WriteRequest{
    78  				{
    79  					Timeseries: []prompb.TimeSeries{
    80  						{
    81  							Labels:  append(lbls, labelpb.ZLabel{Name: "a", Value: "1"}, labelpb.ZLabel{Name: "b", Value: "1"}, labelpb.ZLabel{Name: "Z", Value: "1"}, labelpb.ZLabel{Name: "b", Value: "2"}),
    82  							Samples: []prompb.Sample{{Value: 1, Timestamp: 10}},
    83  						},
    84  					},
    85  				},
    86  			},
    87  			expectedErr: errors.Wrapf(labelpb.ErrOutOfOrderLabels, "add 1 series"),
    88  		},
    89  		"should error out and skip series with duplicate labels": {
    90  			reqs: []*prompb.WriteRequest{
    91  				{
    92  					Timeseries: []prompb.TimeSeries{
    93  						{
    94  							Labels:  append(lbls, labelpb.ZLabel{Name: "a", Value: "1"}, labelpb.ZLabel{Name: "b", Value: "1"}, labelpb.ZLabel{Name: "b", Value: "2"}, labelpb.ZLabel{Name: "z", Value: "1"}),
    95  							Samples: []prompb.Sample{{Value: 1, Timestamp: 10}},
    96  						},
    97  					},
    98  				},
    99  			},
   100  			expectedErr: errors.Wrapf(labelpb.ErrDuplicateLabels, "add 1 series"),
   101  		},
   102  		"should error out and skip series with out-of-order labels; accept series with valid labels": {
   103  			reqs: []*prompb.WriteRequest{
   104  				{
   105  					Timeseries: []prompb.TimeSeries{
   106  						{
   107  							Labels:  append(lbls, labelpb.ZLabel{Name: "A", Value: "1"}, labelpb.ZLabel{Name: "b", Value: "2"}),
   108  							Samples: []prompb.Sample{{Value: 1, Timestamp: 10}},
   109  						},
   110  						{
   111  							Labels:  append(lbls, labelpb.ZLabel{Name: "c", Value: "1"}, labelpb.ZLabel{Name: "d", Value: "2"}),
   112  							Samples: []prompb.Sample{{Value: 1, Timestamp: 10}},
   113  						},
   114  						{
   115  							Labels:  append(lbls, labelpb.ZLabel{Name: "E", Value: "1"}, labelpb.ZLabel{Name: "f", Value: "2"}),
   116  							Samples: []prompb.Sample{{Value: 1, Timestamp: 10}},
   117  						},
   118  					},
   119  				},
   120  			},
   121  			expectedErr: errors.Wrapf(labelpb.ErrOutOfOrderLabels, "add 2 series"),
   122  			expectedIngested: []prompb.TimeSeries{
   123  				{
   124  					Labels:  append(lbls, labelpb.ZLabel{Name: "c", Value: "1"}, labelpb.ZLabel{Name: "d", Value: "2"}),
   125  					Samples: []prompb.Sample{{Value: 1, Timestamp: 10}},
   126  				},
   127  			},
   128  		},
   129  		"should succeed when sample timestamp is NOT too far in the future": {
   130  			reqs: []*prompb.WriteRequest{
   131  				{
   132  					Timeseries: []prompb.TimeSeries{
   133  						{
   134  							Labels:  lbls,
   135  							Samples: []prompb.Sample{{Value: 1, Timestamp: int64(now)}},
   136  						},
   137  					},
   138  				},
   139  			},
   140  			expectedErr: nil,
   141  			expectedIngested: []prompb.TimeSeries{
   142  				{
   143  					Labels:  lbls,
   144  					Samples: []prompb.Sample{{Value: 1, Timestamp: int64(now)}},
   145  				},
   146  			},
   147  			opts: &WriterOptions{TooFarInFutureTimeWindow: 30 * int64(time.Second)},
   148  		},
   149  		"should error out when sample timestamp is too far in the future": {
   150  			reqs: []*prompb.WriteRequest{
   151  				{
   152  					Timeseries: []prompb.TimeSeries{
   153  						{
   154  							Labels: lbls,
   155  							// A sample with a very large timestamp in year 5138 (milliseconds)
   156  							Samples: []prompb.Sample{{Value: 1, Timestamp: 99999999999999}},
   157  						},
   158  					},
   159  				},
   160  			},
   161  			expectedErr: errors.Wrapf(storage.ErrOutOfBounds, "add 1 samples"),
   162  			opts:        &WriterOptions{TooFarInFutureTimeWindow: 10000},
   163  		},
   164  		"should succeed on valid series with exemplars": {
   165  			reqs: []*prompb.WriteRequest{{
   166  				Timeseries: []prompb.TimeSeries{
   167  					{
   168  						Labels: lbls,
   169  						// Ingesting an exemplar requires a sample to create the series first.
   170  						Samples: []prompb.Sample{{Value: 1, Timestamp: 10}},
   171  						Exemplars: []prompb.Exemplar{
   172  							{
   173  								Labels:    []labelpb.ZLabel{{Name: "traceID", Value: "123"}},
   174  								Value:     111,
   175  								Timestamp: 11,
   176  							},
   177  							{
   178  								Labels:    []labelpb.ZLabel{{Name: "traceID", Value: "234"}},
   179  								Value:     112,
   180  								Timestamp: 12,
   181  							},
   182  						},
   183  					},
   184  				},
   185  			}},
   186  			expectedErr:  nil,
   187  			maxExemplars: 2,
   188  		},
   189  		"should error out on valid series with out of order exemplars": {
   190  			reqs: []*prompb.WriteRequest{
   191  				{
   192  					Timeseries: []prompb.TimeSeries{
   193  						{
   194  							Labels: lbls,
   195  							// Ingesting an exemplar requires a sample to create the series first.
   196  							Samples: []prompb.Sample{{Value: 1, Timestamp: 10}},
   197  							Exemplars: []prompb.Exemplar{
   198  								{
   199  									Labels:    []labelpb.ZLabel{{Name: "traceID", Value: "123"}},
   200  									Value:     111,
   201  									Timestamp: 11,
   202  								},
   203  							},
   204  						},
   205  					},
   206  				},
   207  				{
   208  					Timeseries: []prompb.TimeSeries{
   209  						{
   210  							Labels: lbls,
   211  							Exemplars: []prompb.Exemplar{
   212  								{
   213  									Labels:    []labelpb.ZLabel{{Name: "traceID", Value: "1234"}},
   214  									Value:     111,
   215  									Timestamp: 10,
   216  								},
   217  							},
   218  						},
   219  					},
   220  				},
   221  			},
   222  			expectedErr:  errors.Wrapf(storage.ErrOutOfOrderExemplar, "add 1 exemplars"),
   223  			maxExemplars: 2,
   224  		},
   225  		"should error out when exemplar label length exceeds the limit": {
   226  			reqs: []*prompb.WriteRequest{
   227  				{
   228  					Timeseries: []prompb.TimeSeries{
   229  						{
   230  							Labels: lbls,
   231  							// Ingesting an exemplar requires a sample to create the series first.
   232  							Samples: []prompb.Sample{{Value: 1, Timestamp: 10}},
   233  							Exemplars: []prompb.Exemplar{
   234  								{
   235  									Labels:    []labelpb.ZLabel{{Name: strings.Repeat("a", exemplar.ExemplarMaxLabelSetLength), Value: "1"}},
   236  									Value:     111,
   237  									Timestamp: 11,
   238  								},
   239  							},
   240  						},
   241  					},
   242  				},
   243  			},
   244  			expectedErr:  errors.Wrapf(storage.ErrExemplarLabelLength, "add 1 exemplars"),
   245  			maxExemplars: 2,
   246  		},
   247  		"should succeed on histogram with valid labels": {
   248  			reqs: []*prompb.WriteRequest{
   249  				{
   250  					Timeseries: []prompb.TimeSeries{
   251  						{
   252  							Labels: append(lbls, labelpb.ZLabel{Name: "a", Value: "1"}, labelpb.ZLabel{Name: "b", Value: "2"}),
   253  							Histograms: []prompb.Histogram{
   254  								prompb.HistogramToHistogramProto(10, tsdbutil.GenerateTestHistogram(0)),
   255  							},
   256  						},
   257  					},
   258  				},
   259  			},
   260  			expectedErr: nil,
   261  			expectedIngested: []prompb.TimeSeries{
   262  				{
   263  					Labels: append(lbls, labelpb.ZLabel{Name: "a", Value: "1"}, labelpb.ZLabel{Name: "b", Value: "2"}),
   264  					Histograms: []prompb.Histogram{
   265  						prompb.HistogramToHistogramProto(10, tsdbutil.GenerateTestHistogram(0)),
   266  					},
   267  				},
   268  			},
   269  		},
   270  		"should succeed on float histogram with valid labels": {
   271  			reqs: []*prompb.WriteRequest{
   272  				{
   273  					Timeseries: []prompb.TimeSeries{
   274  						{
   275  							Labels: append(lbls, labelpb.ZLabel{Name: "a", Value: "1"}, labelpb.ZLabel{Name: "b", Value: "2"}),
   276  							Histograms: []prompb.Histogram{
   277  								prompb.FloatHistogramToHistogramProto(10, tsdbutil.GenerateTestFloatHistogram(1)),
   278  							},
   279  						},
   280  					},
   281  				},
   282  			},
   283  			expectedErr: nil,
   284  			expectedIngested: []prompb.TimeSeries{
   285  				{
   286  					Labels: append(lbls, labelpb.ZLabel{Name: "a", Value: "1"}, labelpb.ZLabel{Name: "b", Value: "2"}),
   287  					Histograms: []prompb.Histogram{
   288  						prompb.FloatHistogramToHistogramProto(10, tsdbutil.GenerateTestFloatHistogram(1)),
   289  					},
   290  				},
   291  			},
   292  		},
   293  		"should error out on valid histograms with out of order histogram": {
   294  			reqs: []*prompb.WriteRequest{
   295  				{
   296  					Timeseries: []prompb.TimeSeries{
   297  						{
   298  							Labels: append(lbls, labelpb.ZLabel{Name: "a", Value: "1"}, labelpb.ZLabel{Name: "b", Value: "2"}),
   299  							Histograms: []prompb.Histogram{
   300  								prompb.HistogramToHistogramProto(10, tsdbutil.GenerateTestHistogram(0)),
   301  							},
   302  						},
   303  					},
   304  				},
   305  				{
   306  					Timeseries: []prompb.TimeSeries{
   307  						{
   308  							Labels: append(lbls, labelpb.ZLabel{Name: "a", Value: "1"}, labelpb.ZLabel{Name: "b", Value: "2"}),
   309  							Histograms: []prompb.Histogram{
   310  								prompb.HistogramToHistogramProto(9, tsdbutil.GenerateTestHistogram(0)),
   311  							},
   312  						},
   313  					},
   314  				},
   315  			},
   316  			expectedErr: errors.Wrapf(storage.ErrOutOfOrderSample, "add 1 samples"),
   317  			expectedIngested: []prompb.TimeSeries{
   318  				{
   319  					Labels: append(lbls, labelpb.ZLabel{Name: "a", Value: "1"}, labelpb.ZLabel{Name: "b", Value: "2"}),
   320  					Histograms: []prompb.Histogram{
   321  						prompb.HistogramToHistogramProto(10, tsdbutil.GenerateTestHistogram(0)),
   322  					},
   323  				},
   324  			},
   325  		},
   326  	}
   327  
   328  	for testName, testData := range tests {
   329  		t.Run(testName, func(t *testing.T) {
   330  			dir := t.TempDir()
   331  			logger := log.NewNopLogger()
   332  
   333  			m := NewMultiTSDB(dir, logger, prometheus.NewRegistry(), &tsdb.Options{
   334  				MinBlockDuration:       (2 * time.Hour).Milliseconds(),
   335  				MaxBlockDuration:       (2 * time.Hour).Milliseconds(),
   336  				RetentionDuration:      (6 * time.Hour).Milliseconds(),
   337  				NoLockfile:             true,
   338  				MaxExemplars:           testData.maxExemplars,
   339  				EnableExemplarStorage:  true,
   340  				EnableNativeHistograms: true,
   341  			},
   342  				labels.FromStrings("replica", "01"),
   343  				"tenant_id",
   344  				nil,
   345  				false,
   346  				metadata.NoneFunc,
   347  			)
   348  			t.Cleanup(func() { testutil.Ok(t, m.Close()) })
   349  
   350  			testutil.Ok(t, m.Flush())
   351  			testutil.Ok(t, m.Open())
   352  
   353  			ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   354  			defer cancel()
   355  
   356  			app, err := m.TenantAppendable(tenancy.DefaultTenant)
   357  			testutil.Ok(t, err)
   358  
   359  			testutil.Ok(t, runutil.Retry(1*time.Second, ctx.Done(), func() error {
   360  				_, err = app.Appender(context.Background())
   361  				return err
   362  			}))
   363  
   364  			w := NewWriter(logger, m, testData.opts)
   365  
   366  			for idx, req := range testData.reqs {
   367  				err = w.Write(context.Background(), tenancy.DefaultTenant, req)
   368  
   369  				// We expect no error on any request except the last one
   370  				// which may error (and in that case we assert on it).
   371  				if testData.expectedErr == nil || idx < len(testData.reqs)-1 {
   372  					testutil.Ok(t, err)
   373  				} else {
   374  					testutil.NotOk(t, err)
   375  					testutil.Equals(t, testData.expectedErr.Error(), err.Error())
   376  				}
   377  			}
   378  
   379  			// On each expected series, assert we have a ref available.
   380  			a, err := app.Appender(context.Background())
   381  			testutil.Ok(t, err)
   382  			gr := a.(storage.GetRef)
   383  
   384  			for _, ts := range testData.expectedIngested {
   385  				l := labelpb.ZLabelsToPromLabels(ts.Labels)
   386  				ref, _ := gr.GetRef(l, l.Hash())
   387  				testutil.Assert(t, ref != 0, fmt.Sprintf("appender should have reference to series %v", ts))
   388  			}
   389  		})
   390  	}
   391  }
   392  
   393  func BenchmarkWriterTimeSeriesWithSingleLabel_10(b *testing.B)   { benchmarkWriter(b, 1, 10, false) }
   394  func BenchmarkWriterTimeSeriesWithSingleLabel_100(b *testing.B)  { benchmarkWriter(b, 1, 100, false) }
   395  func BenchmarkWriterTimeSeriesWithSingleLabel_1000(b *testing.B) { benchmarkWriter(b, 1, 1000, false) }
   396  
   397  func BenchmarkWriterTimeSeriesWith10Labels_10(b *testing.B)   { benchmarkWriter(b, 10, 10, false) }
   398  func BenchmarkWriterTimeSeriesWith10Labels_100(b *testing.B)  { benchmarkWriter(b, 10, 100, false) }
   399  func BenchmarkWriterTimeSeriesWith10Labels_1000(b *testing.B) { benchmarkWriter(b, 10, 1000, false) }
   400  
   401  func BenchmarkWriterTimeSeriesWithHistogramsWithSingleLabel_10(b *testing.B) {
   402  	benchmarkWriter(b, 1, 10, true)
   403  }
   404  func BenchmarkWriterTimeSeriesWithHistogramsWithSingleLabel_100(b *testing.B) {
   405  	benchmarkWriter(b, 1, 100, true)
   406  }
   407  func BenchmarkWriterTimeSeriesWithHistogramsWithSingleLabel_1000(b *testing.B) {
   408  	benchmarkWriter(b, 1, 1000, true)
   409  }
   410  
   411  func BenchmarkWriterTimeSeriesWithHistogramsWith10Labels_10(b *testing.B) {
   412  	benchmarkWriter(b, 10, 10, true)
   413  }
   414  func BenchmarkWriterTimeSeriesWithHistogramsWith10Labels_100(b *testing.B) {
   415  	benchmarkWriter(b, 10, 100, true)
   416  }
   417  func BenchmarkWriterTimeSeriesWithHistogramsWith10Labels_1000(b *testing.B) {
   418  	benchmarkWriter(b, 10, 1000, true)
   419  }
   420  
   421  func benchmarkWriter(b *testing.B, labelsNum int, seriesNum int, generateHistograms bool) {
   422  	dir := b.TempDir()
   423  	logger := log.NewNopLogger()
   424  
   425  	m := NewMultiTSDB(dir, logger, prometheus.NewRegistry(), &tsdb.Options{
   426  		MinBlockDuration:       (2 * time.Hour).Milliseconds(),
   427  		MaxBlockDuration:       (2 * time.Hour).Milliseconds(),
   428  		RetentionDuration:      (6 * time.Hour).Milliseconds(),
   429  		NoLockfile:             true,
   430  		MaxExemplars:           0,
   431  		EnableExemplarStorage:  true,
   432  		EnableNativeHistograms: generateHistograms,
   433  	},
   434  		labels.FromStrings("replica", "01"),
   435  		"tenant_id",
   436  		nil,
   437  		false,
   438  		metadata.NoneFunc,
   439  	)
   440  	b.Cleanup(func() { testutil.Ok(b, m.Close()) })
   441  
   442  	testutil.Ok(b, m.Flush())
   443  	testutil.Ok(b, m.Open())
   444  
   445  	app, err := m.TenantAppendable("foo")
   446  	testutil.Ok(b, err)
   447  
   448  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   449  	defer cancel()
   450  
   451  	testutil.Ok(b, runutil.Retry(1*time.Second, ctx.Done(), func() error {
   452  		_, err = app.Appender(context.Background())
   453  		return err
   454  	}))
   455  
   456  	timeSeries := generateLabelsAndSeries(labelsNum, seriesNum, generateHistograms)
   457  
   458  	wreq := &prompb.WriteRequest{
   459  		Timeseries: timeSeries,
   460  	}
   461  
   462  	b.Run("without interning", func(b *testing.B) {
   463  		w := NewWriter(logger, m, &WriterOptions{Intern: false})
   464  
   465  		b.ReportAllocs()
   466  		b.ResetTimer()
   467  
   468  		for i := 0; i < b.N; i++ {
   469  			testutil.Ok(b, w.Write(ctx, "foo", wreq))
   470  		}
   471  	})
   472  
   473  	b.Run("with interning", func(b *testing.B) {
   474  		w := NewWriter(logger, m, &WriterOptions{Intern: true})
   475  
   476  		b.ReportAllocs()
   477  		b.ResetTimer()
   478  
   479  		for i := 0; i < b.N; i++ {
   480  			testutil.Ok(b, w.Write(ctx, "foo", wreq))
   481  		}
   482  	})
   483  
   484  }
   485  
   486  // generateLabelsAndSeries generates time series for benchmark with specified number of labels.
   487  // Although in this method we're reusing samples with same value and timestamp, Prometheus actually allows us to provide exact
   488  // duplicates without error (see comment https://github.com/prometheus/prometheus/blob/release-2.37/tsdb/head_append.go#L316).
   489  // This also means the sample won't be appended, which means the overhead of appending additional samples to head is not
   490  // reflected in the benchmark, but should still capture the performance of receive writer.
   491  func generateLabelsAndSeries(numLabels int, numSeries int, generateHistograms bool) []prompb.TimeSeries {
   492  	// Generate some labels first.
   493  	l := make([]labelpb.ZLabel, 0, numLabels)
   494  	l = append(l, labelpb.ZLabel{Name: "__name__", Value: "test"})
   495  	for i := 0; i < numLabels; i++ {
   496  		l = append(l, labelpb.ZLabel{Name: fmt.Sprintf("label_%s", string(rune('a'+i))), Value: fmt.Sprintf("%d", i)})
   497  	}
   498  
   499  	ts := make([]prompb.TimeSeries, numSeries)
   500  	for j := 0; j < numSeries; j++ {
   501  		ts[j] = prompb.TimeSeries{
   502  			Labels: l,
   503  		}
   504  
   505  		if generateHistograms {
   506  			ts[j].Histograms = []prompb.Histogram{prompb.HistogramToHistogramProto(10, tsdbutil.GenerateTestHistogram(0))}
   507  			continue
   508  		}
   509  
   510  		ts[j].Samples = []prompb.Sample{{Value: 1, Timestamp: 10}}
   511  	}
   512  
   513  	return ts
   514  }