diff --git a/CHANGELOG.md b/CHANGELOG.md index 265e21829..c755b02fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Order should be `CHANGE`, `FEATURE`, `ENHANCEMENT`, and `BUGFIX` ## unreleased/master * [FEATURE] Add `--extra-headers` support for `cortextool rules` commands. #288 +* [BUGFIX] Fix out of bounds error on export with large timespans and/or series count. ## v0.11.0 diff --git a/pkg/backfill/backfill.go b/pkg/backfill/backfill.go index abc993690..6a21007bc 100644 --- a/pkg/backfill/backfill.go +++ b/pkg/backfill/backfill.go @@ -85,7 +85,7 @@ func CreateBlocks(input IteratorCreator, mint, maxt int64, maxSamplesInAppender var wroteHeader bool - for t := mint; t <= maxt; t = t + blockDuration { + for t := mint; t <= maxt; t = t + blockDuration/2 { err := func() error { w, err := tsdb.NewBlockWriter(log.NewNopLogger(), outputDir, blockDuration) if err != nil { @@ -101,7 +101,7 @@ func CreateBlocks(input IteratorCreator, mint, maxt int64, maxSamplesInAppender ctx := context.Background() app := w.Appender(ctx) i := input() - tsUpper := t + blockDuration + tsUpper := t + blockDuration/2 samplesCount := 0 for { err := i.Next() diff --git a/pkg/commands/remote_read.go b/pkg/commands/remote_read.go index a1b5cc277..268eb9837 100644 --- a/pkg/commands/remote_read.go +++ b/pkg/commands/remote_read.go @@ -404,7 +404,7 @@ func (c *RemoteReadCommand) export(k *kingpin.ParseContext) error { return err } - iterator := func() backfill.Iterator { + iteratorCreator := func() backfill.Iterator { return newTimeSeriesIterator(timeseries) } @@ -421,7 +421,7 @@ func (c *RemoteReadCommand) export(k *kingpin.ParseContext) error { defer pipeR.Close() log.Infof("Store TSDB blocks in '%s'", c.tsdbPath) - if err := backfill.CreateBlocks(iterator, int64(mint), int64(maxt), 1000, c.tsdbPath, true, pipeW); err != nil { + if err := backfill.CreateBlocks(iteratorCreator, int64(mint), int64(maxt), 1000, c.tsdbPath, true, pipeW); err != nil { return err } diff --git a/pkg/commands/remote_read_test.go b/pkg/commands/remote_read_test.go index e208a736e..1abc77834 100644 --- a/pkg/commands/remote_read_test.go +++ b/pkg/commands/remote_read_test.go @@ -1,11 +1,15 @@ package commands import ( + "fmt" "io" "testing" + "time" "github.com/prometheus/prometheus/prompb" "github.com/stretchr/testify/assert" + + "github.com/grafana/cortex-tools/pkg/backfill" ) func TestTimeSeriesIterator(t *testing.T) { @@ -144,3 +148,41 @@ func TestTimeSeriesIterator(t *testing.T) { } } + +// TestEarlyCommit writes samples of many series that don't fit into the same +// append commit. It makes sure that batching the samples into many commits +// doesn't cause the appends to advance the head block too far and make future +// appends invalid. +func TestEarlyCommit(t *testing.T) { + maxSamplesPerBlock := 1000 + series := 100 + samples := 140 + + start := int64(time.Date(2023, 8, 30, 11, 42, 17, 0, time.UTC).UnixNano()) + inc := int64(time.Minute / time.Millisecond) + end := start + (inc * int64(samples)) + ts := make([]*prompb.TimeSeries, series) + for i := 0; i < series; i++ { + s := &prompb.TimeSeries{ + Labels: []prompb.Label{ + { + Name: "__name__", + Value: fmt.Sprintf("metric_%d", i), + }, + }, + Samples: make([]prompb.Sample, samples), + } + for j := 0; j < samples; j++ { + s.Samples[j] = prompb.Sample{ + Value: float64(j), + Timestamp: start + (inc * int64(j)), + } + } + ts[i] = s + } + iterator := func() backfill.Iterator { + return newTimeSeriesIterator(ts) + } + err := backfill.CreateBlocks(iterator, start, end, maxSamplesPerBlock, t.TempDir(), true, io.Discard) + assert.NoError(t, err) +}