Files
metrics/lib/storage/metric_name_search.go
6296504d5d lib/storage: implement partition index (#8134)
This should reduce disk space occupied by indexDBs as they get deleted along
with the corresponding partitions once those partitions become outside the
retention window.

- Motivation: https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7599
- What to expect: https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8134

Signed-off-by: Artem Fetishev <[email protected]>
Co-authored-by: Andrei Baidarov <[email protected]>
2025-12-24 18:53:49 +01:00

119 lines
3.5 KiB
Go

package storage
import (
"sync"
)
// metricNameSearch is used for searching a metricName by a metricID in
// partition and legacy indexDBs. If useSparseCache is false the name is first
// searched in metricNameCache and also stored in that cache when found in one
// of the indexDBs.
//
// Most index search methods invoked only once per API call. For example, one
// request to /api/v1/series results in one invocation of
// Storage.SearchMetricNames() method. However, searching a metricName by
// metricID is done multiple times per API call. For example, data search
// performs the metricName search for each data block (see search.go).
//
// Additionally, each search method must get a snapshot of idbs so that they
// don't get rotated in the middle of the search. This works for methods that
// are invoked only once per API call. But for searching metricName by metricID
// happens many times per API call and the idb snapshot must be taken
// outside the search method.
//
// To address this, the state of the metricNameSearch type holds the search
// context: the idb snapshot along with other options. This not only ensures
// that idbs won't change during the API call but also avoids performance
// degradation that may be caused by getting the idb snapshot every time the
// search method is invoked (due do mutex locks).
type metricNameSearch struct {
storage *Storage
ptws []*partitionWrapper
idbs []*indexDB
legacyIDBs *legacyIndexDBs
useSparseCache bool
}
// search searches the metricName of a metricID.
func (s *metricNameSearch) search(dst []byte, metricID uint64) ([]byte, bool) {
if !s.useSparseCache {
n := len(dst)
dst = s.storage.getMetricNameFromCache(dst, metricID)
if len(dst) > n {
return dst, true
}
}
var found bool
// This will be just one idb most of the time since a typical time range
// fits within a single month.
for _, idb := range s.idbs {
dst, found = idb.searchMetricName(dst, metricID, s.useSparseCache)
if found {
if !s.useSparseCache {
s.storage.putMetricNameToCache(metricID, dst)
}
return dst, true
}
}
// Fallback to current legacy indexDB.
if idb := s.legacyIDBs.getIDBCurr(); idb != nil {
dst, found = idb.searchMetricName(dst, metricID, s.useSparseCache)
if found {
if !s.useSparseCache {
s.storage.putMetricNameToCache(metricID, dst)
}
return dst, true
}
}
// Fallback to previous legacy indexDB.
if idb := s.legacyIDBs.getIDBPrev(); idb != nil {
dst, found = idb.searchMetricName(dst, metricID, s.useSparseCache)
if found {
if !s.useSparseCache {
s.storage.putMetricNameToCache(metricID, dst)
}
return dst, true
}
}
// Not deleting metricID if no corresponding metricName has been found
// because it is not known which indexDB metricID belongs to.
// For cases when this does happen see indexDB.SearchMetricNames() and
// indexDB.SearchTSIDs().
return dst, false
}
var mnsPool = &sync.Pool{
New: func() any {
return &metricNameSearch{}
},
}
func getMetricNameSearch(storage *Storage, tr TimeRange, useSparseCache bool) *metricNameSearch {
s := mnsPool.Get().(*metricNameSearch)
s.storage = storage
s.ptws = storage.tb.GetPartitions(tr)
for _, ptw := range s.ptws {
s.idbs = append(s.idbs, ptw.pt.idb)
}
s.legacyIDBs = storage.getLegacyIndexDBs()
s.useSparseCache = useSparseCache
return s
}
func putMetricNameSearch(s *metricNameSearch) {
s.storage.tb.PutPartitions(s.ptws)
s.storage.putLegacyIndexDBs(s.legacyIDBs)
s.storage = nil
s.ptws = nil
s.idbs = nil
s.legacyIDBs = nil
s.useSparseCache = false
mnsPool.Put(s)
}