Repository: prometheus/prometheus
File: tsdb/index/postings.go (Go)

Code before:
func (p *MemPostings) addFor(id storage.SeriesRef, l labels.Label) {
	nm, ok := p.m[l.Name]
	if !ok {
		nm = map[string][]storage.SeriesRef{}
		p.m[l.Name] = nm
	}
	list := appendWithExponentialGrowth(nm[l.Value], id)
	nm[l.Value] = list

	if !p.ordered {
		return
	}
	// There is no guarantee that no higher ID was inserted before as they may
	// be generated independently before adding them to postings.
	// We repair order violations on insert. The invariant is that the first n-1
	// items in the list are already sorted.
	for i := len(list) - 1; i >= 1; i-- {
		if list[i] >= list[i-1] {
			break
		}
		list[i], list[i-1] = list[i-1], list[i]
	}
}

func (p *MemPostings) PostingsForLabelMatching(ctx context.Context, name string, match func(string) bool) Postings {
	if match == nil {
		// All postings are requested, no filtering.
		p.mtx.RLock()

		e := p.m[name]
		its := make([]Postings, 0, len(e))
		for _, refs := range e {
			if len(refs) > 0 {
				its = append(its, NewListPostings(refs))
			}
		}

		// Let the mutex go before merging.
		p.mtx.RUnlock()
		return Merge(ctx, its...)
	}

	// We'll copy the values into a slice and then match over that,
	// this way we don't need to hold the mutex while we're matching,
	// which can be slow (seconds) if the match function is a huge regex.
	// Holding this lock prevents new series from being added (slows down the write path)
	// and blocks the compaction process.
	vals := p.labelValues(name)
	for i, count := 0, 1; i < len(vals); count++ {
		if count%checkContextEveryNIterations == 0 && ctx.Err() != nil {
			return ErrPostings(ctx.Err())

Review comment (suggestion):
I was going to ask for this to be documented on the interface, but since it's a breaking change, I would like to ask you to define a completely new method for this.

If we keep the method signature the same, a downstream implementation may not be aware of the change, and it would try to call `match()` and would panic.

If we add a new method, then if there's a downstream implementation, they will need to explicitly implement the new method. If there isn't, they won't be affected.

So I would suggest something like `PostingsForAllLabelValues(ctx context.Contex, name string) Postings`

Code after:
func (p *MemPostings) addFor(id storage.SeriesRef, l labels.Label) {
	nm, ok := p.m[l.Name]
	if !ok {
		nm = map[string][]storage.SeriesRef{}
		p.m[l.Name] = nm
	}
	list := appendWithExponentialGrowth(nm[l.Value], id)
	nm[l.Value] = list

	if !p.ordered {
		return
	}
	// There is no guarantee that no higher ID was inserted before as they may
	// be generated independently before adding them to postings.
	// We repair order violations on insert. The invariant is that the first n-1
	// items in the list are already sorted.
	for i := len(list) - 1; i >= 1; i-- {
		if list[i] >= list[i-1] {
			break
		}
		list[i], list[i-1] = list[i-1], list[i]
	}
}

func (p *MemPostings) PostingsForLabelMatching(ctx context.Context, name string, match func(string) bool) Postings {
	// We'll copy the values into a slice and then match over that,
	// this way we don't need to hold the mutex while we're matching,
	// which can be slow (seconds) if the match function is a huge regex.
	// Holding this lock prevents new series from being added (slows down the write path)
	// and blocks the compaction process.
	vals := p.labelValues(name)
	for i, count := 0, 1; i < len(vals); count++ {
		if count%checkContextEveryNIterations == 0 && ctx.Err() != nil {
			return ErrPostings(ctx.Err())
		}

		if match(vals[i]) {
			i++
			continue
		}

		// Didn't match, bring the last value to this position, make the slice shorter and check again.
		// The order of the slice doesn't matter as it comes from a map iteration.
		vals[i], vals = vals[len(vals)-1], vals[:len(vals)-1]
	}

	// If none matched (or this label had no values), no need to grab the lock again.
	if len(vals) == 0 {
		return EmptyPostings()
	}