Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

compactor: change GatherIndexIssueStats validation logic on sorted labels #831

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions pkg/block/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,13 +345,12 @@ func GatherIndexIssueStats(logger log.Logger, fn string, minTime int64, maxTime
if lastLset != nil && labels.Compare(lastLset, lset) >= 0 {
return stats, errors.Errorf("series %v out of order; previous %v", lset, lastLset)
}
l0 := lset[0]
for _, l := range lset[1:] {
if l.Name <= l0.Name {
return stats, errors.Errorf("out-of-order label set %s for series %d", lset, id)
}
l0 = l

// Ensure all but the first value (__name__) are sorted
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing trailing petiof

if !sort.SliceIsSorted(lset[1:], func(i, j int) bool { return lset[i+1].Name <= lset[j+1].Name }) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's actually check if name is first maybe?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because I think that's the whole point.. if the labels are ordered name is between uppercase and lowercase already.

Copy link
Member

@GiedriusS GiedriusS Feb 13, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bit different and I might raise a different pull request but what if we would relax this check a bit and switch it to < here? Essentially, it seems like Prometheus accepts duplicate labels and if that happens (happened in our case) then compactor doesn't work anymore due to this check. Case in point:

{"caller":"main.go:181","err":"error executing compaction: compaction failed: compaction: gather index issues for block /data/compact/0@{monitor=\"monitor\",replica=\"repl\"}/01D34EDQMSQ29RHAC47XGKHGC7: out-of-order label set {__name__=\"foo\",exported_job=\"vv\",host=\"172_16_226_56\",host=\"172_16_226_56\",region=\"lt\",subtask_index=\"5\",task_attempt_id=\"32e4b047bb768583ff57c709be3b1046\",task_attempt_num=\"8\",task_id=\"688c028a219ff3372f3eecb0ee5811f9\",task_name=\"Source:_foo\",tenant=\"abc\",tier=\"cooltier\",tm_id=\"53b2ed987b08f427dec4ee1465df91fa\"} for series 2594231","level":"error","msg":"running command failed","ts":"2019-02-11T13:30:33.901722306Z"}

@bwplotka

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to #848.

return stats, errors.Errorf("out-of-order label set %s for series %d", lset, id)
}

if len(chks) == 0 {
return stats, errors.Errorf("empty chunks for series %d", id)
}
Expand Down
155 changes: 155 additions & 0 deletions pkg/block/index_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package block

import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"

"github.com/prometheus/tsdb/index"
"github.com/prometheus/tsdb/tsdbutil"

"github.com/go-kit/kit/log"
"github.com/improbable-eng/thanos/pkg/testutil"
"github.com/prometheus/tsdb/labels"
Expand Down Expand Up @@ -44,3 +48,154 @@ func TestWriteReadIndexCache(t *testing.T) {
testutil.Equals(t, []string{"1"}, vals)
testutil.Equals(t, 6, len(postings))
}

func TestGatherIndexIssueStats(t *testing.T) {
tmpDir, err := ioutil.TempDir("", "test-gather-stats")
testutil.Ok(t, err)
// Cleans up all temp files
defer func() { testutil.Ok(t, os.RemoveAll(tmpDir)) }()

l := log.NewNopLogger()

// Non existent file
_, err = GatherIndexIssueStats(l, "", 0, 0)
testutil.NotOk(t, err)

// Real but empty file
tmpfile, err := ioutil.TempFile(tmpDir, "empty-file")
testutil.Ok(t, err)
_, err = GatherIndexIssueStats(l, tmpfile.Name(), 0, 0)
testutil.NotOk(t, err)

// Working test cases
testCases := []struct {
name string
lsets []labels.Labels
expected Stats
startTime int64
endTime int64
numSamples int
}{
{
name: "empty case",
},
{
name: "1 labelset name only",
lsets: []labels.Labels{
{
{Name: "__name__", Value: "metric_name"},
},
},
expected: Stats{TotalSeries: 1},
},
{
name: "2 labelsets name only",
lsets: []labels.Labels{
{
{Name: "__name__", Value: "metric_name"},
}, {
{Name: "__name__", Value: "metric_name_2"},
},
},
expected: Stats{TotalSeries: 2},
},
{
name: "2 labelsets with adtl labels",
lsets: []labels.Labels{
{
{Name: "__name__", Value: "metric_name"},
{Name: "label", Value: "value1"},
}, {
{Name: "__name__", Value: "metric_name"},
{Name: "label", Value: "value2"},
},
},
expected: Stats{TotalSeries: 2},
},
{
name: "1 labelset with lots of labels",
lsets: []labels.Labels{
{
{Name: "__name__", Value: "metric_name"},
{Name: "a", Value: "1"},
{Name: "b", Value: "2"},
{Name: "c", Value: "3"},
{Name: "d", Value: "4"},
{Name: "e", Value: "5"},
},
},
expected: Stats{TotalSeries: 1},
},
{
// Name always comes first and prometheus doesn't sort it.
// https://github.com/prometheus/prometheus/blob/9f903fb3f7841f55d1b634d5fb02986a8bfc5e0c/pkg/textparse/promparse.go#L230
name: "1 labelset with capitalized label",
lsets: []labels.Labels{
{
{Name: "__name__", Value: "metric_name"},
{Name: "CapitalizedLabel", Value: "1"},
{Name: "CapitalizedLabel2", Value: "2"},
{Name: "more_common_label", Value: "3"},
},
},
expected: Stats{TotalSeries: 1},
},
}

for caseNum, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
tmpfile, err := ioutil.TempFile(tmpDir, fmt.Sprintf("test-case-%d", caseNum))
testutil.Ok(t, err)

testutil.Ok(t, writeTestIndex(tmpfile.Name(), tc.startTime, tc.numSamples, tc.lsets...))

stats, err := GatherIndexIssueStats(l, tmpfile.Name(), tc.startTime, tc.endTime)

testutil.Ok(t, err)
// == works for now since the struct is only ints (no pointers or nested values)
testutil.Assert(t, tc.expected == stats,
"index stats did not have an expected value. \nExpected: %+v \nActual: %+v", tc.expected, stats)
})
}

}

// writeTestIndex is a helper function that creates an index file with dummy data for testing
func writeTestIndex(filename string, startTime int64, numSamples int, lsets ...labels.Labels) error {
chunks := tsdbutil.PopulatedChunk(numSamples, startTime)

symbols := make(map[string]struct{})
for _, lset := range lsets {
for _, l := range lset {
symbols[l.Name] = struct{}{}
symbols[l.Value] = struct{}{}
}
}

memPostings := index.NewMemPostings()
for i, lset := range lsets {
memPostings.Add(uint64(i), lset)
}

w, err := index.NewWriter(filename)
if err != nil {
return err
}

if err = w.AddSymbols(symbols); err != nil {
return err
}

for i, lset := range lsets {
if err = w.AddSeries(uint64(i), lset, chunks); err != nil {
return err
}
}

pKey, pVal := index.AllPostingsKey()
if err = w.WritePostings(pKey, pVal, memPostings.All()); err != nil {
return err
}

return w.Close()
}