Skip to content

Commit a600342

Browse files
committed
Optimize storage.LabelQuerier.LabelValues implementations
Signed-off-by: Arve Knudsen <[email protected]>
1 parent 002ae0a commit a600342

File tree

10 files changed

+189
-85
lines changed

10 files changed

+189
-85
lines changed

model/labels/matcher.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,20 @@ const (
2626
MatchNotEqual
2727
MatchRegexp
2828
MatchNotRegexp
29+
// MatchSet is a special type for internal use, that matches series with a certain label name.
30+
MatchSet
2931
)
3032

3133
var matchTypeToStr = [...]string{
3234
MatchEqual: "=",
3335
MatchNotEqual: "!=",
3436
MatchRegexp: "=~",
3537
MatchNotRegexp: "!~",
38+
MatchSet: "[isSet]",
3639
}
3740

3841
func (m MatchType) String() string {
39-
if m < MatchEqual || m > MatchNotRegexp {
42+
if m < MatchEqual || m > MatchSet {
4043
panic("unknown match type")
4144
}
4245
return matchTypeToStr[m]
@@ -92,8 +95,11 @@ func (m *Matcher) Matches(s string) bool {
9295
return m.re.MatchString(s)
9396
case MatchNotRegexp:
9497
return !m.re.MatchString(s)
98+
case MatchSet:
99+
return true
100+
default:
101+
panic("labels.Matcher.Matches: invalid match type")
95102
}
96-
panic("labels.Matcher.Matches: invalid match type")
97103
}
98104

99105
// Inverse returns a matcher that matches the opposite.
@@ -107,8 +113,9 @@ func (m *Matcher) Inverse() (*Matcher, error) {
107113
return NewMatcher(MatchNotRegexp, m.Name, m.Value)
108114
case MatchNotRegexp:
109115
return NewMatcher(MatchRegexp, m.Name, m.Value)
116+
default:
117+
panic("labels.Matcher.Matches: invalid match type")
110118
}
111-
panic("labels.Matcher.Matches: invalid match type")
112119
}
113120

114121
// GetRegexString returns the regex string.

tsdb/block.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@ type IndexReader interface {
8282
// avoiding same calculations twice, however this implementation may lead to a worse performance when called once.
8383
PostingsForMatchers(concurrent bool, ms ...*labels.Matcher) (index.Postings, error)
8484

85+
// PostingsWithLabel returns the postings list iterator for the label name.
86+
// The Postings here contain the offsets to the series inside the index.
87+
// Found IDs are not strictly required to point to a valid Series, e.g.
88+
// during background garbage collections.
89+
PostingsWithLabel(name string) (index.Postings, error)
90+
8591
// SortedPostings returns a postings list that is reordered to be sorted
8692
// by the label set of the underlying series.
8793
SortedPostings(index.Postings) index.Postings
@@ -501,6 +507,10 @@ func (r blockIndexReader) LabelValues(name string, matchers ...*labels.Matcher)
501507
return labelValuesWithMatchers(r.ir, name, matchers...)
502508
}
503509

