mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-21 14:44:00 +00:00
lib/storage: remove inmemory inverted index for recent hours
Production load with >10M active time series showed it could slow down VictoriaMetrics startup times and could eat all the memory leading to OOM. Remove inmemory inverted index for recent hours until thorough testing on production data shows it works OK.
This commit is contained in:
parent
66c4961ff8
commit
01bb3c06c7
7 changed files with 4 additions and 307 deletions
|
@ -407,22 +407,6 @@ func registerStorageMetrics() {
|
|||
return float64(idbm().ItemsCount)
|
||||
})
|
||||
|
||||
metrics.NewGauge(`vm_recent_hour_inverted_index_entries`, func() float64 {
|
||||
return float64(m().RecentHourInvertedIndexSize)
|
||||
})
|
||||
metrics.NewGauge(`vm_recent_hour_inverted_index_unique_tag_pairs`, func() float64 {
|
||||
return float64(m().RecentHourInvertedIndexUniqueTagPairsSize)
|
||||
})
|
||||
metrics.NewGauge(`vm_recent_hour_inverted_index_pending_metric_ids`, func() float64 {
|
||||
return float64(m().RecentHourInvertedIndexPendingMetricIDsSize)
|
||||
})
|
||||
metrics.NewGauge(`vm_recent_hour_inverted_index_search_calls_total`, func() float64 {
|
||||
return float64(idbm().RecentHourInvertedIndexSearchCalls)
|
||||
})
|
||||
metrics.NewGauge(`vm_recent_hour_inverted_index_search_hits_total`, func() float64 {
|
||||
return float64(idbm().RecentHourInvertedIndexSearchHits)
|
||||
})
|
||||
|
||||
metrics.NewGauge(`vm_date_range_search_calls_total`, func() float64 {
|
||||
return float64(idbm().DateRangeSearchCalls)
|
||||
})
|
||||
|
|
|
@ -95,12 +95,6 @@ type indexDB struct {
|
|||
// The number of successful searches for metric ids by days.
|
||||
dateMetricIDsSearchHits uint64
|
||||
|
||||
// The number of calls for recent hour searches over inverted index.
|
||||
recentHourInvertedIndexSearchCalls uint64
|
||||
|
||||
// The number of hits for recent hour searches over inverted index.
|
||||
recentHourInvertedIndexSearchHits uint64
|
||||
|
||||
// The number of calls for date range searches.
|
||||
dateRangeSearchCalls uint64
|
||||
|
||||
|
@ -231,9 +225,6 @@ type IndexDBMetrics struct {
|
|||
DateMetricIDsSearchCalls uint64
|
||||
DateMetricIDsSearchHits uint64
|
||||
|
||||
RecentHourInvertedIndexSearchCalls uint64
|
||||
RecentHourInvertedIndexSearchHits uint64
|
||||
|
||||
DateRangeSearchCalls uint64
|
||||
DateRangeSearchHits uint64
|
||||
|
||||
|
@ -275,9 +266,6 @@ func (db *indexDB) UpdateMetrics(m *IndexDBMetrics) {
|
|||
m.DateMetricIDsSearchCalls += atomic.LoadUint64(&db.dateMetricIDsSearchCalls)
|
||||
m.DateMetricIDsSearchHits += atomic.LoadUint64(&db.dateMetricIDsSearchHits)
|
||||
|
||||
m.RecentHourInvertedIndexSearchCalls += atomic.LoadUint64(&db.recentHourInvertedIndexSearchCalls)
|
||||
m.RecentHourInvertedIndexSearchHits += atomic.LoadUint64(&db.recentHourInvertedIndexSearchHits)
|
||||
|
||||
m.DateRangeSearchCalls += atomic.LoadUint64(&db.dateRangeSearchCalls)
|
||||
m.DateRangeSearchHits += atomic.LoadUint64(&db.dateRangeSearchHits)
|
||||
|
||||
|
@ -1667,10 +1655,6 @@ func (is *indexSearch) updateMetricIDsForTagFilters(metricIDs *uint64set.Set, tf
|
|||
return bytes.Compare(a.prefix, b.prefix) < 0
|
||||
})
|
||||
|
||||
if is.tryUpdatingMetricIDsForRecentHour(metricIDs, tfs, tr) {
|
||||
// Fast path: found metricIDs in the inmemoryInvertedIndex for the last hour.
|
||||
return nil
|
||||
}
|
||||
ok, err := is.tryUpdatingMetricIDsForDateRange(metricIDs, tfs, tr, maxMetrics)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -2193,34 +2177,6 @@ func (is *indexSearch) getMetricIDsForRecentHours(tr TimeRange, maxMetrics int)
|
|||
return nil, false
|
||||
}
|
||||
|
||||
func (is *indexSearch) tryUpdatingMetricIDsForRecentHour(metricIDs *uint64set.Set, tfs *TagFilters, tr TimeRange) bool {
|
||||
minHour := uint64(tr.MinTimestamp) / msecPerHour
|
||||
maxHour := uint64(tr.MaxTimestamp) / msecPerHour
|
||||
|
||||
hmCurr := is.db.currHourMetricIDs.Load().(*hourMetricIDs)
|
||||
if maxHour == hmCurr.hour && minHour == maxHour && hmCurr.isFull {
|
||||
// The tr fits the current hour.
|
||||
hmCurr.iidx.UpdateMetricIDsForTagFilters(metricIDs, hmCurr.m, tfs)
|
||||
atomic.AddUint64(&is.db.recentHourInvertedIndexSearchHits, 1)
|
||||
return true
|
||||
}
|
||||
hmPrev := is.db.prevHourMetricIDs.Load().(*hourMetricIDs)
|
||||
if maxHour == hmPrev.hour && minHour == maxHour && hmPrev.isFull {
|
||||
// The tr fits the previous hour.
|
||||
hmPrev.iidx.UpdateMetricIDsForTagFilters(metricIDs, hmPrev.m, tfs)
|
||||
atomic.AddUint64(&is.db.recentHourInvertedIndexSearchHits, 1)
|
||||
return true
|
||||
}
|
||||
if maxHour == hmCurr.hour && minHour == hmPrev.hour && hmCurr.isFull && hmPrev.isFull {
|
||||
// The tr spans the previous and the current hours.
|
||||
hmPrev.iidx.UpdateMetricIDsForTagFilters(metricIDs, hmPrev.m, tfs)
|
||||
hmCurr.iidx.UpdateMetricIDsForTagFilters(metricIDs, hmCurr.m, tfs)
|
||||
atomic.AddUint64(&is.db.recentHourInvertedIndexSearchHits, 1)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (db *indexDB) storeDateMetricID(date, metricID uint64) error {
|
||||
is := db.getIndexSearch()
|
||||
ok, err := is.hasDateMetricID(date, metricID)
|
||||
|
|
|
@ -1418,10 +1418,6 @@ func TestSearchTSIDWithTimeRange(t *testing.T) {
|
|||
prevMetricIDs.m.Add(tsids[i].MetricID)
|
||||
currMetricIDs.m.Add(tsids[i].MetricID)
|
||||
}
|
||||
prevMetricIDs.iidx = newInmemoryInvertedIndex()
|
||||
prevMetricIDs.iidx.MustUpdate(db, prevMetricIDs.m)
|
||||
currMetricIDs.iidx = newInmemoryInvertedIndex()
|
||||
currMetricIDs.iidx.MustUpdate(db, currMetricIDs.m)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,199 +0,0 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/uint64set"
|
||||
)
|
||||
|
||||
type inmemoryInvertedIndex struct {
|
||||
mu sync.RWMutex
|
||||
m map[string]*uint64set.Set
|
||||
pendingMetricIDs []uint64
|
||||
}
|
||||
|
||||
func (iidx *inmemoryInvertedIndex) GetUniqueTagPairsLen() int {
|
||||
if iidx == nil {
|
||||
return 0
|
||||
}
|
||||
iidx.mu.RLock()
|
||||
n := len(iidx.m)
|
||||
iidx.mu.RUnlock()
|
||||
return n
|
||||
}
|
||||
|
||||
func (iidx *inmemoryInvertedIndex) GetEntriesCount() int {
|
||||
if iidx == nil {
|
||||
return 0
|
||||
}
|
||||
n := 0
|
||||
iidx.mu.RLock()
|
||||
for _, v := range iidx.m {
|
||||
n += v.Len()
|
||||
}
|
||||
iidx.mu.RUnlock()
|
||||
return n
|
||||
}
|
||||
|
||||
func (iidx *inmemoryInvertedIndex) GetPendingMetricIDsLen() int {
|
||||
if iidx == nil {
|
||||
return 0
|
||||
}
|
||||
iidx.mu.RLock()
|
||||
n := len(iidx.pendingMetricIDs)
|
||||
iidx.mu.RUnlock()
|
||||
return n
|
||||
}
|
||||
|
||||
func newInmemoryInvertedIndex() *inmemoryInvertedIndex {
|
||||
return &inmemoryInvertedIndex{
|
||||
m: make(map[string]*uint64set.Set),
|
||||
}
|
||||
}
|
||||
|
||||
func (iidx *inmemoryInvertedIndex) Clone() *inmemoryInvertedIndex {
|
||||
if iidx == nil {
|
||||
return newInmemoryInvertedIndex()
|
||||
}
|
||||
iidx.mu.RLock()
|
||||
mCopy := make(map[string]*uint64set.Set, len(iidx.m))
|
||||
for k, v := range iidx.m {
|
||||
mCopy[k] = v.Clone()
|
||||
}
|
||||
pendingMetricIDs := append([]uint64{}, iidx.pendingMetricIDs...)
|
||||
iidx.mu.RUnlock()
|
||||
return &inmemoryInvertedIndex{
|
||||
m: mCopy,
|
||||
pendingMetricIDs: pendingMetricIDs,
|
||||
}
|
||||
}
|
||||
|
||||
func (iidx *inmemoryInvertedIndex) MustUpdate(idb *indexDB, src *uint64set.Set) {
|
||||
metricIDs := src.AppendTo(nil)
|
||||
iidx.mu.Lock()
|
||||
iidx.pendingMetricIDs = append(iidx.pendingMetricIDs, metricIDs...)
|
||||
if err := iidx.addPendingEntriesLocked(idb); err != nil {
|
||||
logger.Panicf("FATAL: cannot update inmemoryInvertedIndex with pendingMetricIDs: %s", err)
|
||||
}
|
||||
iidx.mu.Unlock()
|
||||
}
|
||||
|
||||
func (iidx *inmemoryInvertedIndex) AddMetricID(idb *indexDB, metricID uint64) {
|
||||
iidx.mu.Lock()
|
||||
iidx.pendingMetricIDs = append(iidx.pendingMetricIDs, metricID)
|
||||
if err := iidx.addPendingEntriesLocked(idb); err != nil {
|
||||
logger.Panicf("FATAL: cannot update inmemoryInvertedIndex with pendingMetricIDs: %s", err)
|
||||
}
|
||||
iidx.mu.Unlock()
|
||||
}
|
||||
|
||||
func (iidx *inmemoryInvertedIndex) UpdateMetricIDsForTagFilters(metricIDs, allMetricIDs *uint64set.Set, tfs *TagFilters) {
|
||||
if iidx == nil {
|
||||
return
|
||||
}
|
||||
var result *uint64set.Set
|
||||
var tfFirst *tagFilter
|
||||
for i := range tfs.tfs {
|
||||
if tfs.tfs[i].isNegative {
|
||||
continue
|
||||
}
|
||||
tfFirst = &tfs.tfs[i]
|
||||
break
|
||||
}
|
||||
|
||||
iidx.mu.RLock()
|
||||
defer iidx.mu.RUnlock()
|
||||
|
||||
if tfFirst == nil {
|
||||
result = allMetricIDs.Clone()
|
||||
} else {
|
||||
result = iidx.getMetricIDsForTagFilterLocked(tfFirst, tfs.commonPrefix)
|
||||
}
|
||||
for i := range tfs.tfs {
|
||||
tf := &tfs.tfs[i]
|
||||
if tf == tfFirst {
|
||||
continue
|
||||
}
|
||||
m := iidx.getMetricIDsForTagFilterLocked(tf, tfs.commonPrefix)
|
||||
if tf.isNegative {
|
||||
result.Subtract(m)
|
||||
} else {
|
||||
result.Intersect(m)
|
||||
}
|
||||
if result.Len() == 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
metricIDs.Union(result)
|
||||
}
|
||||
|
||||
func (iidx *inmemoryInvertedIndex) getMetricIDsForTagFilterLocked(tf *tagFilter, commonPrefix []byte) *uint64set.Set {
|
||||
if !bytes.HasPrefix(tf.prefix, commonPrefix) {
|
||||
logger.Panicf("BUG: tf.prefix must start with commonPrefix=%q; got %q", commonPrefix, tf.prefix)
|
||||
}
|
||||
prefix := tf.prefix[len(commonPrefix):]
|
||||
var m uint64set.Set
|
||||
kb := kbPool.Get()
|
||||
defer kbPool.Put(kb)
|
||||
for k, v := range iidx.m {
|
||||
if len(k) < len(prefix) || k[:len(prefix)] != string(prefix) {
|
||||
continue
|
||||
}
|
||||
kb.B = append(kb.B[:0], k[len(prefix):]...)
|
||||
ok, err := tf.matchSuffix(kb.B)
|
||||
if err != nil {
|
||||
logger.Panicf("BUG: unexpected error from matchSuffix(%q): %s", kb.B, err)
|
||||
}
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
m.Union(v)
|
||||
}
|
||||
return &m
|
||||
}
|
||||
|
||||
func (iidx *inmemoryInvertedIndex) addPendingEntriesLocked(idb *indexDB) error {
|
||||
metricIDs := iidx.pendingMetricIDs
|
||||
iidx.pendingMetricIDs = iidx.pendingMetricIDs[:0]
|
||||
|
||||
kb := kbPool.Get()
|
||||
defer kbPool.Put(kb)
|
||||
|
||||
mn := GetMetricName()
|
||||
defer PutMetricName(mn)
|
||||
for _, metricID := range metricIDs {
|
||||
var err error
|
||||
kb.B, err = idb.searchMetricName(kb.B[:0], metricID)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
iidx.pendingMetricIDs = append(iidx.pendingMetricIDs, metricID)
|
||||
continue
|
||||
}
|
||||
return fmt.Errorf("cannot find metricName by metricID %d: %s", metricID, err)
|
||||
}
|
||||
if err = mn.Unmarshal(kb.B); err != nil {
|
||||
return fmt.Errorf("cannot unmarshal metricName %q: %s", kb.B, err)
|
||||
}
|
||||
kb.B = marshalTagValue(kb.B[:0], nil)
|
||||
kb.B = marshalTagValue(kb.B, mn.MetricGroup)
|
||||
iidx.addMetricIDLocked(kb.B, metricID)
|
||||
for i := range mn.Tags {
|
||||
kb.B = mn.Tags[i].Marshal(kb.B[:0])
|
||||
iidx.addMetricIDLocked(kb.B, metricID)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (iidx *inmemoryInvertedIndex) addMetricIDLocked(key []byte, metricID uint64) {
|
||||
v := iidx.m[string(key)]
|
||||
if v == nil {
|
||||
v = &uint64set.Set{}
|
||||
iidx.m[string(key)] = v
|
||||
}
|
||||
v.Add(metricID)
|
||||
}
|
|
@ -72,21 +72,15 @@ func TestSearchQueryMarshalUnmarshal(t *testing.T) {
|
|||
|
||||
func TestSearch(t *testing.T) {
|
||||
t.Run("global_inverted_index", func(t *testing.T) {
|
||||
testSearchGeneric(t, false, false)
|
||||
testSearchGeneric(t, false)
|
||||
})
|
||||
t.Run("perday_inverted_index", func(t *testing.T) {
|
||||
testSearchGeneric(t, false, true)
|
||||
})
|
||||
t.Run("recent_hour_global_inverted_index", func(t *testing.T) {
|
||||
testSearchGeneric(t, true, false)
|
||||
})
|
||||
t.Run("recent_hour_perday_inverted_index", func(t *testing.T) {
|
||||
testSearchGeneric(t, true, true)
|
||||
testSearchGeneric(t, true)
|
||||
})
|
||||
}
|
||||
|
||||
func testSearchGeneric(t *testing.T, forceRecentHourInvertedIndex, forcePerDayInvertedIndex bool) {
|
||||
path := fmt.Sprintf("TestSearch_%v_%v", forceRecentHourInvertedIndex, forcePerDayInvertedIndex)
|
||||
func testSearchGeneric(t *testing.T, forcePerDayInvertedIndex bool) {
|
||||
path := fmt.Sprintf("TestSearch_%v", forcePerDayInvertedIndex)
|
||||
st, err := OpenStorage(path, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot open storage %q: %s", path, err)
|
||||
|
@ -147,10 +141,6 @@ func testSearchGeneric(t *testing.T, forceRecentHourInvertedIndex, forcePerDayIn
|
|||
extDB.startDateForPerDayInvertedIndex = 0
|
||||
})
|
||||
}
|
||||
if forceRecentHourInvertedIndex {
|
||||
hm := st.currHourMetricIDs.Load().(*hourMetricIDs)
|
||||
hm.isFull = true
|
||||
}
|
||||
|
||||
// Run search.
|
||||
tr := TimeRange{
|
||||
|
|
|
@ -140,13 +140,6 @@ func OpenStorage(path string, retentionMonths int) (*Storage, error) {
|
|||
idbCurr.SetExtDB(idbPrev)
|
||||
s.idbCurr.Store(idbCurr)
|
||||
|
||||
// Initialize iidx. hmCurr and hmPrev shouldn't be used till now,
|
||||
// so it should be safe initializing it inplace.
|
||||
hmPrev.iidx = newInmemoryInvertedIndex()
|
||||
hmPrev.iidx.MustUpdate(s.idb(), hmPrev.m)
|
||||
hmCurr.iidx = newInmemoryInvertedIndex()
|
||||
hmCurr.iidx.MustUpdate(s.idb(), hmCurr.m)
|
||||
|
||||
// Load data
|
||||
tablePath := path + "/data"
|
||||
tb, err := openTable(tablePath, retentionMonths, s.getDeletedMetricIDs)
|
||||
|
@ -318,10 +311,6 @@ type Metrics struct {
|
|||
|
||||
HourMetricIDCacheSize uint64
|
||||
|
||||
RecentHourInvertedIndexSize uint64
|
||||
RecentHourInvertedIndexUniqueTagPairsSize uint64
|
||||
RecentHourInvertedIndexPendingMetricIDsSize uint64
|
||||
|
||||
IndexDBMetrics IndexDBMetrics
|
||||
TableMetrics TableMetrics
|
||||
}
|
||||
|
@ -378,15 +367,6 @@ func (s *Storage) UpdateMetrics(m *Metrics) {
|
|||
}
|
||||
m.HourMetricIDCacheSize += uint64(hourMetricIDsLen)
|
||||
|
||||
m.RecentHourInvertedIndexSize += uint64(hmPrev.iidx.GetEntriesCount())
|
||||
m.RecentHourInvertedIndexSize += uint64(hmCurr.iidx.GetEntriesCount())
|
||||
|
||||
m.RecentHourInvertedIndexUniqueTagPairsSize += uint64(hmPrev.iidx.GetUniqueTagPairsLen())
|
||||
m.RecentHourInvertedIndexUniqueTagPairsSize += uint64(hmCurr.iidx.GetUniqueTagPairsLen())
|
||||
|
||||
m.RecentHourInvertedIndexPendingMetricIDsSize += uint64(hmPrev.iidx.GetPendingMetricIDsLen())
|
||||
m.RecentHourInvertedIndexPendingMetricIDsSize += uint64(hmCurr.iidx.GetPendingMetricIDsLen())
|
||||
|
||||
s.idb().UpdateMetrics(&m.IndexDBMetrics)
|
||||
s.tb.UpdateMetrics(&m.TableMetrics)
|
||||
}
|
||||
|
@ -920,7 +900,6 @@ func (s *Storage) updatePerDateData(rows []rawRow, lastError error) error {
|
|||
s.pendingHourEntriesLock.Lock()
|
||||
s.pendingHourEntries.Add(metricID)
|
||||
s.pendingHourEntriesLock.Unlock()
|
||||
hm.iidx.AddMetricID(idb, metricID)
|
||||
}
|
||||
|
||||
// Slower path: check global cache for (date, metricID) entry.
|
||||
|
@ -1098,20 +1077,16 @@ func (s *Storage) updateCurrHourMetricIDs() {
|
|||
|
||||
// Slow path: hm.m must be updated with non-empty s.pendingHourEntries.
|
||||
var m *uint64set.Set
|
||||
var iidx *inmemoryInvertedIndex
|
||||
isFull := hm.isFull
|
||||
if hm.hour == hour {
|
||||
m = hm.m.Clone()
|
||||
iidx = hm.iidx.Clone()
|
||||
} else {
|
||||
m = &uint64set.Set{}
|
||||
iidx = newInmemoryInvertedIndex()
|
||||
isFull = true
|
||||
}
|
||||
m.Union(newMetricIDs)
|
||||
hmNew := &hourMetricIDs{
|
||||
m: m,
|
||||
iidx: iidx,
|
||||
hour: hour,
|
||||
isFull: isFull,
|
||||
}
|
||||
|
@ -1123,7 +1098,6 @@ func (s *Storage) updateCurrHourMetricIDs() {
|
|||
|
||||
type hourMetricIDs struct {
|
||||
m *uint64set.Set
|
||||
iidx *inmemoryInvertedIndex
|
||||
hour uint64
|
||||
isFull bool
|
||||
}
|
||||
|
|
|
@ -108,7 +108,6 @@ func TestUpdateCurrHourMetricIDs(t *testing.T) {
|
|||
hour := uint64(timestampFromTime(time.Now())) / msecPerHour
|
||||
hmOrig := &hourMetricIDs{
|
||||
m: &uint64set.Set{},
|
||||
iidx: newInmemoryInvertedIndex(),
|
||||
hour: 123,
|
||||
}
|
||||
hmOrig.m.Add(12)
|
||||
|
@ -144,7 +143,6 @@ func TestUpdateCurrHourMetricIDs(t *testing.T) {
|
|||
hour := uint64(timestampFromTime(time.Now())) / msecPerHour
|
||||
hmOrig := &hourMetricIDs{
|
||||
m: &uint64set.Set{},
|
||||
iidx: newInmemoryInvertedIndex(),
|
||||
hour: hour,
|
||||
}
|
||||
hmOrig.m.Add(12)
|
||||
|
@ -189,7 +187,6 @@ func TestUpdateCurrHourMetricIDs(t *testing.T) {
|
|||
hour := uint64(timestampFromTime(time.Now())) / msecPerHour
|
||||
hmOrig := &hourMetricIDs{
|
||||
m: &uint64set.Set{},
|
||||
iidx: newInmemoryInvertedIndex(),
|
||||
hour: 123,
|
||||
}
|
||||
hmOrig.m.Add(12)
|
||||
|
@ -231,7 +228,6 @@ func TestUpdateCurrHourMetricIDs(t *testing.T) {
|
|||
hour := uint64(timestampFromTime(time.Now())) / msecPerHour
|
||||
hmOrig := &hourMetricIDs{
|
||||
m: &uint64set.Set{},
|
||||
iidx: newInmemoryInvertedIndex(),
|
||||
hour: hour,
|
||||
}
|
||||
hmOrig.m.Add(12)
|
||||
|
|
Loading…
Reference in a new issue