510+
func (r blockIndexReader) PostingsWithLabel(name string) (index.Postings, error) {
511+
return r.ir.PostingsWithLabel(name)
512+
}
513+
504514
func (r blockIndexReader) LabelNames(matchers ...*labels.Matcher) ([]string, error) {
505515
if len(matchers) == 0 {
506516
return r.b.LabelNames()

tsdb/head_read.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,10 @@ func (h *headIndexReader) PostingsForMatchers(concurrent bool, ms ...*labels.Mat
124124
return h.head.pfmc.PostingsForMatchers(h, concurrent, ms...)
125125
}
126126

127+
func (h *headIndexReader) PostingsWithLabel(name string) (index.Postings, error) {
128+
return h.head.postings.GetWithLabel(name), nil
129+
}
130+
127131
func (h *headIndexReader) SortedPostings(p index.Postings) index.Postings {
128132
series := make([]*memSeries, 0, 128)
129133

tsdb/index/index.go

Lines changed: 94 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1484,31 +1484,35 @@ func (r *Reader) SortedLabelValues(name string, matchers ...*labels.Matcher) ([]
14841484
// LabelValues returns value tuples that exist for the given label name.
14851485
// It is not safe to use the return value beyond the lifetime of the byte slice
14861486
// passed into the Reader.
1487-
// TODO(replay): Support filtering by matchers
14881487
func (r *Reader) LabelValues(name string, matchers ...*labels.Matcher) ([]string, error) {
1489-
if len(matchers) > 0 {
1490-
return nil, errors.Errorf("matchers parameter is not implemented: %+v", matchers)
1491-
}
1492-
14931488
if r.version == FormatV1 {
14941489
e, ok := r.postingsV1[name]
14951490
if !ok {
14961491
return nil, nil
14971492
}
14981493
values := make([]string, 0, len(e))
14991494
for k := range e {
1500-
values = append(values, k)
1495+
isMatch := true
1496+
for _, m := range matchers {
1497+
if m.Name == name && !m.Matches(k) {
1498+
isMatch = false
1499+
break
1500+
}
1501+
}
1502+
1503+
if isMatch {
1504+
values = append(values, k)
1505+
}
15011506
}
15021507
return values, nil
15031508

15041509
}
1505-
e, ok := r.postings[name]
1506-
if !ok {
1507-
return nil, nil
1508-
}
1510+
1511+
e := r.postings[name]
15091512
if len(e) == 0 {
15101513
return nil, nil
15111514
}
1515+
15121516
values := make([]string, 0, len(e)*symbolFactor)
15131517

15141518
d := encoding.NewDecbufAt(r.b, int(r.toc.PostingsTable), nil)
@@ -1528,7 +1532,19 @@ func (r *Reader) LabelValues(name string, matchers ...*labels.Matcher) ([]string
15281532
d.Skip(skip)
15291533
}
15301534
s := yoloString(d.UvarintBytes()) // Label value.
1531-
values = append(values, s)
1535+
1536+
isMatch := true
1537+
// Try to exclude via matchers for the label name
1538+
for _, m := range matchers {
1539+
if m.Name == name && !m.Matches(s) {
1540+
isMatch = false
1541+
break
1542+
}
1543+
}
1544+
1545+
if isMatch {
1546+
values = append(values, s)
1547+
}
15321548
if s == lastVal {
15331549
break
15341550
}
@@ -1647,8 +1663,8 @@ func (r *Reader) Postings(name string, values ...string) (Postings, error) {
16471663
return Merge(res...), nil
16481664
}
16491665

1650-
e, ok := r.postings[name]
1651-
if !ok {
1666+
e := r.postings[name]
1667+
if len(e) == 0 {
16521668
return EmptyPostings(), nil
16531669
}
16541670

@@ -1725,6 +1741,71 @@ func (r *Reader) Postings(name string, values ...string) (Postings, error) {
17251741
return Merge(res...), nil
17261742
}
17271743

1744+
func (r *Reader) PostingsWithLabel(name string) (Postings, error) {
1745+
if r.version == FormatV1 {
1746+
e := r.postingsV1[name]
1747+
if len(e) == 0 {
1748+
return EmptyPostings(), nil
1749+
}
1750+
1751+
var res []Postings
1752+
for _, off := range e {
1753+
// Read from the postings table.
1754+
d := encoding.NewDecbufAt(r.b, int(off), castagnoliTable)
1755+
_, p, err := r.dec.Postings(d.Get())
1756+
if err != nil {
1757+
return nil, errors.Wrap(err, "decode postings")
1758+
}
1759+
res = append(res, p)
1760+
}
1761+
return Merge(res...), nil
1762+
}
1763+
1764+
e := r.postings[name]
1765+
if len(e) == 0 {
1766+
return EmptyPostings(), nil
1767+
}
1768+
1769+
d := encoding.NewDecbufAt(r.b, int(r.toc.PostingsTable), nil)
1770+
// Skip to start
1771+
d.Skip(e[0].off)
1772+
lastVal := e[len(e)-1].value
1773+
1774+
skip := 0
1775+
var res []Postings
1776+
for d.Err() == nil {
1777+
if skip == 0 {
1778+
// These are always the same number of bytes,
1779+
// and it's faster to skip than to parse.
1780+
skip = d.Len()
1781+
d.Uvarint() // Keycount.
1782+
d.UvarintBytes() // Label name.
1783+
skip -= d.Len()
1784+
} else {
1785+
d.Skip(skip)
1786+
}
1787+
v := yoloString(d.UvarintBytes()) // Label value.
1788+
1789+
postingsOff := d.Uvarint64()
1790+
// Read from the postings table
1791+
d2 := encoding.NewDecbufAt(r.b, int(postingsOff), castagnoliTable)
1792+
_, p, err := r.dec.Postings(d2.Get())
1793+
if err != nil {
1794+
return nil, errors.Wrap(err, "decode postings")
1795+
}
1796+
res = append(res, p)
1797+
1798+
if v == lastVal {
1799+
break
1800+
}
1801+
}
1802+
if d.Err() != nil {
1803+
return nil, errors.Wrap(d.Err(), "get postings offset entry")
1804+
}
1805+
1806+
return Merge(res...), nil
1807+
}
1808+
17281809
// SortedPostings returns the given postings list reordered so that the backing series
17291810
// are sorted.
17301811
func (r *Reader) SortedPostings(p Postings) Postings {

tsdb/index/postings.go

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,13 +135,24 @@ func (p *MemPostings) LabelNames() []string {
135135
}
136136

137137
// LabelValues returns label values for the given name.
138-
func (p *MemPostings) LabelValues(name string) []string {
138+
func (p *MemPostings) LabelValues(name string, matchers ...*labels.Matcher) []string {
139139
p.mtx.RLock()
140140
defer p.mtx.RUnlock()
141141

142142
values := make([]string, 0, len(p.m[name]))
143143
for v := range p.m[name] {
144-
values = append(values, v)
144+
isMatch := true
145+
// Try to exclude this value through corresponding matchers
146+
for _, m := range matchers {
147+
if m.Name == name && !m.Matches(v) {
148+
isMatch = false
149+
break
150+
}
151+
}
152+
153+
if isMatch {
154+
values = append(values, v)
155+
}
145156
}
146157
return values
147158
}
@@ -216,6 +227,18 @@ func (p *MemPostings) Get(name, value string) Postings {
216227
return newListPostings(lp...)
217228
}
218229

230+
// GetWithLabel returns a postings list for the given label name.
231+
func (p *MemPostings) GetWithLabel(name string) Postings {
232+
p.mtx.RLock()
233+
var ps []Postings
234+
for _, srs := range p.m[name] {
235+
ps = append(ps, newListPostings(srs...))
236+
}
237+
p.mtx.RUnlock()
238+
239+
return Merge(ps...)
240+
}
241+
219242
// All returns a postings list over all documents ever added.
220243
func (p *MemPostings) All() Postings {
221244
return p.Get(AllPostingsKey())

tsdb/ooo_head_read.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,10 @@ func (oh *OOOHeadIndexReader) Postings(name string, values ...string) (index.Pos
212212
}
213213
}
214214

215+
func (oh *OOOHeadIndexReader) PostingsWithLabel(name string) (index.Postings, error) {
216+
return oh.head.postings.GetWithLabel(name), nil
217+
}
218+
215219
type OOOHeadChunkReader struct {
216220
head *Head
217221
mint, maxt int64
@@ -410,6 +414,10 @@ func (ir *OOOCompactionHeadIndexReader) Postings(name string, values ...string)
410414
return index.NewListPostings(ir.ch.postings), nil
411415
}
412416

417+
func (ir *OOOCompactionHeadIndexReader) PostingsWithLabel(name string) (index.Postings, error) {
418+
return nil, errors.New("not implemented")
419+
}
420+
413421
func (ir *OOOCompactionHeadIndexReader) SortedPostings(p index.Postings) index.Postings {
414422
// This will already be sorted from the Postings() call above.
415423
return p

tsdb/postings_for_matchers_cache.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ type IndexPostingsReader interface {
2525
// Found IDs are not strictly required to point to a valid Series, e.g.
2626
// during background garbage collections. Input values must be sorted.
2727
Postings(name string, values ...string) (index.Postings, error)
28+
29+
// PostingsWithLabel returns the postings list iterator for the label name.
30+
// The Postings here contain the offsets to the series inside the index.
31+
// Found IDs are not strictly required to point to a valid Series, e.g.
32+
// during background garbage collections.
33+
PostingsWithLabel(name string) (index.Postings, error)
2834
}
2935

3036
// NewPostingsForMatchersCache creates a new PostingsForMatchersCache.

tsdb/postings_for_matchers_cache_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,10 @@ func (idx indexForPostingsMock) Postings(string, ...string) (index.Postings, err
269269
panic("implement me")
270270
}
271271

272+
func (idx indexForPostingsMock) PostingsWithLabel(string) (index.Postings, error) {
273+
panic("implement me")
274+
}
275+
272276
// timeNowMock offers a mockable time.Now() implementation
273277
// empty value is ready to be used, and it should not be copied (use a reference)
274278
type timeNowMock struct {

0 commit comments

Comments
 (0)