2019-05-22 21:16:55 +00:00
|
|
|
package storage
|
|
|
|
|
|
|
|
import (
|
2021-03-18 12:52:49 +00:00
|
|
|
"bytes"
|
2019-05-22 21:16:55 +00:00
|
|
|
"fmt"
|
2020-01-30 22:54:28 +00:00
|
|
|
"io"
|
2019-05-22 21:16:55 +00:00
|
|
|
"math"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"regexp"
|
|
|
|
"sort"
|
2021-02-02 22:24:05 +00:00
|
|
|
"strings"
|
2019-05-22 21:16:55 +00:00
|
|
|
"sync"
|
|
|
|
"sync/atomic"
|
|
|
|
"time"
|
|
|
|
"unsafe"
|
|
|
|
|
2023-03-25 21:33:54 +00:00
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/backupnames"
|
2021-05-20 11:15:19 +00:00
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bloomfilter"
|
2022-07-05 20:47:46 +00:00
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
2021-08-13 09:10:00 +00:00
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/decimal"
|
2019-05-22 21:16:55 +00:00
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding"
|
2020-05-14 19:01:51 +00:00
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
|
2019-05-22 21:16:55 +00:00
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/memory"
|
2022-05-31 23:29:19 +00:00
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/querytracer"
|
2024-02-09 02:03:20 +00:00
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/snapshot/snapshotutil"
|
2024-01-22 16:12:37 +00:00
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timeutil"
|
2019-09-24 18:10:22 +00:00
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/uint64set"
|
2019-08-13 18:35:19 +00:00
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/workingsetcache"
|
2019-05-22 21:16:55 +00:00
|
|
|
"github.com/VictoriaMetrics/fastcache"
|
2023-01-10 05:43:04 +00:00
|
|
|
"github.com/VictoriaMetrics/metricsql"
|
2019-05-22 21:16:55 +00:00
|
|
|
)
|
|
|
|
|
2020-10-20 11:29:26 +00:00
|
|
|
const (
|
2023-09-03 08:33:37 +00:00
|
|
|
retention31Days = 31 * 24 * time.Hour
|
|
|
|
retentionMax = 100 * 12 * retention31Days
|
2020-10-20 11:29:26 +00:00
|
|
|
)
|
2019-05-22 21:16:55 +00:00
|
|
|
|
|
|
|
// Storage represents TSDB storage.
|
|
|
|
type Storage struct {
|
2024-09-06 15:57:21 +00:00
|
|
|
rowsReceivedTotal atomic.Uint64
|
|
|
|
rowsAddedTotal atomic.Uint64
|
|
|
|
naNValueRows atomic.Uint64
|
2024-08-27 19:30:37 +00:00
|
|
|
|
2024-02-23 22:15:21 +00:00
|
|
|
tooSmallTimestampRows atomic.Uint64
|
|
|
|
tooBigTimestampRows atomic.Uint64
|
2024-09-06 15:57:21 +00:00
|
|
|
invalidRawMetricNames atomic.Uint64
|
2019-10-17 15:22:56 +00:00
|
|
|
|
2024-02-23 22:15:21 +00:00
|
|
|
timeseriesRepopulated atomic.Uint64
|
|
|
|
timeseriesPreCreated atomic.Uint64
|
|
|
|
newTimeseriesCreated atomic.Uint64
|
|
|
|
slowRowInserts atomic.Uint64
|
|
|
|
slowPerDayIndexInserts atomic.Uint64
|
|
|
|
slowMetricNameLoads atomic.Uint64
|
2020-05-15 10:44:23 +00:00
|
|
|
|
2024-02-23 22:15:21 +00:00
|
|
|
hourlySeriesLimitRowsDropped atomic.Uint64
|
|
|
|
dailySeriesLimitRowsDropped atomic.Uint64
|
2021-05-20 11:15:19 +00:00
|
|
|
|
2023-07-22 22:20:21 +00:00
|
|
|
// nextRotationTimestamp is a timestamp in seconds of the next indexdb rotation.
|
|
|
|
//
|
|
|
|
// It is used for gradual pre-population of the idbNext during the last hour before the indexdb rotation.
|
|
|
|
// in order to reduce spikes in CPU and disk IO usage just after the rotiation.
|
|
|
|
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401
|
2024-02-23 22:15:21 +00:00
|
|
|
nextRotationTimestamp atomic.Int64
|
2023-07-22 22:20:21 +00:00
|
|
|
|
2020-10-20 13:10:46 +00:00
|
|
|
path string
|
|
|
|
cachePath string
|
|
|
|
retentionMsecs int64
|
2019-05-22 21:16:55 +00:00
|
|
|
|
2019-05-25 18:51:11 +00:00
|
|
|
// lock file for exclusive access to the storage on the given path.
|
2019-05-22 21:16:55 +00:00
|
|
|
flockF *os.File
|
|
|
|
|
2023-07-22 22:20:21 +00:00
|
|
|
// idbCurr contains the currently used indexdb.
|
2023-07-20 00:37:49 +00:00
|
|
|
idbCurr atomic.Pointer[indexDB]
|
2019-05-22 21:16:55 +00:00
|
|
|
|
2023-07-22 22:20:21 +00:00
|
|
|
// idbNext is the next indexdb, which will become idbCurr at the next rotation.
|
|
|
|
//
|
|
|
|
// It is started to be gradually pre-populated with the data for active time series during the last hour
|
|
|
|
// before nextRotationTimestamp.
|
|
|
|
// This reduces spikes in CPU and disk IO usage just after the rotiation.
|
|
|
|
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401
|
|
|
|
idbNext atomic.Pointer[indexDB]
|
|
|
|
|
2019-05-22 21:16:55 +00:00
|
|
|
tb *table
|
|
|
|
|
2021-05-20 11:15:19 +00:00
|
|
|
// Series cardinality limiters.
|
|
|
|
hourlySeriesLimiter *bloomfilter.Limiter
|
|
|
|
dailySeriesLimiter *bloomfilter.Limiter
|
|
|
|
|
2019-05-22 21:16:55 +00:00
|
|
|
// tsidCache is MetricName -> TSID cache.
|
2019-08-13 18:35:19 +00:00
|
|
|
tsidCache *workingsetcache.Cache
|
2019-05-22 21:16:55 +00:00
|
|
|
|
|
|
|
// metricIDCache is MetricID -> TSID cache.
|
2019-08-13 18:35:19 +00:00
|
|
|
metricIDCache *workingsetcache.Cache
|
2019-05-22 21:16:55 +00:00
|
|
|
|
|
|
|
// metricNameCache is MetricID -> MetricName cache.
|
2019-08-13 18:35:19 +00:00
|
|
|
metricNameCache *workingsetcache.Cache
|
2019-05-22 21:16:55 +00:00
|
|
|
|
2023-07-22 22:20:21 +00:00
|
|
|
// dateMetricIDCache is (generation, Date, MetricID) cache, where generation is the indexdb generation.
|
|
|
|
// See generationTSID for details.
|
2019-11-09 21:05:14 +00:00
|
|
|
dateMetricIDCache *dateMetricIDCache
|
2019-05-22 21:16:55 +00:00
|
|
|
|
2019-11-11 22:16:42 +00:00
|
|
|
// Fast cache for MetricID values occurred during the current hour.
|
2023-07-20 00:37:49 +00:00
|
|
|
currHourMetricIDs atomic.Pointer[hourMetricIDs]
|
2019-06-09 16:06:53 +00:00
|
|
|
|
2019-11-11 22:16:42 +00:00
|
|
|
// Fast cache for MetricID values occurred during the previous hour.
|
2023-07-20 00:37:49 +00:00
|
|
|
prevHourMetricIDs atomic.Pointer[hourMetricIDs]
|
2019-06-09 16:06:53 +00:00
|
|
|
|
2020-05-11 22:06:17 +00:00
|
|
|
// Fast cache for pre-populating per-day inverted index for the next day.
|
|
|
|
// This is needed in order to remove CPU usage spikes at 00:00 UTC
|
|
|
|
// due to creation of per-day inverted index for active time series.
|
|
|
|
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/430 for details.
|
2023-07-20 00:37:49 +00:00
|
|
|
nextDayMetricIDs atomic.Pointer[byDateMetricIDEntry]
|
2020-05-11 22:06:17 +00:00
|
|
|
|
2019-06-09 16:06:53 +00:00
|
|
|
// Pending MetricID values to be added to currHourMetricIDs.
|
2019-11-08 17:37:16 +00:00
|
|
|
pendingHourEntriesLock sync.Mutex
|
2022-11-07 12:04:06 +00:00
|
|
|
pendingHourEntries *uint64set.Set
|
2019-06-02 15:34:08 +00:00
|
|
|
|
2020-05-11 22:06:17 +00:00
|
|
|
// Pending MetricIDs to be added to nextDayMetricIDs.
|
|
|
|
pendingNextDayMetricIDsLock sync.Mutex
|
|
|
|
pendingNextDayMetricIDs *uint64set.Set
|
|
|
|
|
2021-07-07 07:27:47 +00:00
|
|
|
// prefetchedMetricIDs contains metricIDs for pre-fetched metricNames in the prefetchMetricNames function.
|
2024-01-16 13:26:08 +00:00
|
|
|
prefetchedMetricIDsLock sync.Mutex
|
|
|
|
prefetchedMetricIDs *uint64set.Set
|
2020-01-29 23:59:43 +00:00
|
|
|
|
2021-07-07 07:27:47 +00:00
|
|
|
// prefetchedMetricIDsDeadline is used for periodic reset of prefetchedMetricIDs in order to limit its size under high rate of creating new series.
|
2024-02-23 22:15:21 +00:00
|
|
|
prefetchedMetricIDsDeadline atomic.Uint64
|
2021-07-07 07:27:47 +00:00
|
|
|
|
2024-04-02 18:24:57 +00:00
|
|
|
stopCh chan struct{}
|
2019-06-02 15:34:08 +00:00
|
|
|
|
2020-08-06 13:48:21 +00:00
|
|
|
currHourMetricIDsUpdaterWG sync.WaitGroup
|
|
|
|
nextDayMetricIDsUpdaterWG sync.WaitGroup
|
|
|
|
retentionWatcherWG sync.WaitGroup
|
2021-10-08 11:15:52 +00:00
|
|
|
freeDiskSpaceWatcherWG sync.WaitGroup
|
2020-03-24 20:24:54 +00:00
|
|
|
|
|
|
|
// The snapshotLock prevents from concurrent creation of snapshots,
|
|
|
|
// since this may result in snapshots without recently added data,
|
|
|
|
// which may be in the process of flushing to disk by concurrently running
|
|
|
|
// snapshot process.
|
|
|
|
snapshotLock sync.Mutex
|
2021-02-10 12:37:14 +00:00
|
|
|
|
|
|
|
// The minimum timestamp when composite index search can be used.
|
|
|
|
minTimestampForCompositeIndex int64
|
2021-06-15 11:56:51 +00:00
|
|
|
|
|
|
|
// An inmemory set of deleted metricIDs.
|
|
|
|
//
|
|
|
|
// It is safe to keep the set in memory even for big number of deleted
|
|
|
|
// metricIDs, since it usually requires 1 bit per deleted metricID.
|
2023-07-20 00:37:49 +00:00
|
|
|
deletedMetricIDs atomic.Pointer[uint64set.Set]
|
2021-06-15 11:56:51 +00:00
|
|
|
deletedMetricIDsUpdateLock sync.Mutex
|
2021-10-08 16:34:38 +00:00
|
|
|
|
2024-03-27 08:51:03 +00:00
|
|
|
// missingMetricIDs maps metricID to the deadline in unix timestamp seconds
|
|
|
|
// after which all the indexdb entries for the given metricID
|
2024-09-16 08:05:08 +00:00
|
|
|
// must be deleted if index entry isn't found by the given metricID.
|
|
|
|
// This is used inside searchMetricNameWithCache() and getTSIDsFromMetricIDs()
|
|
|
|
// for detecting permanently missing metricID->metricName/TSID entries.
|
2024-03-27 08:51:03 +00:00
|
|
|
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5959
|
2024-03-17 22:19:17 +00:00
|
|
|
missingMetricIDsLock sync.Mutex
|
|
|
|
missingMetricIDs map[uint64]uint64
|
|
|
|
missingMetricIDsResetDeadline uint64
|
|
|
|
|
2024-02-23 21:29:23 +00:00
|
|
|
// isReadOnly is set to true when the storage is in read-only mode.
|
|
|
|
isReadOnly atomic.Bool
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
|
|
|
|
2023-04-15 06:01:20 +00:00
|
|
|
// MustOpenStorage opens storage on the given path with the given retentionMsecs.
|
2023-09-01 07:27:51 +00:00
|
|
|
func MustOpenStorage(path string, retention time.Duration, maxHourlySeries, maxDailySeries int) *Storage {
|
2019-05-22 21:16:55 +00:00
|
|
|
path, err := filepath.Abs(path)
|
|
|
|
if err != nil {
|
2023-04-15 06:01:20 +00:00
|
|
|
logger.Panicf("FATAL: cannot determine absolute path for %q: %s", path, err)
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
2023-09-01 07:27:51 +00:00
|
|
|
if retention <= 0 || retention > retentionMax {
|
|
|
|
retention = retentionMax
|
2021-02-15 12:30:12 +00:00
|
|
|
}
|
2019-05-22 21:16:55 +00:00
|
|
|
s := &Storage{
|
2020-10-20 13:10:46 +00:00
|
|
|
path: path,
|
2023-03-25 21:33:54 +00:00
|
|
|
cachePath: filepath.Join(path, cacheDirname),
|
2023-09-01 07:27:51 +00:00
|
|
|
retentionMsecs: retention.Milliseconds(),
|
2024-04-02 18:24:57 +00:00
|
|
|
stopCh: make(chan struct{}),
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
2023-04-14 05:11:56 +00:00
|
|
|
fs.MustMkdirIfNotExist(path)
|
2019-05-22 21:16:55 +00:00
|
|
|
|
2021-07-13 14:58:21 +00:00
|
|
|
// Check whether the cache directory must be removed
|
2023-03-25 21:33:54 +00:00
|
|
|
// It is removed if it contains resetCacheOnStartupFilename.
|
2021-07-13 14:58:21 +00:00
|
|
|
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1447 for details.
|
2023-03-25 21:33:54 +00:00
|
|
|
if fs.IsPathExist(filepath.Join(s.cachePath, resetCacheOnStartupFilename)) {
|
|
|
|
logger.Infof("removing cache directory at %q, since it contains `%s` file...", s.cachePath, resetCacheOnStartupFilename)
|
2023-03-19 08:36:05 +00:00
|
|
|
// Do not use fs.MustRemoveAll() here, since the cache directory may be mounted
|
|
|
|
// to a separate filesystem. In this case the fs.MustRemoveAll() will fail while
|
2022-09-13 10:30:55 +00:00
|
|
|
// trying to remove the mount root.
|
|
|
|
fs.RemoveDirContents(s.cachePath)
|
2021-07-13 14:58:21 +00:00
|
|
|
logger.Infof("cache directory at %q has been successfully removed", s.cachePath)
|
|
|
|
}
|
|
|
|
|
2019-08-12 22:45:22 +00:00
|
|
|
// Protect from concurrent opens.
|
2023-04-15 02:49:54 +00:00
|
|
|
s.flockF = fs.MustCreateFlockFile(path)
|
2019-05-22 21:16:55 +00:00
|
|
|
|
2021-12-22 11:10:15 +00:00
|
|
|
// Check whether restore process finished successfully
|
2023-03-25 21:33:54 +00:00
|
|
|
restoreLockF := filepath.Join(path, backupnames.RestoreInProgressFilename)
|
2021-12-22 11:10:15 +00:00
|
|
|
if fs.IsPathExist(restoreLockF) {
|
2023-04-15 06:01:20 +00:00
|
|
|
logger.Panicf("FATAL: incomplete vmrestore run; run vmrestore again or remove lock file %q", restoreLockF)
|
2021-12-22 11:10:15 +00:00
|
|
|
}
|
|
|
|
|
2021-02-10 12:37:14 +00:00
|
|
|
// Pre-create snapshots directory if it is missing.
|
2023-03-25 21:33:54 +00:00
|
|
|
snapshotsPath := filepath.Join(path, snapshotsDirname)
|
2023-04-14 05:11:56 +00:00
|
|
|
fs.MustMkdirIfNotExist(snapshotsPath)
|
2022-09-13 10:10:33 +00:00
|
|
|
fs.MustRemoveTemporaryDirs(snapshotsPath)
|
2021-02-10 12:37:14 +00:00
|
|
|
|
2021-05-20 11:15:19 +00:00
|
|
|
// Initialize series cardinality limiter.
|
|
|
|
if maxHourlySeries > 0 {
|
|
|
|
s.hourlySeriesLimiter = bloomfilter.NewLimiter(maxHourlySeries, time.Hour)
|
|
|
|
}
|
|
|
|
if maxDailySeries > 0 {
|
|
|
|
s.dailySeriesLimiter = bloomfilter.NewLimiter(maxDailySeries, 24*time.Hour)
|
|
|
|
}
|
|
|
|
|
2019-05-22 21:16:55 +00:00
|
|
|
// Load caches.
|
|
|
|
mem := memory.Allowed()
|
2023-05-16 22:14:18 +00:00
|
|
|
s.tsidCache = s.mustLoadCache("metricName_tsid", getTSIDCacheSize())
|
|
|
|
s.metricIDCache = s.mustLoadCache("metricID_tsid", mem/16)
|
|
|
|
s.metricNameCache = s.mustLoadCache("metricID_metricName", mem/10)
|
2019-11-09 21:05:14 +00:00
|
|
|
s.dateMetricIDCache = newDateMetricIDCache()
|
2019-05-22 21:16:55 +00:00
|
|
|
|
2020-05-14 19:01:51 +00:00
|
|
|
hour := fasttime.UnixHour()
|
2019-06-14 04:52:32 +00:00
|
|
|
hmCurr := s.mustLoadHourMetricIDs(hour, "curr_hour_metric_ids")
|
|
|
|
hmPrev := s.mustLoadHourMetricIDs(hour-1, "prev_hour_metric_ids")
|
|
|
|
s.currHourMetricIDs.Store(hmCurr)
|
|
|
|
s.prevHourMetricIDs.Store(hmPrev)
|
2022-11-07 12:04:06 +00:00
|
|
|
s.pendingHourEntries = &uint64set.Set{}
|
2019-06-14 04:52:32 +00:00
|
|
|
|
2020-05-11 22:06:17 +00:00
|
|
|
s.pendingNextDayMetricIDs = &uint64set.Set{}
|
|
|
|
|
2024-01-16 13:26:08 +00:00
|
|
|
s.prefetchedMetricIDs = &uint64set.Set{}
|
2020-01-29 23:59:43 +00:00
|
|
|
|
2021-02-10 15:55:33 +00:00
|
|
|
// Load metadata
|
2023-03-25 21:33:54 +00:00
|
|
|
metadataDir := filepath.Join(path, metadataDirname)
|
|
|
|
isEmptyDB := !fs.IsPathExist(filepath.Join(path, indexdbDirname))
|
2023-04-14 05:11:56 +00:00
|
|
|
fs.MustMkdirIfNotExist(metadataDir)
|
2021-02-10 15:55:33 +00:00
|
|
|
s.minTimestampForCompositeIndex = mustGetMinTimestampForCompositeIndex(metadataDir, isEmptyDB)
|
|
|
|
|
2019-05-22 21:16:55 +00:00
|
|
|
// Load indexdb
|
2023-03-25 21:33:54 +00:00
|
|
|
idbPath := filepath.Join(path, indexdbDirname)
|
|
|
|
idbSnapshotsPath := filepath.Join(idbPath, snapshotsDirname)
|
2023-04-14 05:11:56 +00:00
|
|
|
fs.MustMkdirIfNotExist(idbSnapshotsPath)
|
2022-09-13 10:10:33 +00:00
|
|
|
fs.MustRemoveTemporaryDirs(idbSnapshotsPath)
|
2023-07-22 22:20:21 +00:00
|
|
|
idbNext, idbCurr, idbPrev := s.mustOpenIndexDBTables(idbPath)
|
|
|
|
|
2019-05-22 21:16:55 +00:00
|
|
|
idbCurr.SetExtDB(idbPrev)
|
2023-07-22 22:20:21 +00:00
|
|
|
idbNext.SetExtDB(idbCurr)
|
|
|
|
|
2019-05-22 21:16:55 +00:00
|
|
|
s.idbCurr.Store(idbCurr)
|
2023-07-22 22:20:21 +00:00
|
|
|
s.idbNext.Store(idbNext)
|
|
|
|
|
|
|
|
// Initialize nextRotationTimestamp
|
2023-09-03 08:33:37 +00:00
|
|
|
nowSecs := int64(fasttime.UnixTimestamp())
|
2023-09-01 07:27:51 +00:00
|
|
|
retentionSecs := retention.Milliseconds() / 1000 // not .Seconds() because unnecessary float64 conversion
|
|
|
|
nextRotationTimestamp := nextRetentionDeadlineSeconds(nowSecs, retentionSecs, retentionTimezoneOffsetSecs)
|
2024-02-23 22:15:21 +00:00
|
|
|
s.nextRotationTimestamp.Store(nextRotationTimestamp)
|
2023-07-22 22:20:21 +00:00
|
|
|
|
|
|
|
// Load nextDayMetricIDs cache
|
|
|
|
date := fasttime.UnixDate()
|
|
|
|
nextDayMetricIDs := s.mustLoadNextDayMetricIDs(idbCurr.generation, date)
|
|
|
|
s.nextDayMetricIDs.Store(nextDayMetricIDs)
|
2019-05-22 21:16:55 +00:00
|
|
|
|
2021-06-15 11:56:51 +00:00
|
|
|
// Load deleted metricIDs from idbCurr and idbPrev
|
|
|
|
dmisCurr, err := idbCurr.loadDeletedMetricIDs()
|
|
|
|
if err != nil {
|
2023-04-15 06:01:20 +00:00
|
|
|
logger.Panicf("FATAL: cannot load deleted metricIDs for the current indexDB at %q: %s", path, err)
|
2021-06-15 11:56:51 +00:00
|
|
|
}
|
|
|
|
dmisPrev, err := idbPrev.loadDeletedMetricIDs()
|
|
|
|
if err != nil {
|
2023-04-15 06:01:20 +00:00
|
|
|
logger.Panicf("FATAL: cannot load deleted metricIDs for the previous indexDB at %q: %s", path, err)
|
2021-06-15 11:56:51 +00:00
|
|
|
}
|
|
|
|
s.setDeletedMetricIDs(dmisCurr)
|
|
|
|
s.updateDeletedMetricIDs(dmisPrev)
|
|
|
|
|
2023-04-01 06:50:27 +00:00
|
|
|
// check for free disk space before opening the table
|
|
|
|
// to prevent unexpected part merges. See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4023
|
|
|
|
s.startFreeDiskSpaceWatcher()
|
|
|
|
|
2019-05-22 21:16:55 +00:00
|
|
|
// Load data
|
2023-03-25 21:33:54 +00:00
|
|
|
tablePath := filepath.Join(path, dataDirname)
|
2023-04-15 06:01:20 +00:00
|
|
|
tb := mustOpenTable(tablePath, s)
|
2019-05-22 21:16:55 +00:00
|
|
|
s.tb = tb
|
|
|
|
|
2019-06-09 16:06:53 +00:00
|
|
|
s.startCurrHourMetricIDsUpdater()
|
2020-05-11 22:06:17 +00:00
|
|
|
s.startNextDayMetricIDsUpdater()
|
2019-05-22 21:16:55 +00:00
|
|
|
s.startRetentionWatcher()
|
|
|
|
|
2023-04-15 06:01:20 +00:00
|
|
|
return s
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
|
|
|
|
2022-02-21 11:50:34 +00:00
|
|
|
var maxTSIDCacheSize int
|
|
|
|
|
2022-06-01 08:07:53 +00:00
|
|
|
// SetTSIDCacheSize overrides the default size of storage/tsid cache
|
2022-02-21 11:50:34 +00:00
|
|
|
func SetTSIDCacheSize(size int) {
|
|
|
|
maxTSIDCacheSize = size
|
|
|
|
}
|
|
|
|
|
|
|
|
func getTSIDCacheSize() int {
|
|
|
|
if maxTSIDCacheSize <= 0 {
|
|
|
|
return int(float64(memory.Allowed()) * 0.37)
|
|
|
|
}
|
|
|
|
return maxTSIDCacheSize
|
|
|
|
}
|
|
|
|
|
2021-06-15 11:56:51 +00:00
|
|
|
func (s *Storage) getDeletedMetricIDs() *uint64set.Set {
|
2023-07-20 00:37:49 +00:00
|
|
|
return s.deletedMetricIDs.Load()
|
2021-06-15 11:56:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Storage) setDeletedMetricIDs(dmis *uint64set.Set) {
|
|
|
|
s.deletedMetricIDs.Store(dmis)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Storage) updateDeletedMetricIDs(metricIDs *uint64set.Set) {
|
|
|
|
s.deletedMetricIDsUpdateLock.Lock()
|
|
|
|
dmisOld := s.getDeletedMetricIDs()
|
|
|
|
dmisNew := dmisOld.Clone()
|
|
|
|
dmisNew.Union(metricIDs)
|
|
|
|
s.setDeletedMetricIDs(dmisNew)
|
|
|
|
s.deletedMetricIDsUpdateLock.Unlock()
|
|
|
|
}
|
|
|
|
|
2023-05-16 18:50:15 +00:00
|
|
|
// DebugFlush makes sure all the recently added data is visible to search.
|
|
|
|
//
|
|
|
|
// Note: this function doesn't store all the in-memory data to disk - it just converts
|
|
|
|
// recently added items to searchable parts, which can be stored either in memory
|
|
|
|
// (if they are quite small) or to persistent disk.
|
|
|
|
//
|
|
|
|
// This function is for debugging and testing purposes only,
|
|
|
|
// since it may slow down data ingestion when used frequently.
|
2020-11-11 12:40:27 +00:00
|
|
|
func (s *Storage) DebugFlush() {
|
2022-12-04 06:17:46 +00:00
|
|
|
s.tb.flushPendingRows()
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
idb := s.idb()
|
|
|
|
idb.tb.DebugFlush()
|
|
|
|
idb.doExtDB(func(extDB *indexDB) {
|
|
|
|
extDB.tb.DebugFlush()
|
|
|
|
})
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// CreateSnapshot creates snapshot for s and returns the snapshot name.
|
2024-02-23 02:46:11 +00:00
|
|
|
func (s *Storage) CreateSnapshot() (string, error) {
|
2019-05-22 21:16:55 +00:00
|
|
|
logger.Infof("creating Storage snapshot for %q...", s.path)
|
|
|
|
startTime := time.Now()
|
|
|
|
|
2020-03-24 20:24:54 +00:00
|
|
|
s.snapshotLock.Lock()
|
|
|
|
defer s.snapshotLock.Unlock()
|
|
|
|
|
2023-02-27 20:57:22 +00:00
|
|
|
var dirsToRemoveOnError []string
|
|
|
|
defer func() {
|
|
|
|
for _, dir := range dirsToRemoveOnError {
|
|
|
|
fs.MustRemoveAll(dir)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2024-02-09 02:03:20 +00:00
|
|
|
snapshotName := snapshotutil.NewName()
|
2019-05-22 21:16:55 +00:00
|
|
|
srcDir := s.path
|
2023-03-25 21:33:54 +00:00
|
|
|
dstDir := filepath.Join(srcDir, snapshotsDirname, snapshotName)
|
2023-04-14 05:11:56 +00:00
|
|
|
fs.MustMkdirFailIfExist(dstDir)
|
2023-02-27 20:57:22 +00:00
|
|
|
dirsToRemoveOnError = append(dirsToRemoveOnError, dstDir)
|
2019-05-22 21:16:55 +00:00
|
|
|
|
2024-02-23 02:46:11 +00:00
|
|
|
smallDir, bigDir := s.tb.MustCreateSnapshot(snapshotName)
|
2023-02-27 20:57:22 +00:00
|
|
|
dirsToRemoveOnError = append(dirsToRemoveOnError, smallDir, bigDir)
|
|
|
|
|
2023-03-25 21:33:54 +00:00
|
|
|
dstDataDir := filepath.Join(dstDir, dataDirname)
|
2023-04-14 05:11:56 +00:00
|
|
|
fs.MustMkdirFailIfExist(dstDataDir)
|
2023-04-14 05:52:45 +00:00
|
|
|
|
2023-03-25 21:33:54 +00:00
|
|
|
dstSmallDir := filepath.Join(dstDataDir, smallDirname)
|
2023-04-14 05:52:45 +00:00
|
|
|
fs.MustSymlinkRelative(smallDir, dstSmallDir)
|
|
|
|
|
2023-03-25 21:33:54 +00:00
|
|
|
dstBigDir := filepath.Join(dstDataDir, bigDirname)
|
2023-04-14 05:52:45 +00:00
|
|
|
fs.MustSymlinkRelative(bigDir, dstBigDir)
|
|
|
|
|
2019-06-11 20:13:04 +00:00
|
|
|
fs.MustSyncPath(dstDataDir)
|
2019-05-22 21:16:55 +00:00
|
|
|
|
2023-03-25 21:33:54 +00:00
|
|
|
srcMetadataDir := filepath.Join(srcDir, metadataDirname)
|
|
|
|
dstMetadataDir := filepath.Join(dstDir, metadataDirname)
|
2023-04-14 06:02:55 +00:00
|
|
|
fs.MustCopyDirectory(srcMetadataDir, dstMetadataDir)
|
2023-02-27 20:57:22 +00:00
|
|
|
|
2023-03-25 21:33:54 +00:00
|
|
|
idbSnapshot := filepath.Join(srcDir, indexdbDirname, snapshotsDirname, snapshotName)
|
2019-05-22 21:16:55 +00:00
|
|
|
idb := s.idb()
|
2023-03-25 21:33:54 +00:00
|
|
|
currSnapshot := filepath.Join(idbSnapshot, idb.name)
|
2024-02-23 02:46:11 +00:00
|
|
|
if err := idb.tb.CreateSnapshotAt(currSnapshot); err != nil {
|
2020-06-30 19:58:18 +00:00
|
|
|
return "", fmt.Errorf("cannot create curr indexDB snapshot: %w", err)
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
2023-02-27 20:57:22 +00:00
|
|
|
dirsToRemoveOnError = append(dirsToRemoveOnError, idbSnapshot)
|
|
|
|
|
2024-02-23 02:46:11 +00:00
|
|
|
var err error
|
2019-05-22 21:16:55 +00:00
|
|
|
ok := idb.doExtDB(func(extDB *indexDB) {
|
2023-03-25 21:33:54 +00:00
|
|
|
prevSnapshot := filepath.Join(idbSnapshot, extDB.name)
|
2024-02-23 02:46:11 +00:00
|
|
|
err = extDB.tb.CreateSnapshotAt(prevSnapshot)
|
2019-05-22 21:16:55 +00:00
|
|
|
})
|
|
|
|
if ok && err != nil {
|
2020-06-30 19:58:18 +00:00
|
|
|
return "", fmt.Errorf("cannot create prev indexDB snapshot: %w", err)
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
2023-03-25 21:33:54 +00:00
|
|
|
dstIdbDir := filepath.Join(dstDir, indexdbDirname)
|
2023-04-14 05:52:45 +00:00
|
|
|
fs.MustSymlinkRelative(idbSnapshot, dstIdbDir)
|
2019-05-22 21:16:55 +00:00
|
|
|
|
2019-06-11 20:13:04 +00:00
|
|
|
fs.MustSyncPath(dstDir)
|
2019-05-22 21:16:55 +00:00
|
|
|
|
2020-01-22 16:27:44 +00:00
|
|
|
logger.Infof("created Storage snapshot for %q at %q in %.3f seconds", srcDir, dstDir, time.Since(startTime).Seconds())
|
2023-02-27 20:57:22 +00:00
|
|
|
dirsToRemoveOnError = nil
|
2019-05-22 21:16:55 +00:00
|
|
|
return snapshotName, nil
|
|
|
|
}
|
|
|
|
|
2024-02-22 16:32:53 +00:00
|
|
|
func (s *Storage) mustGetSnapshotsCount() int {
|
|
|
|
snapshotNames, err := s.ListSnapshots()
|
|
|
|
if err != nil {
|
|
|
|
logger.Panicf("FATAL: cannot list snapshots: %s", err)
|
|
|
|
}
|
|
|
|
return len(snapshotNames)
|
|
|
|
}
|
|
|
|
|
2019-05-22 21:16:55 +00:00
|
|
|
// ListSnapshots returns sorted list of existing snapshots for s.
|
|
|
|
func (s *Storage) ListSnapshots() ([]string, error) {
|
2023-03-25 21:33:54 +00:00
|
|
|
snapshotsPath := filepath.Join(s.path, snapshotsDirname)
|
2019-05-22 21:16:55 +00:00
|
|
|
d, err := os.Open(snapshotsPath)
|
|
|
|
if err != nil {
|
2022-12-04 07:15:22 +00:00
|
|
|
return nil, fmt.Errorf("cannot open snapshots directory: %w", err)
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
|
|
|
defer fs.MustClose(d)
|
|
|
|
|
|
|
|
fnames, err := d.Readdirnames(-1)
|
|
|
|
if err != nil {
|
2022-12-04 07:15:22 +00:00
|
|
|
return nil, fmt.Errorf("cannot read snapshots directory at %q: %w", snapshotsPath, err)
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
|
|
|
snapshotNames := make([]string, 0, len(fnames))
|
|
|
|
for _, fname := range fnames {
|
2024-02-09 02:03:20 +00:00
|
|
|
if err := snapshotutil.Validate(fname); err != nil {
|
2019-05-22 21:16:55 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
snapshotNames = append(snapshotNames, fname)
|
|
|
|
}
|
|
|
|
sort.Strings(snapshotNames)
|
|
|
|
return snapshotNames, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// DeleteSnapshot deletes the given snapshot.
|
|
|
|
func (s *Storage) DeleteSnapshot(snapshotName string) error {
|
2024-02-09 02:03:20 +00:00
|
|
|
if err := snapshotutil.Validate(snapshotName); err != nil {
|
2022-05-04 19:12:03 +00:00
|
|
|
return fmt.Errorf("invalid snapshotName %q: %w", snapshotName, err)
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
2023-03-25 21:33:54 +00:00
|
|
|
snapshotPath := filepath.Join(s.path, snapshotsDirname, snapshotName)
|
2019-05-22 21:16:55 +00:00
|
|
|
|
|
|
|
logger.Infof("deleting snapshot %q...", snapshotPath)
|
|
|
|
startTime := time.Now()
|
|
|
|
|
|
|
|
s.tb.MustDeleteSnapshot(snapshotName)
|
2023-03-25 21:33:54 +00:00
|
|
|
idbPath := filepath.Join(s.path, indexdbDirname, snapshotsDirname, snapshotName)
|
2022-09-13 10:10:33 +00:00
|
|
|
fs.MustRemoveDirAtomic(idbPath)
|
|
|
|
fs.MustRemoveDirAtomic(snapshotPath)
|
2019-05-22 21:16:55 +00:00
|
|
|
|
2020-01-22 16:27:44 +00:00
|
|
|
logger.Infof("deleted snapshot %q in %.3f seconds", snapshotPath, time.Since(startTime).Seconds())
|
2019-05-22 21:16:55 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-05-02 08:00:15 +00:00
|
|
|
// DeleteStaleSnapshots deletes snapshot older than given maxAge
|
|
|
|
func (s *Storage) DeleteStaleSnapshots(maxAge time.Duration) error {
|
|
|
|
list, err := s.ListSnapshots()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
expireDeadline := time.Now().UTC().Add(-maxAge)
|
|
|
|
for _, snapshotName := range list {
|
2024-02-09 02:03:20 +00:00
|
|
|
t, err := snapshotutil.Time(snapshotName)
|
2022-05-02 08:00:15 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot parse snapshot date from %q: %w", snapshotName, err)
|
|
|
|
}
|
|
|
|
if t.Before(expireDeadline) {
|
|
|
|
if err := s.DeleteSnapshot(snapshotName); err != nil {
|
|
|
|
return fmt.Errorf("cannot delete snapshot %q: %w", snapshotName, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-05-22 21:16:55 +00:00
|
|
|
func (s *Storage) idb() *indexDB {
|
2023-07-20 00:37:49 +00:00
|
|
|
return s.idbCurr.Load()
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Metrics contains essential metrics for the Storage.
|
|
|
|
type Metrics struct {
|
2024-09-06 15:57:21 +00:00
|
|
|
RowsReceivedTotal uint64
|
2020-10-09 10:35:48 +00:00
|
|
|
RowsAddedTotal uint64
|
2020-02-27 21:47:05 +00:00
|
|
|
DedupsDuringMerge uint64
|
2024-02-22 16:32:53 +00:00
|
|
|
SnapshotsCount uint64
|
2020-02-27 21:47:05 +00:00
|
|
|
|
2024-09-06 15:57:21 +00:00
|
|
|
NaNValueRows uint64
|
2019-07-26 11:10:25 +00:00
|
|
|
TooSmallTimestampRows uint64
|
|
|
|
TooBigTimestampRows uint64
|
2024-09-06 15:57:21 +00:00
|
|
|
InvalidRawMetricNames uint64
|
2019-07-26 11:10:25 +00:00
|
|
|
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
TimeseriesRepopulated uint64
|
2023-07-22 22:20:21 +00:00
|
|
|
TimeseriesPreCreated uint64
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
NewTimeseriesCreated uint64
|
2020-05-15 10:44:23 +00:00
|
|
|
SlowRowInserts uint64
|
|
|
|
SlowPerDayIndexInserts uint64
|
2020-05-15 11:11:39 +00:00
|
|
|
SlowMetricNameLoads uint64
|
2020-05-15 10:44:23 +00:00
|
|
|
|
2022-08-24 10:41:53 +00:00
|
|
|
HourlySeriesLimitRowsDropped uint64
|
|
|
|
HourlySeriesLimitMaxSeries uint64
|
|
|
|
HourlySeriesLimitCurrentSeries uint64
|
|
|
|
|
|
|
|
DailySeriesLimitRowsDropped uint64
|
|
|
|
DailySeriesLimitMaxSeries uint64
|
|
|
|
DailySeriesLimitCurrentSeries uint64
|
2021-05-20 11:15:19 +00:00
|
|
|
|
2020-09-09 20:18:32 +00:00
|
|
|
TimestampsBlocksMerged uint64
|
|
|
|
TimestampsBytesSaved uint64
|
|
|
|
|
2021-12-02 08:28:45 +00:00
|
|
|
TSIDCacheSize uint64
|
|
|
|
TSIDCacheSizeBytes uint64
|
|
|
|
TSIDCacheSizeMaxBytes uint64
|
|
|
|
TSIDCacheRequests uint64
|
|
|
|
TSIDCacheMisses uint64
|
|
|
|
TSIDCacheCollisions uint64
|
|
|
|
|
|
|
|
MetricIDCacheSize uint64
|
|
|
|
MetricIDCacheSizeBytes uint64
|
|
|
|
MetricIDCacheSizeMaxBytes uint64
|
|
|
|
MetricIDCacheRequests uint64
|
|
|
|
MetricIDCacheMisses uint64
|
|
|
|
MetricIDCacheCollisions uint64
|
|
|
|
|
|
|
|
MetricNameCacheSize uint64
|
|
|
|
MetricNameCacheSizeBytes uint64
|
|
|
|
MetricNameCacheSizeMaxBytes uint64
|
|
|
|
MetricNameCacheRequests uint64
|
|
|
|
MetricNameCacheMisses uint64
|
|
|
|
MetricNameCacheCollisions uint64
|
2019-05-22 21:16:55 +00:00
|
|
|
|
2019-11-11 11:21:05 +00:00
|
|
|
DateMetricIDCacheSize uint64
|
2019-11-13 15:58:05 +00:00
|
|
|
DateMetricIDCacheSizeBytes uint64
|
2019-11-11 11:21:05 +00:00
|
|
|
DateMetricIDCacheSyncsCount uint64
|
|
|
|
DateMetricIDCacheResetsCount uint64
|
2019-05-22 21:16:55 +00:00
|
|
|
|
2019-11-13 17:00:02 +00:00
|
|
|
HourMetricIDCacheSize uint64
|
|
|
|
HourMetricIDCacheSizeBytes uint64
|
2019-06-19 15:36:47 +00:00
|
|
|
|
2020-05-11 22:06:17 +00:00
|
|
|
NextDayMetricIDCacheSize uint64
|
|
|
|
NextDayMetricIDCacheSizeBytes uint64
|
|
|
|
|
2020-01-29 23:59:43 +00:00
|
|
|
PrefetchedMetricIDsSize uint64
|
|
|
|
PrefetchedMetricIDsSizeBytes uint64
|
|
|
|
|
2022-07-13 09:37:04 +00:00
|
|
|
NextRetentionSeconds uint64
|
|
|
|
|
2019-05-22 21:16:55 +00:00
|
|
|
IndexDBMetrics IndexDBMetrics
|
|
|
|
TableMetrics TableMetrics
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reset resets m.
|
|
|
|
func (m *Metrics) Reset() {
|
|
|
|
*m = Metrics{}
|
|
|
|
}
|
|
|
|
|
|
|
|
// UpdateMetrics updates m with metrics from s.
|
|
|
|
func (s *Storage) UpdateMetrics(m *Metrics) {
|
2024-09-06 15:57:21 +00:00
|
|
|
m.RowsReceivedTotal += s.rowsReceivedTotal.Load()
|
2024-08-27 19:30:37 +00:00
|
|
|
m.RowsAddedTotal += s.rowsAddedTotal.Load()
|
2024-02-23 20:51:48 +00:00
|
|
|
m.DedupsDuringMerge = dedupsDuringMerge.Load()
|
2024-02-22 16:32:53 +00:00
|
|
|
m.SnapshotsCount += uint64(s.mustGetSnapshotsCount())
|
2020-02-27 21:47:05 +00:00
|
|
|
|
2024-09-06 15:57:21 +00:00
|
|
|
m.NaNValueRows += s.naNValueRows.Load()
|
2024-02-23 22:15:21 +00:00
|
|
|
m.TooSmallTimestampRows += s.tooSmallTimestampRows.Load()
|
|
|
|
m.TooBigTimestampRows += s.tooBigTimestampRows.Load()
|
2024-09-06 15:57:21 +00:00
|
|
|
m.InvalidRawMetricNames += s.invalidRawMetricNames.Load()
|
2019-07-26 11:10:25 +00:00
|
|
|
|
2024-02-23 22:15:21 +00:00
|
|
|
m.TimeseriesRepopulated += s.timeseriesRepopulated.Load()
|
|
|
|
m.TimeseriesPreCreated += s.timeseriesPreCreated.Load()
|
|
|
|
m.NewTimeseriesCreated += s.newTimeseriesCreated.Load()
|
|
|
|
m.SlowRowInserts += s.slowRowInserts.Load()
|
|
|
|
m.SlowPerDayIndexInserts += s.slowPerDayIndexInserts.Load()
|
|
|
|
m.SlowMetricNameLoads += s.slowMetricNameLoads.Load()
|
2020-05-15 10:44:23 +00:00
|
|
|
|
2022-08-24 10:41:53 +00:00
|
|
|
if sl := s.hourlySeriesLimiter; sl != nil {
|
2024-02-23 22:15:21 +00:00
|
|
|
m.HourlySeriesLimitRowsDropped += s.hourlySeriesLimitRowsDropped.Load()
|
2022-08-24 10:41:53 +00:00
|
|
|
m.HourlySeriesLimitMaxSeries += uint64(sl.MaxItems())
|
|
|
|
m.HourlySeriesLimitCurrentSeries += uint64(sl.CurrentItems())
|
|
|
|
}
|
|
|
|
|
|
|
|
if sl := s.dailySeriesLimiter; sl != nil {
|
2024-02-23 22:15:21 +00:00
|
|
|
m.DailySeriesLimitRowsDropped += s.dailySeriesLimitRowsDropped.Load()
|
2022-08-24 10:41:53 +00:00
|
|
|
m.DailySeriesLimitMaxSeries += uint64(sl.MaxItems())
|
|
|
|
m.DailySeriesLimitCurrentSeries += uint64(sl.CurrentItems())
|
|
|
|
}
|
2021-05-20 11:15:19 +00:00
|
|
|
|
2024-02-23 22:15:21 +00:00
|
|
|
m.TimestampsBlocksMerged = timestampsBlocksMerged.Load()
|
|
|
|
m.TimestampsBytesSaved = timestampsBytesSaved.Load()
|
2020-09-09 20:18:32 +00:00
|
|
|
|
2019-05-22 21:16:55 +00:00
|
|
|
var cs fastcache.Stats
|
|
|
|
s.tsidCache.UpdateStats(&cs)
|
|
|
|
m.TSIDCacheSize += cs.EntriesCount
|
2019-07-09 21:47:29 +00:00
|
|
|
m.TSIDCacheSizeBytes += cs.BytesSize
|
2021-12-02 08:28:45 +00:00
|
|
|
m.TSIDCacheSizeMaxBytes += cs.MaxBytesSize
|
2019-05-22 21:16:55 +00:00
|
|
|
m.TSIDCacheRequests += cs.GetCalls
|
|
|
|
m.TSIDCacheMisses += cs.Misses
|
|
|
|
m.TSIDCacheCollisions += cs.Collisions
|
|
|
|
|
|
|
|
cs.Reset()
|
|
|
|
s.metricIDCache.UpdateStats(&cs)
|
|
|
|
m.MetricIDCacheSize += cs.EntriesCount
|
2019-07-09 21:47:29 +00:00
|
|
|
m.MetricIDCacheSizeBytes += cs.BytesSize
|
2021-12-02 08:28:45 +00:00
|
|
|
m.MetricIDCacheSizeMaxBytes += cs.MaxBytesSize
|
2019-05-22 21:16:55 +00:00
|
|
|
m.MetricIDCacheRequests += cs.GetCalls
|
|
|
|
m.MetricIDCacheMisses += cs.Misses
|
|
|
|
m.MetricIDCacheCollisions += cs.Collisions
|
|
|
|
|
|
|
|
cs.Reset()
|
|
|
|
s.metricNameCache.UpdateStats(&cs)
|
|
|
|
m.MetricNameCacheSize += cs.EntriesCount
|
2019-07-09 21:47:29 +00:00
|
|
|
m.MetricNameCacheSizeBytes += cs.BytesSize
|
2021-12-02 08:28:45 +00:00
|
|
|
m.MetricNameCacheSizeMaxBytes += cs.MaxBytesSize
|
2019-05-22 21:16:55 +00:00
|
|
|
m.MetricNameCacheRequests += cs.GetCalls
|
|
|
|
m.MetricNameCacheMisses += cs.Misses
|
|
|
|
m.MetricNameCacheCollisions += cs.Collisions
|
|
|
|
|
2019-11-09 21:05:14 +00:00
|
|
|
m.DateMetricIDCacheSize += uint64(s.dateMetricIDCache.EntriesCount())
|
2019-11-13 15:58:05 +00:00
|
|
|
m.DateMetricIDCacheSizeBytes += uint64(s.dateMetricIDCache.SizeBytes())
|
2024-02-23 22:15:21 +00:00
|
|
|
m.DateMetricIDCacheSyncsCount += s.dateMetricIDCache.syncsCount.Load()
|
|
|
|
m.DateMetricIDCacheResetsCount += s.dateMetricIDCache.resetsCount.Load()
|
2019-05-22 21:16:55 +00:00
|
|
|
|
2023-07-20 00:37:49 +00:00
|
|
|
hmCurr := s.currHourMetricIDs.Load()
|
|
|
|
hmPrev := s.prevHourMetricIDs.Load()
|
2019-09-24 18:10:22 +00:00
|
|
|
hourMetricIDsLen := hmPrev.m.Len()
|
|
|
|
if hmCurr.m.Len() > hourMetricIDsLen {
|
|
|
|
hourMetricIDsLen = hmCurr.m.Len()
|
2019-06-19 15:36:47 +00:00
|
|
|
}
|
|
|
|
m.HourMetricIDCacheSize += uint64(hourMetricIDsLen)
|
2019-11-13 17:00:02 +00:00
|
|
|
m.HourMetricIDCacheSizeBytes += hmCurr.m.SizeBytes()
|
|
|
|
m.HourMetricIDCacheSizeBytes += hmPrev.m.SizeBytes()
|
2019-06-19 15:36:47 +00:00
|
|
|
|
2023-07-20 00:37:49 +00:00
|
|
|
nextDayMetricIDs := &s.nextDayMetricIDs.Load().v
|
2020-05-11 22:06:17 +00:00
|
|
|
m.NextDayMetricIDCacheSize += uint64(nextDayMetricIDs.Len())
|
|
|
|
m.NextDayMetricIDCacheSizeBytes += nextDayMetricIDs.SizeBytes()
|
|
|
|
|
2024-01-16 13:26:08 +00:00
|
|
|
s.prefetchedMetricIDsLock.Lock()
|
|
|
|
prefetchedMetricIDs := s.prefetchedMetricIDs
|
2020-01-29 23:59:43 +00:00
|
|
|
m.PrefetchedMetricIDsSize += uint64(prefetchedMetricIDs.Len())
|
|
|
|
m.PrefetchedMetricIDsSizeBytes += uint64(prefetchedMetricIDs.SizeBytes())
|
2024-01-16 13:26:08 +00:00
|
|
|
s.prefetchedMetricIDsLock.Unlock()
|
2020-01-29 23:59:43 +00:00
|
|
|
|
2023-07-22 22:20:21 +00:00
|
|
|
d := s.nextRetentionSeconds()
|
|
|
|
if d < 0 {
|
|
|
|
d = 0
|
|
|
|
}
|
|
|
|
m.NextRetentionSeconds = uint64(d)
|
2022-07-13 09:37:04 +00:00
|
|
|
|
2019-05-22 21:16:55 +00:00
|
|
|
s.idb().UpdateMetrics(&m.IndexDBMetrics)
|
|
|
|
s.tb.UpdateMetrics(&m.TableMetrics)
|
|
|
|
}
|
|
|
|
|
2023-07-22 22:20:21 +00:00
|
|
|
func (s *Storage) nextRetentionSeconds() int64 {
|
2024-02-23 22:15:21 +00:00
|
|
|
return s.nextRotationTimestamp.Load() - int64(fasttime.UnixTimestamp())
|
2023-07-22 22:20:21 +00:00
|
|
|
}
|
|
|
|
|
2021-10-08 09:52:56 +00:00
|
|
|
// SetFreeDiskSpaceLimit sets the minimum free disk space size of current storage path
|
|
|
|
//
|
|
|
|
// The function must be called before opening or creating any storage.
|
2022-12-15 03:26:24 +00:00
|
|
|
func SetFreeDiskSpaceLimit(bytes int64) {
|
2021-10-08 11:15:52 +00:00
|
|
|
freeDiskSpaceLimitBytes = uint64(bytes)
|
2021-10-08 09:52:56 +00:00
|
|
|
}
|
|
|
|
|
2021-10-08 11:15:52 +00:00
|
|
|
var freeDiskSpaceLimitBytes uint64
|
|
|
|
|
2021-10-08 09:52:56 +00:00
|
|
|
// IsReadOnly returns information is storage in read only mode
|
|
|
|
func (s *Storage) IsReadOnly() bool {
|
2024-02-23 21:29:23 +00:00
|
|
|
return s.isReadOnly.Load()
|
2021-10-08 09:52:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Storage) startFreeDiskSpaceWatcher() {
|
|
|
|
f := func() {
|
|
|
|
freeSpaceBytes := fs.MustGetFreeSpace(s.path)
|
2021-10-08 11:15:52 +00:00
|
|
|
if freeSpaceBytes < freeDiskSpaceLimitBytes {
|
2023-10-31 15:06:55 +00:00
|
|
|
// Switch the storage to readonly mode if there is no enough free space left at s.path
|
2024-01-21 11:58:27 +00:00
|
|
|
//
|
2024-02-23 21:29:23 +00:00
|
|
|
// Use Load in front of CompareAndSwap in order to avoid slow inter-CPU synchronization
|
2024-01-21 11:58:27 +00:00
|
|
|
// when the storage is already in read-only mode.
|
2024-02-23 21:29:23 +00:00
|
|
|
if !s.isReadOnly.Load() && s.isReadOnly.CompareAndSwap(false, true) {
|
2023-10-30 09:52:57 +00:00
|
|
|
// log notification only on state change
|
|
|
|
logger.Warnf("switching the storage at %s to read-only mode, since it has less than -storage.minFreeDiskSpaceBytes=%d of free space: %d bytes left",
|
|
|
|
s.path, freeDiskSpaceLimitBytes, freeSpaceBytes)
|
|
|
|
}
|
2021-10-08 09:52:56 +00:00
|
|
|
return
|
|
|
|
}
|
2024-02-23 21:29:23 +00:00
|
|
|
// Use Load in front of CompareAndSwap in order to avoid slow inter-CPU synchronization
|
2024-01-21 11:58:27 +00:00
|
|
|
// when the storage isn't in read-only mode.
|
2024-02-23 21:29:23 +00:00
|
|
|
if s.isReadOnly.Load() && s.isReadOnly.CompareAndSwap(true, false) {
|
lib/{mergeset,storage}: make background merge more responsive and scalable
- Maintain a separate worker pool per each part type (in-memory, file, big and small).
Previously a shared pool was used for merging all the part types.
A single merge worker could merge parts with mixed types at once. For example,
it could merge simultaneously an in-memory part plus a big file part.
Such a merge could take hours for big file part. During the duration of this merge
the in-memory part was pinned in memory and couldn't be persisted to disk
under the configured -inmemoryDataFlushInterval .
Another common issue, which could happen when parts with mixed types are merged,
is uncontrolled growth of in-memory parts or small parts when all the merge workers
were busy with merging big files. Such growth could lead to significant performance
degradataion for queries, since every query needs to check ever growing list of parts.
This could also slow down the registration of new time series, since VictoriaMetrics
searches for the internal series_id in the indexdb for every new time series.
The third issue is graceful shutdown duration, which could be very long when a background
merge is running on in-memory parts plus big file parts. This merge couldn't be interrupted,
since it merges in-memory parts.
A separate pool of merge workers per every part type elegantly resolves both issues:
- In-memory parts are merged to file-based parts in a timely manner, since the maximum
size of in-memory parts is limited.
- Long-running merges for big parts do not block merges for in-memory parts and small parts.
- Graceful shutdown duration is now limited by the time needed for flushing in-memory parts to files.
Merging for file parts is instantly canceled on graceful shutdown now.
- Deprecate -smallMergeConcurrency command-line flag, since the new background merge algorithm
should automatically self-tune according to the number of available CPU cores.
- Deprecate -finalMergeDelay command-line flag, since it wasn't working correctly.
It is better to run forced merge when needed - https://docs.victoriametrics.com/#forced-merge
- Tune the number of shards for pending rows and items before the data goes to in-memory parts
and becomes visible for search. This improves the maximum data ingestion rate and the maximum rate
for registration of new time series. This should reduce the duration of data ingestion slowdown
in VictoriaMetrics cluster on e.g. re-routing events, when some of vmstorage nodes become temporarily
unavailable.
- Prevent from possible "sync: WaitGroup misuse" panic on graceful shutdown.
This is a follow-up for fa566c68a6ccf7385a05f649aee7e5f5a38afb15 .
Thanks @misutoth to for the inspiration at https://github.com/VictoriaMetrics/VictoriaMetrics/pull/5212
Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5190
Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3790
Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3551
Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3337
Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3425
Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3647
Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3641
Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/648
Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/291
2024-01-26 20:39:49 +00:00
|
|
|
s.notifyReadWriteMode()
|
|
|
|
logger.Warnf("switching the storage at %s to read-write mode, since it has more than -storage.minFreeDiskSpaceBytes=%d of free space: %d bytes left",
|
2021-10-19 20:58:05 +00:00
|
|
|
s.path, freeDiskSpaceLimitBytes, freeSpaceBytes)
|
|
|
|
}
|
2021-10-08 09:52:56 +00:00
|
|
|
}
|
|
|
|
f()
|
2021-10-08 11:15:52 +00:00
|
|
|
s.freeDiskSpaceWatcherWG.Add(1)
|
2021-10-08 09:52:56 +00:00
|
|
|
go func() {
|
2021-10-08 11:15:52 +00:00
|
|
|
defer s.freeDiskSpaceWatcherWG.Done()
|
2024-01-22 16:12:37 +00:00
|
|
|
d := timeutil.AddJitterToDuration(time.Second)
|
|
|
|
ticker := time.NewTicker(d)
|
2021-10-08 11:15:52 +00:00
|
|
|
defer ticker.Stop()
|
2021-10-08 09:52:56 +00:00
|
|
|
for {
|
|
|
|
select {
|
2024-04-02 18:24:57 +00:00
|
|
|
case <-s.stopCh:
|
2021-10-08 09:52:56 +00:00
|
|
|
return
|
2021-10-08 11:15:52 +00:00
|
|
|
case <-ticker.C:
|
2021-10-08 09:52:56 +00:00
|
|
|
f()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
lib/{mergeset,storage}: make background merge more responsive and scalable
- Maintain a separate worker pool per each part type (in-memory, file, big and small).
Previously a shared pool was used for merging all the part types.
A single merge worker could merge parts with mixed types at once. For example,
it could merge simultaneously an in-memory part plus a big file part.
Such a merge could take hours for big file part. During the duration of this merge
the in-memory part was pinned in memory and couldn't be persisted to disk
under the configured -inmemoryDataFlushInterval .
Another common issue, which could happen when parts with mixed types are merged,
is uncontrolled growth of in-memory parts or small parts when all the merge workers
were busy with merging big files. Such growth could lead to significant performance
degradataion for queries, since every query needs to check ever growing list of parts.
This could also slow down the registration of new time series, since VictoriaMetrics
searches for the internal series_id in the indexdb for every new time series.
The third issue is graceful shutdown duration, which could be very long when a background
merge is running on in-memory parts plus big file parts. This merge couldn't be interrupted,
since it merges in-memory parts.
A separate pool of merge workers per every part type elegantly resolves both issues:
- In-memory parts are merged to file-based parts in a timely manner, since the maximum
size of in-memory parts is limited.
- Long-running merges for big parts do not block merges for in-memory parts and small parts.
- Graceful shutdown duration is now limited by the time needed for flushing in-memory parts to files.
Merging for file parts is instantly canceled on graceful shutdown now.
- Deprecate -smallMergeConcurrency command-line flag, since the new background merge algorithm
should automatically self-tune according to the number of available CPU cores.
- Deprecate -finalMergeDelay command-line flag, since it wasn't working correctly.
It is better to run forced merge when needed - https://docs.victoriametrics.com/#forced-merge
- Tune the number of shards for pending rows and items before the data goes to in-memory parts
and becomes visible for search. This improves the maximum data ingestion rate and the maximum rate
for registration of new time series. This should reduce the duration of data ingestion slowdown
in VictoriaMetrics cluster on e.g. re-routing events, when some of vmstorage nodes become temporarily
unavailable.
- Prevent from possible "sync: WaitGroup misuse" panic on graceful shutdown.
This is a follow-up for fa566c68a6ccf7385a05f649aee7e5f5a38afb15 .
Thanks @misutoth to for the inspiration at https://github.com/VictoriaMetrics/VictoriaMetrics/pull/5212
Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5190
Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3790
Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3551
Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3337
Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3425
Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3647
Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3641
Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/648
Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/291
2024-01-26 20:39:49 +00:00
|
|
|
func (s *Storage) notifyReadWriteMode() {
|
|
|
|
s.tb.NotifyReadWriteMode()
|
|
|
|
|
|
|
|
idb := s.idb()
|
|
|
|
idb.tb.NotifyReadWriteMode()
|
|
|
|
idb.doExtDB(func(extDB *indexDB) {
|
|
|
|
extDB.tb.NotifyReadWriteMode()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-05-22 21:16:55 +00:00
|
|
|
func (s *Storage) startRetentionWatcher() {
|
|
|
|
s.retentionWatcherWG.Add(1)
|
|
|
|
go func() {
|
|
|
|
s.retentionWatcher()
|
|
|
|
s.retentionWatcherWG.Done()
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Storage) retentionWatcher() {
|
|
|
|
for {
|
2023-07-22 22:20:21 +00:00
|
|
|
d := s.nextRetentionSeconds()
|
2019-05-22 21:16:55 +00:00
|
|
|
select {
|
2024-04-02 18:24:57 +00:00
|
|
|
case <-s.stopCh:
|
2019-05-22 21:16:55 +00:00
|
|
|
return
|
2023-07-29 02:47:02 +00:00
|
|
|
case currentTime := <-time.After(time.Second * time.Duration(d)):
|
|
|
|
s.mustRotateIndexDB(currentTime)
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-09 16:06:53 +00:00
|
|
|
func (s *Storage) startCurrHourMetricIDsUpdater() {
|
|
|
|
s.currHourMetricIDsUpdaterWG.Add(1)
|
2019-06-02 15:34:08 +00:00
|
|
|
go func() {
|
2019-06-09 16:06:53 +00:00
|
|
|
s.currHourMetricIDsUpdater()
|
|
|
|
s.currHourMetricIDsUpdaterWG.Done()
|
2019-06-02 15:34:08 +00:00
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
2020-05-11 22:06:17 +00:00
|
|
|
func (s *Storage) startNextDayMetricIDsUpdater() {
|
|
|
|
s.nextDayMetricIDsUpdaterWG.Add(1)
|
|
|
|
go func() {
|
|
|
|
s.nextDayMetricIDsUpdater()
|
|
|
|
s.nextDayMetricIDsUpdaterWG.Done()
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
2019-06-09 16:06:53 +00:00
|
|
|
func (s *Storage) currHourMetricIDsUpdater() {
|
2024-01-22 16:12:37 +00:00
|
|
|
d := timeutil.AddJitterToDuration(time.Second * 10)
|
|
|
|
ticker := time.NewTicker(d)
|
2020-02-13 10:55:58 +00:00
|
|
|
defer ticker.Stop()
|
2019-06-02 15:34:08 +00:00
|
|
|
for {
|
|
|
|
select {
|
2024-04-02 18:24:57 +00:00
|
|
|
case <-s.stopCh:
|
2022-11-07 11:55:37 +00:00
|
|
|
hour := fasttime.UnixHour()
|
|
|
|
s.updateCurrHourMetricIDs(hour)
|
2019-06-02 15:34:08 +00:00
|
|
|
return
|
2020-02-13 10:55:58 +00:00
|
|
|
case <-ticker.C:
|
2022-11-07 11:55:37 +00:00
|
|
|
hour := fasttime.UnixHour()
|
|
|
|
s.updateCurrHourMetricIDs(hour)
|
2019-06-02 15:34:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-11 22:06:17 +00:00
|
|
|
func (s *Storage) nextDayMetricIDsUpdater() {
|
2024-01-22 16:12:37 +00:00
|
|
|
d := timeutil.AddJitterToDuration(time.Second * 11)
|
|
|
|
ticker := time.NewTicker(d)
|
2020-05-11 22:06:17 +00:00
|
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
|
|
select {
|
2024-04-02 18:24:57 +00:00
|
|
|
case <-s.stopCh:
|
2022-11-07 12:04:06 +00:00
|
|
|
date := fasttime.UnixDate()
|
|
|
|
s.updateNextDayMetricIDs(date)
|
2020-05-11 22:06:17 +00:00
|
|
|
return
|
|
|
|
case <-ticker.C:
|
2022-11-07 12:04:06 +00:00
|
|
|
date := fasttime.UnixDate()
|
|
|
|
s.updateNextDayMetricIDs(date)
|
2020-05-11 22:06:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-29 02:47:02 +00:00
|
|
|
func (s *Storage) mustRotateIndexDB(currentTime time.Time) {
|
2023-07-22 22:20:21 +00:00
|
|
|
// Create new indexdb table, which will be used as idbNext
|
2019-05-22 21:16:55 +00:00
|
|
|
newTableName := nextIndexDBTableName()
|
2023-03-25 21:33:54 +00:00
|
|
|
idbNewPath := filepath.Join(s.path, indexdbDirname, newTableName)
|
2023-07-22 22:20:21 +00:00
|
|
|
idbNew := mustOpenIndexDB(idbNewPath, s, &s.isReadOnly)
|
|
|
|
|
|
|
|
// Update nextRotationTimestamp
|
2023-08-23 11:22:53 +00:00
|
|
|
nextRotationTimestamp := currentTime.Unix() + s.retentionMsecs/1000
|
2024-02-23 22:15:21 +00:00
|
|
|
s.nextRotationTimestamp.Store(nextRotationTimestamp)
|
2023-07-22 22:20:21 +00:00
|
|
|
|
|
|
|
// Set idbNext to idbNew
|
|
|
|
idbNext := s.idbNext.Load()
|
|
|
|
idbNew.SetExtDB(idbNext)
|
|
|
|
s.idbNext.Store(idbNew)
|
2019-05-22 21:16:55 +00:00
|
|
|
|
2023-07-22 22:20:21 +00:00
|
|
|
// Set idbCurr to idbNext
|
2019-05-22 21:16:55 +00:00
|
|
|
idbCurr := s.idb()
|
2023-07-22 22:20:21 +00:00
|
|
|
s.idbCurr.Store(idbNext)
|
|
|
|
|
|
|
|
// Schedule data removal for idbPrev
|
2019-05-22 21:16:55 +00:00
|
|
|
idbCurr.doExtDB(func(extDB *indexDB) {
|
|
|
|
extDB.scheduleToDrop()
|
|
|
|
})
|
|
|
|
idbCurr.SetExtDB(nil)
|
|
|
|
|
|
|
|
// Persist changes on the file system.
|
2019-06-11 20:13:04 +00:00
|
|
|
fs.MustSyncPath(s.path)
|
2019-05-22 21:16:55 +00:00
|
|
|
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
// Do not flush tsidCache to avoid read/write path slowdown.
|
|
|
|
// The cache is automatically re-populated with new TSID entries
|
|
|
|
// with the updated indexdb generation.
|
lib/index: reduce read/write load after indexDB rotation (#2177)
* lib/index: reduce read/write load after indexDB rotation
IndexDB in VM is responsible for storing TSID - ID's used for identifying
time series. The index is stored on disk and used by both ingestion and read path.
IndexDB is stored separately to data parts and is global for all stored data.
It can't be deleted partially as VM deletes data parts. Instead, indexDB is
rotated once in `retention` interval.
The rotation procedure means that `current` indexDB becomes `previous`,
and new freshly created indexDB struct becomes `current`. So in any time,
VM holds indexDB for current and previous retention periods.
When time series is ingested or queried, VM checks if its TSID is present
in `current` indexDB. If it is missing, it checks the `previous` indexDB.
If TSID was found, it gets copied to the `current` indexDB. In this way
`current` indexDB stores only series which were active during the retention
period.
To improve indexDB lookups, VM uses a cache layer called `tsidCache`. Both
write and read path consult `tsidCache` and on miss the relad lookup happens.
When rotation happens, VM resets the `tsidCache`. This is needed for ingestion
path to trigger `current` indexDB re-population. Since index re-population
requires additional resources, every index rotation event may cause some extra
load on CPU and disk. While it may be unnoticeable for most of the cases,
for systems with very high number of unique series each rotation may lead
to performance degradation for some period of time.
This PR makes an attempt to smooth out resource usage after the rotation.
The changes are following:
1. `tsidCache` is no longer reset after the rotation;
2. Instead, each entry in `tsidCache` gains a notion of indexDB to which
they belong;
3. On ingestion path after the rotation we check if requested TSID was
found in `tsidCache`. Then we have 3 branches:
3.1 Fast path. It was found, and belongs to the `current` indexDB. Return TSID.
3.2 Slow path. It wasn't found, so we generate it from scratch,
add to `current` indexDB, add it to `tsidCache`.
3.3 Smooth path. It was found but does not belong to the `current` indexDB.
In this case, we add it to the `current` indexDB with some probability.
The probability is based on time passed since the last rotation with some threshold.
The more time has passed since rotation the higher is chance to re-populate `current` indexDB.
The default re-population interval in this PR is set to `1h`, during which entries from
`previous` index supposed to slowly re-populate `current` index.
The new metric `vm_timeseries_repopulated_total` was added to identify how many TSIDs
were moved from `previous` indexDB to the `current` indexDB. This metric supposed to
grow only during the first `1h` after the last rotation.
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401
Signed-off-by: hagen1778 <roman@victoriametrics.com>
* wip
* wip
Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
2022-02-11 22:30:08 +00:00
|
|
|
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401
|
2019-05-22 21:16:55 +00:00
|
|
|
|
2022-06-19 17:48:42 +00:00
|
|
|
// Flush metric id caches for the current and the previous hour,
|
2023-07-22 22:20:21 +00:00
|
|
|
// since they may contain entries missing in idbCurr after the rotation.
|
2022-06-19 17:48:42 +00:00
|
|
|
// This should prevent from missing data in queries when
|
|
|
|
// the following steps are performed for short -retentionPeriod (e.g. 1 day):
|
|
|
|
//
|
|
|
|
// 1. Add samples for some series between 3-4 UTC. These series are registered in currHourMetricIDs.
|
|
|
|
// 2. The indexdb rotation is performed at 4 UTC. currHourMetricIDs is moved to prevHourMetricIDs.
|
|
|
|
// 3. Continue adding samples for series from step 1 during time range 4-5 UTC.
|
|
|
|
// These series are already registered in prevHourMetricIDs, so VM doesn't add per-day entries to the current indexdb.
|
|
|
|
// 4. Stop adding new samples for these series just before 5 UTC.
|
|
|
|
// 5. The next indexdb rotation is performed at 4 UTC next day.
|
2023-07-22 22:20:21 +00:00
|
|
|
// The information about the series added at step 3 disappears from indexdb, since the old indexdb from step 1 is deleted,
|
2022-06-19 17:48:42 +00:00
|
|
|
// while the current indexdb doesn't contain information about the series.
|
|
|
|
// So queries for the last 24 hours stop returning samples added at step 3.
|
|
|
|
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
|
|
|
|
s.pendingHourEntriesLock.Lock()
|
2022-11-07 12:04:06 +00:00
|
|
|
s.pendingHourEntries = &uint64set.Set{}
|
2022-06-19 17:48:42 +00:00
|
|
|
s.pendingHourEntriesLock.Unlock()
|
|
|
|
s.currHourMetricIDs.Store(&hourMetricIDs{})
|
|
|
|
s.prevHourMetricIDs.Store(&hourMetricIDs{})
|
|
|
|
|
2023-07-22 22:20:21 +00:00
|
|
|
// Do not flush dateMetricIDCache, since it contains entries prefixed with idb generation.
|
|
|
|
|
|
|
|
// There is no need in resetting nextDayMetricIDs, since it contains entries prefixed with idb generation.
|
2019-05-22 21:16:55 +00:00
|
|
|
|
|
|
|
// Do not flush metricIDCache and metricNameCache, since all the metricIDs
|
|
|
|
// from prev idb remain valid after the rotation.
|
|
|
|
}
|
|
|
|
|
2021-06-11 09:42:26 +00:00
|
|
|
func (s *Storage) resetAndSaveTSIDCache() {
|
2022-02-16 16:37:26 +00:00
|
|
|
// Reset cache and then store the reset cache on disk in order to prevent
|
|
|
|
// from inconsistent behaviour after possible unclean shutdown.
|
|
|
|
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1347
|
2021-06-11 09:42:26 +00:00
|
|
|
s.tsidCache.Reset()
|
2023-05-16 22:14:18 +00:00
|
|
|
s.mustSaveCache(s.tsidCache, "metricName_tsid")
|
2021-06-11 09:42:26 +00:00
|
|
|
}
|
|
|
|
|
2019-05-22 21:16:55 +00:00
|
|
|
// MustClose closes the storage.
|
2021-02-17 12:59:04 +00:00
|
|
|
//
|
|
|
|
// It is expected that the s is no longer used during the close.
|
2019-05-22 21:16:55 +00:00
|
|
|
func (s *Storage) MustClose() {
|
2024-04-02 18:24:57 +00:00
|
|
|
close(s.stopCh)
|
2019-05-22 21:16:55 +00:00
|
|
|
|
2021-10-08 11:15:52 +00:00
|
|
|
s.freeDiskSpaceWatcherWG.Wait()
|
2019-05-22 21:16:55 +00:00
|
|
|
s.retentionWatcherWG.Wait()
|
2019-06-09 16:06:53 +00:00
|
|
|
s.currHourMetricIDsUpdaterWG.Wait()
|
2020-05-11 22:06:17 +00:00
|
|
|
s.nextDayMetricIDsUpdaterWG.Wait()
|
2019-05-22 21:16:55 +00:00
|
|
|
|
|
|
|
s.tb.MustClose()
|
|
|
|
s.idb().MustClose()
|
|
|
|
|
|
|
|
// Save caches.
|
2023-05-16 22:14:18 +00:00
|
|
|
s.mustSaveCache(s.tsidCache, "metricName_tsid")
|
2021-06-11 09:42:26 +00:00
|
|
|
s.tsidCache.Stop()
|
2023-05-16 22:14:18 +00:00
|
|
|
s.mustSaveCache(s.metricIDCache, "metricID_tsid")
|
2021-06-11 09:42:26 +00:00
|
|
|
s.metricIDCache.Stop()
|
2023-05-16 22:14:18 +00:00
|
|
|
s.mustSaveCache(s.metricNameCache, "metricID_metricName")
|
2021-06-11 09:42:26 +00:00
|
|
|
s.metricNameCache.Stop()
|
2019-05-22 21:16:55 +00:00
|
|
|
|
2023-07-20 00:37:49 +00:00
|
|
|
hmCurr := s.currHourMetricIDs.Load()
|
2019-06-14 04:52:32 +00:00
|
|
|
s.mustSaveHourMetricIDs(hmCurr, "curr_hour_metric_ids")
|
2023-07-20 00:37:49 +00:00
|
|
|
hmPrev := s.prevHourMetricIDs.Load()
|
2019-06-14 04:52:32 +00:00
|
|
|
s.mustSaveHourMetricIDs(hmPrev, "prev_hour_metric_ids")
|
|
|
|
|
2023-07-20 00:37:49 +00:00
|
|
|
nextDayMetricIDs := s.nextDayMetricIDs.Load()
|
2020-05-11 22:06:17 +00:00
|
|
|
s.mustSaveNextDayMetricIDs(nextDayMetricIDs)
|
|
|
|
|
2019-05-22 21:16:55 +00:00
|
|
|
// Release lock file.
|
2023-04-15 03:09:56 +00:00
|
|
|
fs.MustClose(s.flockF)
|
|
|
|
s.flockF = nil
|
2021-09-01 11:14:37 +00:00
|
|
|
|
|
|
|
// Stop series limiters.
|
|
|
|
if sl := s.hourlySeriesLimiter; sl != nil {
|
|
|
|
sl.MustStop()
|
|
|
|
}
|
|
|
|
if sl := s.dailySeriesLimiter; sl != nil {
|
|
|
|
sl.MustStop()
|
|
|
|
}
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
|
|
|
|
2023-07-22 22:20:21 +00:00
|
|
|
func (s *Storage) mustLoadNextDayMetricIDs(generation, date uint64) *byDateMetricIDEntry {
|
2020-05-11 22:06:17 +00:00
|
|
|
e := &byDateMetricIDEntry{
|
2023-07-22 22:20:21 +00:00
|
|
|
k: generationDateKey{
|
|
|
|
generation: generation,
|
|
|
|
date: date,
|
|
|
|
},
|
2020-05-11 22:06:17 +00:00
|
|
|
}
|
2023-07-22 22:20:21 +00:00
|
|
|
name := "next_day_metric_ids_v2"
|
2023-03-25 21:33:54 +00:00
|
|
|
path := filepath.Join(s.cachePath, name)
|
2020-05-11 22:06:17 +00:00
|
|
|
if !fs.IsPathExist(path) {
|
|
|
|
return e
|
|
|
|
}
|
2022-08-21 20:51:13 +00:00
|
|
|
src, err := os.ReadFile(path)
|
2020-05-11 22:06:17 +00:00
|
|
|
if err != nil {
|
|
|
|
logger.Panicf("FATAL: cannot read %s: %s", path, err)
|
|
|
|
}
|
2023-07-22 22:20:21 +00:00
|
|
|
if len(src) < 24 {
|
|
|
|
logger.Errorf("discarding %s, since it has broken header; got %d bytes; want %d bytes", path, len(src), 24)
|
2020-05-11 22:06:17 +00:00
|
|
|
return e
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unmarshal header
|
2023-07-22 22:20:21 +00:00
|
|
|
generationLoaded := encoding.UnmarshalUint64(src)
|
|
|
|
src = src[8:]
|
|
|
|
if generationLoaded != generation {
|
|
|
|
logger.Infof("discarding %s, since it contains data for stale generation; got %d; want %d", path, generationLoaded, generation)
|
|
|
|
}
|
2020-05-11 22:06:17 +00:00
|
|
|
dateLoaded := encoding.UnmarshalUint64(src)
|
|
|
|
src = src[8:]
|
|
|
|
if dateLoaded != date {
|
|
|
|
logger.Infof("discarding %s, since it contains data for stale date; got %d; want %d", path, dateLoaded, date)
|
|
|
|
return e
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unmarshal uint64set
|
|
|
|
m, tail, err := unmarshalUint64Set(src)
|
|
|
|
if err != nil {
|
|
|
|
logger.Infof("discarding %s because cannot load uint64set: %s", path, err)
|
|
|
|
return e
|
|
|
|
}
|
|
|
|
if len(tail) > 0 {
|
|
|
|
logger.Infof("discarding %s because non-empty tail left; len(tail)=%d", path, len(tail))
|
|
|
|
return e
|
|
|
|
}
|
|
|
|
e.v = *m
|
|
|
|
return e
|
|
|
|
}
|
|
|
|
|
2019-06-14 04:52:32 +00:00
|
|
|
func (s *Storage) mustLoadHourMetricIDs(hour uint64, name string) *hourMetricIDs {
|
2020-05-11 22:06:17 +00:00
|
|
|
hm := &hourMetricIDs{
|
|
|
|
hour: hour,
|
|
|
|
}
|
2023-03-25 21:33:54 +00:00
|
|
|
path := filepath.Join(s.cachePath, name)
|
2019-06-14 04:52:32 +00:00
|
|
|
if !fs.IsPathExist(path) {
|
2020-05-11 22:06:17 +00:00
|
|
|
return hm
|
2019-06-14 04:52:32 +00:00
|
|
|
}
|
2022-08-21 20:51:13 +00:00
|
|
|
src, err := os.ReadFile(path)
|
2019-06-14 04:52:32 +00:00
|
|
|
if err != nil {
|
|
|
|
logger.Panicf("FATAL: cannot read %s: %s", path, err)
|
|
|
|
}
|
2022-11-07 11:57:56 +00:00
|
|
|
if len(src) < 16 {
|
|
|
|
logger.Errorf("discarding %s, since it has broken header; got %d bytes; want %d bytes", path, len(src), 16)
|
2020-05-11 22:06:17 +00:00
|
|
|
return hm
|
2019-06-14 04:52:32 +00:00
|
|
|
}
|
2019-11-08 17:37:16 +00:00
|
|
|
|
|
|
|
// Unmarshal header
|
2019-06-14 04:52:32 +00:00
|
|
|
hourLoaded := encoding.UnmarshalUint64(src)
|
|
|
|
src = src[8:]
|
|
|
|
if hourLoaded != hour {
|
2020-05-11 22:06:17 +00:00
|
|
|
logger.Infof("discarding %s, since it contains outdated hour; got %d; want %d", path, hourLoaded, hour)
|
|
|
|
return hm
|
2019-06-14 04:52:32 +00:00
|
|
|
}
|
2019-11-08 17:37:16 +00:00
|
|
|
|
2020-05-11 22:06:17 +00:00
|
|
|
// Unmarshal uint64set
|
|
|
|
m, tail, err := unmarshalUint64Set(src)
|
|
|
|
if err != nil {
|
|
|
|
logger.Infof("discarding %s because cannot load uint64set: %s", path, err)
|
|
|
|
return hm
|
2019-06-14 04:52:32 +00:00
|
|
|
}
|
2020-05-11 22:06:17 +00:00
|
|
|
if len(tail) > 0 {
|
|
|
|
logger.Infof("discarding %s because non-empty tail left; len(tail)=%d", path, len(tail))
|
|
|
|
return hm
|
2019-06-14 04:52:32 +00:00
|
|
|
}
|
2020-05-11 22:06:17 +00:00
|
|
|
hm.m = m
|
|
|
|
return hm
|
|
|
|
}
|
2019-11-13 11:11:02 +00:00
|
|
|
|
2020-05-11 22:06:17 +00:00
|
|
|
func (s *Storage) mustSaveNextDayMetricIDs(e *byDateMetricIDEntry) {
|
2023-07-22 22:20:21 +00:00
|
|
|
name := "next_day_metric_ids_v2"
|
2023-03-25 21:33:54 +00:00
|
|
|
path := filepath.Join(s.cachePath, name)
|
2020-05-11 22:06:17 +00:00
|
|
|
dst := make([]byte, 0, e.v.Len()*8+16)
|
|
|
|
|
|
|
|
// Marshal header
|
2023-07-22 22:20:21 +00:00
|
|
|
dst = encoding.MarshalUint64(dst, e.k.generation)
|
|
|
|
dst = encoding.MarshalUint64(dst, e.k.date)
|
2020-05-11 22:06:17 +00:00
|
|
|
|
|
|
|
// Marshal e.v
|
|
|
|
dst = marshalUint64Set(dst, &e.v)
|
|
|
|
|
2022-08-21 20:51:13 +00:00
|
|
|
if err := os.WriteFile(path, dst, 0644); err != nil {
|
2020-05-11 22:06:17 +00:00
|
|
|
logger.Panicf("FATAL: cannot write %d bytes to %q: %s", len(dst), path, err)
|
2019-06-14 04:52:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Storage) mustSaveHourMetricIDs(hm *hourMetricIDs, name string) {
|
2023-03-25 21:33:54 +00:00
|
|
|
path := filepath.Join(s.cachePath, name)
|
2019-09-24 18:10:22 +00:00
|
|
|
dst := make([]byte, 0, hm.m.Len()*8+24)
|
2019-11-08 17:37:16 +00:00
|
|
|
|
|
|
|
// Marshal header
|
2019-06-14 04:52:32 +00:00
|
|
|
dst = encoding.MarshalUint64(dst, hm.hour)
|
2019-11-08 17:37:16 +00:00
|
|
|
|
|
|
|
// Marshal hm.m
|
2020-05-11 22:06:17 +00:00
|
|
|
dst = marshalUint64Set(dst, hm.m)
|
2019-11-13 11:11:02 +00:00
|
|
|
|
2022-08-21 20:51:13 +00:00
|
|
|
if err := os.WriteFile(path, dst, 0644); err != nil {
|
2019-06-14 04:52:32 +00:00
|
|
|
logger.Panicf("FATAL: cannot write %d bytes to %q: %s", len(dst), path, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-11 22:06:17 +00:00
|
|
|
func unmarshalUint64Set(src []byte) (*uint64set.Set, []byte, error) {
|
|
|
|
mLen := encoding.UnmarshalUint64(src)
|
|
|
|
src = src[8:]
|
|
|
|
if uint64(len(src)) < 8*mLen {
|
|
|
|
return nil, nil, fmt.Errorf("cannot unmarshal uint64set; got %d bytes; want at least %d bytes", len(src), 8*mLen)
|
|
|
|
}
|
|
|
|
m := &uint64set.Set{}
|
|
|
|
for i := uint64(0); i < mLen; i++ {
|
|
|
|
metricID := encoding.UnmarshalUint64(src)
|
|
|
|
src = src[8:]
|
|
|
|
m.Add(metricID)
|
|
|
|
}
|
|
|
|
return m, src, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func marshalUint64Set(dst []byte, m *uint64set.Set) []byte {
|
|
|
|
dst = encoding.MarshalUint64(dst, uint64(m.Len()))
|
|
|
|
m.ForEach(func(part []uint64) bool {
|
|
|
|
for _, metricID := range part {
|
|
|
|
dst = encoding.MarshalUint64(dst, metricID)
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
return dst
|
|
|
|
}
|
|
|
|
|
2021-02-10 12:37:14 +00:00
|
|
|
func mustGetMinTimestampForCompositeIndex(metadataDir string, isEmptyDB bool) int64 {
|
2023-03-25 21:33:54 +00:00
|
|
|
path := filepath.Join(metadataDir, "minTimestampForCompositeIndex")
|
2021-02-10 12:37:14 +00:00
|
|
|
minTimestamp, err := loadMinTimestampForCompositeIndex(path)
|
|
|
|
if err == nil {
|
|
|
|
return minTimestamp
|
|
|
|
}
|
2021-02-10 13:56:07 +00:00
|
|
|
if !os.IsNotExist(err) {
|
|
|
|
logger.Errorf("cannot read minTimestampForCompositeIndex, so trying to re-create it; error: %s", err)
|
|
|
|
}
|
2021-02-10 12:37:14 +00:00
|
|
|
date := time.Now().UnixNano() / 1e6 / msecPerDay
|
|
|
|
if !isEmptyDB {
|
|
|
|
// The current and the next day can already contain non-composite indexes,
|
|
|
|
// so they cannot be queried with composite indexes.
|
|
|
|
date += 2
|
|
|
|
} else {
|
|
|
|
date = 0
|
|
|
|
}
|
|
|
|
minTimestamp = date * msecPerDay
|
|
|
|
dateBuf := encoding.MarshalInt64(nil, minTimestamp)
|
2023-04-14 05:41:12 +00:00
|
|
|
fs.MustWriteAtomic(path, dateBuf, true)
|
2021-02-10 12:37:14 +00:00
|
|
|
return minTimestamp
|
|
|
|
}
|
|
|
|
|
|
|
|
func loadMinTimestampForCompositeIndex(path string) (int64, error) {
|
2022-08-21 20:51:13 +00:00
|
|
|
data, err := os.ReadFile(path)
|
2021-02-10 12:37:14 +00:00
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
if len(data) != 8 {
|
|
|
|
return 0, fmt.Errorf("unexpected length of %q; got %d bytes; want 8 bytes", path, len(data))
|
|
|
|
}
|
|
|
|
return encoding.UnmarshalInt64(data), nil
|
|
|
|
}
|
|
|
|
|
2023-05-16 22:14:18 +00:00
|
|
|
func (s *Storage) mustLoadCache(name string, sizeBytes int) *workingsetcache.Cache {
|
2023-03-25 21:33:54 +00:00
|
|
|
path := filepath.Join(s.cachePath, name)
|
2023-05-16 22:14:18 +00:00
|
|
|
return workingsetcache.Load(path, sizeBytes)
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
|
|
|
|
2023-05-16 22:14:18 +00:00
|
|
|
func (s *Storage) mustSaveCache(c *workingsetcache.Cache, name string) {
|
2021-06-11 09:42:26 +00:00
|
|
|
saveCacheLock.Lock()
|
|
|
|
defer saveCacheLock.Unlock()
|
|
|
|
|
2023-03-25 21:33:54 +00:00
|
|
|
path := filepath.Join(s.cachePath, name)
|
2019-08-13 18:35:19 +00:00
|
|
|
if err := c.Save(path); err != nil {
|
2023-05-16 22:14:18 +00:00
|
|
|
logger.Panicf("FATAL: cannot save cache to %q: %s", path, err)
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-11 09:42:26 +00:00
|
|
|
// saveCacheLock prevents from data races when multiple concurrent goroutines save the same cache.
|
|
|
|
var saveCacheLock sync.Mutex
|
|
|
|
|
2022-05-25 12:57:01 +00:00
|
|
|
// SetRetentionTimezoneOffset sets the offset, which is used for calculating the time for indexdb rotation.
|
|
|
|
// See https://github.com/VictoriaMetrics/VictoriaMetrics/pull/2574
|
|
|
|
func SetRetentionTimezoneOffset(offset time.Duration) {
|
2023-07-22 22:20:21 +00:00
|
|
|
retentionTimezoneOffsetSecs = int64(offset.Seconds())
|
2022-05-25 12:57:01 +00:00
|
|
|
}
|
|
|
|
|
2023-07-22 22:20:21 +00:00
|
|
|
var retentionTimezoneOffsetSecs int64
|
2022-05-25 12:57:01 +00:00
|
|
|
|
2023-07-22 22:20:21 +00:00
|
|
|
func nextRetentionDeadlineSeconds(atSecs, retentionSecs, offsetSecs int64) int64 {
|
|
|
|
// Round retentionSecs to days. This guarantees that per-day inverted index works as expected
|
|
|
|
const secsPerDay = 24 * 3600
|
|
|
|
retentionSecs = ((retentionSecs + secsPerDay - 1) / secsPerDay) * secsPerDay
|
2023-05-04 15:16:48 +00:00
|
|
|
|
2023-07-22 22:20:21 +00:00
|
|
|
// Schedule the deadline to +4 hours from the next retention period start
|
|
|
|
// because of historical reasons - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/248
|
|
|
|
offsetSecs -= 4 * 3600
|
2023-05-04 15:16:48 +00:00
|
|
|
|
2023-07-22 22:20:21 +00:00
|
|
|
// Make sure that offsetSecs doesn't exceed retentionSecs
|
|
|
|
offsetSecs %= retentionSecs
|
2023-05-16 15:14:08 +00:00
|
|
|
|
2023-07-22 22:20:21 +00:00
|
|
|
// align the retention deadline to multiples of retentionSecs
|
|
|
|
// This makes the deadline independent of atSecs.
|
|
|
|
deadline := ((atSecs + offsetSecs + retentionSecs - 1) / retentionSecs) * retentionSecs
|
|
|
|
|
|
|
|
// Apply the provided offsetSecs
|
|
|
|
deadline -= offsetSecs
|
|
|
|
|
|
|
|
return deadline
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
|
|
|
|
2022-06-28 14:36:27 +00:00
|
|
|
// SearchMetricNames returns marshaled metric names matching the given tfss on the given tr.
|
|
|
|
//
|
|
|
|
// The marshaled metric names must be unmarshaled via MetricName.UnmarshalString().
|
|
|
|
func (s *Storage) SearchMetricNames(qt *querytracer.Tracer, tfss []*TagFilters, tr TimeRange, maxMetrics int, deadline uint64) ([]string, error) {
|
2022-06-09 16:46:26 +00:00
|
|
|
qt = qt.NewChild("search for matching metric names: filters=%s, timeRange=%s", tfss, &tr)
|
2022-06-08 18:05:17 +00:00
|
|
|
defer qt.Done()
|
2024-01-29 14:50:15 +00:00
|
|
|
|
2022-10-23 09:15:24 +00:00
|
|
|
metricIDs, err := s.idb().searchMetricIDs(qt, tfss, tr, maxMetrics, deadline)
|
2020-11-16 08:55:55 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-10-23 09:15:24 +00:00
|
|
|
if len(metricIDs) == 0 {
|
2022-05-31 23:29:19 +00:00
|
|
|
return nil, nil
|
|
|
|
}
|
2022-10-23 09:15:24 +00:00
|
|
|
if err = s.prefetchMetricNames(qt, metricIDs, deadline); err != nil {
|
2020-11-16 08:55:55 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
idb := s.idb()
|
2022-10-23 09:15:24 +00:00
|
|
|
metricNames := make([]string, 0, len(metricIDs))
|
|
|
|
metricNamesSeen := make(map[string]struct{}, len(metricIDs))
|
2020-11-16 08:55:55 +00:00
|
|
|
var metricName []byte
|
2022-10-23 09:15:24 +00:00
|
|
|
for i, metricID := range metricIDs {
|
2021-03-22 21:02:37 +00:00
|
|
|
if i&paceLimiterSlowIterationsMask == 0 {
|
|
|
|
if err := checkSearchDeadlineAndPace(deadline); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
2023-09-22 09:32:59 +00:00
|
|
|
var ok bool
|
|
|
|
metricName, ok = idb.searchMetricNameWithCache(metricName[:0], metricID)
|
|
|
|
if !ok {
|
|
|
|
// Skip missing metricName for metricID.
|
2024-03-27 08:51:03 +00:00
|
|
|
// It should be automatically fixed. See indexDB.searchMetricNameWithCache for details.
|
2023-09-22 09:32:59 +00:00
|
|
|
continue
|
2020-11-16 08:55:55 +00:00
|
|
|
}
|
2022-06-28 14:36:27 +00:00
|
|
|
if _, ok := metricNamesSeen[string(metricName)]; ok {
|
|
|
|
// The given metric name was already seen; skip it
|
|
|
|
continue
|
2020-11-16 08:55:55 +00:00
|
|
|
}
|
2022-06-28 14:36:27 +00:00
|
|
|
metricNames = append(metricNames, string(metricName))
|
|
|
|
metricNamesSeen[metricNames[len(metricNames)-1]] = struct{}{}
|
2020-11-16 08:55:55 +00:00
|
|
|
}
|
2022-06-28 14:36:27 +00:00
|
|
|
qt.Printf("loaded %d metric names", len(metricNames))
|
|
|
|
return metricNames, nil
|
2020-11-16 08:55:55 +00:00
|
|
|
}
|
|
|
|
|
2024-01-23 14:08:38 +00:00
|
|
|
// prefetchMetricNames pre-fetches metric names for the given srcMetricIDs into metricID->metricName cache.
|
2020-01-29 23:59:43 +00:00
|
|
|
//
|
2022-10-23 09:15:24 +00:00
|
|
|
// This should speed-up further searchMetricNameWithCache calls for srcMetricIDs from tsids.
|
2024-01-23 14:08:38 +00:00
|
|
|
//
|
|
|
|
// It is expected that srcMetricIDs are already sorted by the caller. Otherwise the pre-fetching may be slow.
|
2022-10-23 09:15:24 +00:00
|
|
|
func (s *Storage) prefetchMetricNames(qt *querytracer.Tracer, srcMetricIDs []uint64, deadline uint64) error {
|
|
|
|
qt = qt.NewChild("prefetch metric names for %d metricIDs", len(srcMetricIDs))
|
2022-06-08 18:05:17 +00:00
|
|
|
defer qt.Done()
|
2024-01-17 11:46:24 +00:00
|
|
|
|
|
|
|
if len(srcMetricIDs) < 500 {
|
|
|
|
qt.Printf("skip pre-fetching metric names for low number of metric ids=%d", len(srcMetricIDs))
|
2020-07-23 16:21:49 +00:00
|
|
|
return nil
|
|
|
|
}
|
2024-01-16 13:26:08 +00:00
|
|
|
|
2024-01-23 14:08:38 +00:00
|
|
|
var metricIDs []uint64
|
2024-01-16 13:26:08 +00:00
|
|
|
s.prefetchedMetricIDsLock.Lock()
|
|
|
|
prefetchedMetricIDs := s.prefetchedMetricIDs
|
2022-10-23 09:15:24 +00:00
|
|
|
for _, metricID := range srcMetricIDs {
|
2020-01-29 23:59:43 +00:00
|
|
|
if prefetchedMetricIDs.Has(metricID) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
metricIDs = append(metricIDs, metricID)
|
|
|
|
}
|
2024-01-16 13:26:08 +00:00
|
|
|
s.prefetchedMetricIDsLock.Unlock()
|
|
|
|
|
2022-10-23 09:15:24 +00:00
|
|
|
qt.Printf("%d out of %d metric names must be pre-fetched", len(metricIDs), len(srcMetricIDs))
|
2020-01-29 23:59:43 +00:00
|
|
|
if len(metricIDs) < 500 {
|
|
|
|
// It is cheaper to skip pre-fetching and obtain metricNames inline.
|
2024-01-17 11:46:24 +00:00
|
|
|
qt.Printf("skip pre-fetching metric names for low number of missing metric ids=%d", len(metricIDs))
|
2020-01-29 23:59:43 +00:00
|
|
|
return nil
|
|
|
|
}
|
2024-02-23 22:15:21 +00:00
|
|
|
s.slowMetricNameLoads.Add(uint64(len(metricIDs)))
|
2020-01-29 23:59:43 +00:00
|
|
|
|
|
|
|
// Pre-fetch metricIDs.
|
2021-03-22 20:41:47 +00:00
|
|
|
var missingMetricIDs []uint64
|
2020-01-29 23:59:43 +00:00
|
|
|
var metricName []byte
|
|
|
|
var err error
|
2020-01-30 22:54:28 +00:00
|
|
|
idb := s.idb()
|
2020-07-23 17:42:57 +00:00
|
|
|
is := idb.getIndexSearch(deadline)
|
2020-01-30 22:54:28 +00:00
|
|
|
defer idb.putIndexSearch(is)
|
2020-07-23 16:21:49 +00:00
|
|
|
for loops, metricID := range metricIDs {
|
2020-08-07 05:37:33 +00:00
|
|
|
if loops&paceLimiterSlowIterationsMask == 0 {
|
2020-07-23 17:42:57 +00:00
|
|
|
if err := checkSearchDeadlineAndPace(is.deadline); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-07-23 16:21:49 +00:00
|
|
|
}
|
2023-09-22 09:32:59 +00:00
|
|
|
var ok bool
|
|
|
|
metricName, ok = is.searchMetricNameWithCache(metricName[:0], metricID)
|
|
|
|
if !ok {
|
|
|
|
missingMetricIDs = append(missingMetricIDs, metricID)
|
|
|
|
continue
|
2020-01-29 23:59:43 +00:00
|
|
|
}
|
|
|
|
}
|
2021-03-22 20:41:47 +00:00
|
|
|
idb.doExtDB(func(extDB *indexDB) {
|
|
|
|
is := extDB.getIndexSearch(deadline)
|
|
|
|
defer extDB.putIndexSearch(is)
|
|
|
|
for loops, metricID := range missingMetricIDs {
|
|
|
|
if loops&paceLimiterSlowIterationsMask == 0 {
|
|
|
|
if err = checkSearchDeadlineAndPace(is.deadline); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2023-09-22 09:32:59 +00:00
|
|
|
metricName, _ = is.searchMetricNameWithCache(metricName[:0], metricID)
|
2021-03-22 20:41:47 +00:00
|
|
|
}
|
|
|
|
})
|
2023-03-12 07:29:43 +00:00
|
|
|
if err != nil && err != io.EOF {
|
2021-03-22 20:41:47 +00:00
|
|
|
return err
|
|
|
|
}
|
2022-05-31 23:29:19 +00:00
|
|
|
qt.Printf("pre-fetch metric names for %d metric ids", len(metricIDs))
|
2020-01-29 23:59:43 +00:00
|
|
|
|
|
|
|
// Store the pre-fetched metricIDs, so they aren't pre-fetched next time.
|
2021-07-07 07:27:47 +00:00
|
|
|
s.prefetchedMetricIDsLock.Lock()
|
2024-02-23 22:15:21 +00:00
|
|
|
if fasttime.UnixTimestamp() > s.prefetchedMetricIDsDeadline.Load() {
|
2021-07-07 07:27:47 +00:00
|
|
|
// Periodically reset the prefetchedMetricIDs in order to limit its size.
|
2024-01-16 13:26:08 +00:00
|
|
|
s.prefetchedMetricIDs = &uint64set.Set{}
|
2024-01-22 16:12:37 +00:00
|
|
|
d := timeutil.AddJitterToDuration(time.Second * 20 * 60)
|
|
|
|
metricIDsDeadline := fasttime.UnixTimestamp() + uint64(d.Seconds())
|
2024-02-23 22:15:21 +00:00
|
|
|
s.prefetchedMetricIDsDeadline.Store(metricIDsDeadline)
|
2020-08-06 13:48:21 +00:00
|
|
|
}
|
2024-01-16 13:26:08 +00:00
|
|
|
s.prefetchedMetricIDs.AddMulti(metricIDs)
|
2021-07-07 07:27:47 +00:00
|
|
|
s.prefetchedMetricIDsLock.Unlock()
|
2024-01-16 13:26:08 +00:00
|
|
|
|
2022-05-31 23:29:19 +00:00
|
|
|
qt.Printf("cache metric ids for pre-fetched metric names")
|
2020-01-29 23:59:43 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-08-10 10:17:12 +00:00
|
|
|
// ErrDeadlineExceeded is returned when the request times out.
|
|
|
|
var ErrDeadlineExceeded = fmt.Errorf("deadline exceeded")
|
2020-07-23 17:42:57 +00:00
|
|
|
|
2022-07-05 20:56:31 +00:00
|
|
|
// DeleteSeries deletes all the series matching the given tfss.
|
2019-05-22 21:16:55 +00:00
|
|
|
//
|
|
|
|
// Returns the number of metrics deleted.
|
2022-07-05 20:56:31 +00:00
|
|
|
func (s *Storage) DeleteSeries(qt *querytracer.Tracer, tfss []*TagFilters) (int, error) {
|
2022-06-27 09:53:46 +00:00
|
|
|
deletedCount, err := s.idb().DeleteTSIDs(qt, tfss)
|
2019-05-22 21:16:55 +00:00
|
|
|
if err != nil {
|
2020-06-30 19:58:18 +00:00
|
|
|
return deletedCount, fmt.Errorf("cannot delete tsids: %w", err)
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
// Do not reset MetricName->TSID cache, since it is already reset inside DeleteTSIDs.
|
2020-07-06 18:56:14 +00:00
|
|
|
|
2020-07-14 11:02:14 +00:00
|
|
|
// Do not reset MetricID->MetricName cache, since it must be used only
|
2019-05-22 21:16:55 +00:00
|
|
|
// after filtering out deleted metricIDs.
|
2020-07-06 18:56:14 +00:00
|
|
|
|
2019-05-22 21:16:55 +00:00
|
|
|
return deletedCount, nil
|
|
|
|
}
|
|
|
|
|
2022-06-12 01:32:13 +00:00
|
|
|
// SearchLabelNamesWithFiltersOnTimeRange searches for label names matching the given tfss on tr.
|
2023-04-10 17:16:36 +00:00
|
|
|
func (s *Storage) SearchLabelNamesWithFiltersOnTimeRange(qt *querytracer.Tracer, tfss []*TagFilters, tr TimeRange, maxLabelNames, maxMetrics int, deadline uint64,
|
|
|
|
) ([]string, error) {
|
2022-06-12 01:32:13 +00:00
|
|
|
return s.idb().SearchLabelNamesWithFiltersOnTimeRange(qt, tfss, tr, maxLabelNames, maxMetrics, deadline)
|
2020-11-04 22:15:43 +00:00
|
|
|
}
|
|
|
|
|
2022-06-12 01:32:13 +00:00
|
|
|
// SearchLabelValuesWithFiltersOnTimeRange searches for label values for the given labelName, filters and tr.
|
|
|
|
func (s *Storage) SearchLabelValuesWithFiltersOnTimeRange(qt *querytracer.Tracer, labelName string, tfss []*TagFilters,
|
2023-04-10 17:16:36 +00:00
|
|
|
tr TimeRange, maxLabelValues, maxMetrics int, deadline uint64,
|
|
|
|
) ([]string, error) {
|
2024-04-17 23:10:07 +00:00
|
|
|
idb := s.idb()
|
|
|
|
|
|
|
|
key := labelName
|
|
|
|
if key == "__name__" {
|
|
|
|
key = ""
|
|
|
|
}
|
|
|
|
if len(tfss) == 1 && len(tfss[0].tfs) == 1 && string(tfss[0].tfs[0].key) == key {
|
|
|
|
// tfss contains only a single filter on labelName. It is faster searching for label values
|
2024-04-18 18:29:33 +00:00
|
|
|
// without any filters and limits and then later applying the filter and the limit to the found label values.
|
|
|
|
qt.Printf("search for up to %d values for the label %q on the time range %s", maxMetrics, labelName, &tr)
|
|
|
|
lvs, err := idb.SearchLabelValuesWithFiltersOnTimeRange(qt, labelName, nil, tr, maxMetrics, maxMetrics, deadline)
|
2024-04-17 23:10:07 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-04-18 18:29:33 +00:00
|
|
|
needSlowSearch := len(lvs) == maxMetrics
|
|
|
|
|
|
|
|
lvsLen := len(lvs)
|
2024-04-17 23:10:07 +00:00
|
|
|
lvs = filterLabelValues(lvs, &tfss[0].tfs[0], key)
|
2024-04-18 18:29:33 +00:00
|
|
|
qt.Printf("found %d out of %d values for the label %q after filtering", len(lvs), lvsLen, labelName)
|
|
|
|
if len(lvs) >= maxLabelValues {
|
|
|
|
qt.Printf("leave %d out of %d values for the label %q because of the limit", maxLabelValues, len(lvs), labelName)
|
|
|
|
lvs = lvs[:maxLabelValues]
|
|
|
|
|
|
|
|
// We found at least maxLabelValues unique values for the label with the given filters.
|
|
|
|
// It is OK returning all these values instead of falling back to the slow search.
|
|
|
|
needSlowSearch = false
|
|
|
|
}
|
|
|
|
if !needSlowSearch {
|
|
|
|
return lvs, nil
|
|
|
|
}
|
|
|
|
qt.Printf("fall back to slow search because only a subset of label values is found")
|
2024-04-17 23:10:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return idb.SearchLabelValuesWithFiltersOnTimeRange(qt, labelName, tfss, tr, maxLabelValues, maxMetrics, deadline)
|
|
|
|
}
|
|
|
|
|
|
|
|
func filterLabelValues(lvs []string, tf *tagFilter, key string) []string {
|
|
|
|
var b []byte
|
|
|
|
result := lvs[:0]
|
|
|
|
for _, lv := range lvs {
|
|
|
|
b = marshalCommonPrefix(b[:0], nsPrefixTagToMetricIDs)
|
|
|
|
b = marshalTagValue(b, bytesutil.ToUnsafeBytes(key))
|
|
|
|
b = marshalTagValue(b, bytesutil.ToUnsafeBytes(lv))
|
|
|
|
ok, err := tf.match(b)
|
|
|
|
if err != nil {
|
|
|
|
logger.Panicf("BUG: cannot match label %q=%q with tagFilter %s: %w", key, lv, tf.String(), err)
|
|
|
|
}
|
|
|
|
if ok {
|
|
|
|
result = append(result, lv)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
|
|
|
|
2020-09-10 21:28:19 +00:00
|
|
|
// SearchTagValueSuffixes returns all the tag value suffixes for the given tagKey and tagValuePrefix on the given tr.
|
|
|
|
//
|
|
|
|
// This allows implementing https://graphite-api.readthedocs.io/en/latest/api.html#metrics-find or similar APIs.
|
2021-02-02 22:24:05 +00:00
|
|
|
//
|
|
|
|
// If more than maxTagValueSuffixes suffixes is found, then only the first maxTagValueSuffixes suffixes is returned.
|
2022-07-05 20:47:46 +00:00
|
|
|
func (s *Storage) SearchTagValueSuffixes(qt *querytracer.Tracer, tr TimeRange, tagKey, tagValuePrefix string,
|
2023-04-10 17:16:36 +00:00
|
|
|
delimiter byte, maxTagValueSuffixes int, deadline uint64,
|
|
|
|
) ([]string, error) {
|
2022-06-27 09:53:46 +00:00
|
|
|
return s.idb().SearchTagValueSuffixes(qt, tr, tagKey, tagValuePrefix, delimiter, maxTagValueSuffixes, deadline)
|
2020-09-10 21:28:19 +00:00
|
|
|
}
|
|
|
|
|
2021-02-02 22:24:05 +00:00
|
|
|
// SearchGraphitePaths returns all the matching paths for the given graphite query on the given tr.
|
2022-06-27 09:53:46 +00:00
|
|
|
func (s *Storage) SearchGraphitePaths(qt *querytracer.Tracer, tr TimeRange, query []byte, maxPaths int, deadline uint64) ([]string, error) {
|
2021-12-14 17:51:46 +00:00
|
|
|
query = replaceAlternateRegexpsWithGraphiteWildcards(query)
|
2022-06-27 09:53:46 +00:00
|
|
|
return s.searchGraphitePaths(qt, tr, nil, query, maxPaths, deadline)
|
2021-03-18 12:52:49 +00:00
|
|
|
}
|
|
|
|
|
2021-12-14 17:51:46 +00:00
|
|
|
// replaceAlternateRegexpsWithGraphiteWildcards replaces (foo|..|bar) with {foo,...,bar} in b and returns the new value.
|
|
|
|
func replaceAlternateRegexpsWithGraphiteWildcards(b []byte) []byte {
|
|
|
|
var dst []byte
|
|
|
|
for {
|
|
|
|
n := bytes.IndexByte(b, '(')
|
|
|
|
if n < 0 {
|
|
|
|
if len(dst) == 0 {
|
|
|
|
// Fast path - b doesn't contain the openining brace.
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
dst = append(dst, b...)
|
|
|
|
return dst
|
|
|
|
}
|
|
|
|
dst = append(dst, b[:n]...)
|
|
|
|
b = b[n+1:]
|
|
|
|
n = bytes.IndexByte(b, ')')
|
|
|
|
if n < 0 {
|
|
|
|
dst = append(dst, '(')
|
|
|
|
dst = append(dst, b...)
|
|
|
|
return dst
|
|
|
|
}
|
|
|
|
x := b[:n]
|
|
|
|
b = b[n+1:]
|
|
|
|
if string(x) == ".*" {
|
|
|
|
dst = append(dst, '*')
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
dst = append(dst, '{')
|
|
|
|
for len(x) > 0 {
|
|
|
|
n = bytes.IndexByte(x, '|')
|
|
|
|
if n < 0 {
|
|
|
|
dst = append(dst, x...)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
dst = append(dst, x[:n]...)
|
|
|
|
x = x[n+1:]
|
|
|
|
dst = append(dst, ',')
|
|
|
|
}
|
|
|
|
dst = append(dst, '}')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-27 09:53:46 +00:00
|
|
|
func (s *Storage) searchGraphitePaths(qt *querytracer.Tracer, tr TimeRange, qHead, qTail []byte, maxPaths int, deadline uint64) ([]string, error) {
|
2021-03-18 13:21:13 +00:00
|
|
|
n := bytes.IndexAny(qTail, "*[{")
|
2021-02-02 22:24:05 +00:00
|
|
|
if n < 0 {
|
2021-03-18 12:52:49 +00:00
|
|
|
// Verify that qHead matches a metric name.
|
|
|
|
qHead = append(qHead, qTail...)
|
2022-07-05 20:47:46 +00:00
|
|
|
suffixes, err := s.SearchTagValueSuffixes(qt, tr, "", bytesutil.ToUnsafeString(qHead), '.', 1, deadline)
|
2021-02-02 22:24:05 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if len(suffixes) == 0 {
|
|
|
|
// The query doesn't match anything.
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
if len(suffixes[0]) > 0 {
|
|
|
|
// The query matches a metric name with additional suffix.
|
|
|
|
return nil, nil
|
|
|
|
}
|
2021-03-18 12:52:49 +00:00
|
|
|
return []string{string(qHead)}, nil
|
2021-02-02 22:24:05 +00:00
|
|
|
}
|
2021-03-18 12:52:49 +00:00
|
|
|
qHead = append(qHead, qTail[:n]...)
|
2022-07-05 20:47:46 +00:00
|
|
|
suffixes, err := s.SearchTagValueSuffixes(qt, tr, "", bytesutil.ToUnsafeString(qHead), '.', maxPaths, deadline)
|
2021-02-02 22:24:05 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if len(suffixes) == 0 {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
if len(suffixes) >= maxPaths {
|
|
|
|
return nil, fmt.Errorf("more than maxPaths=%d suffixes found", maxPaths)
|
|
|
|
}
|
2021-03-18 12:52:49 +00:00
|
|
|
qNode := qTail[n:]
|
|
|
|
qTail = nil
|
2021-02-02 22:24:05 +00:00
|
|
|
mustMatchLeafs := true
|
2021-03-18 12:52:49 +00:00
|
|
|
if m := bytes.IndexByte(qNode, '.'); m >= 0 {
|
2021-02-02 22:24:05 +00:00
|
|
|
qTail = qNode[m+1:]
|
2021-02-03 16:45:42 +00:00
|
|
|
qNode = qNode[:m+1]
|
2021-02-02 22:24:05 +00:00
|
|
|
mustMatchLeafs = false
|
|
|
|
}
|
2021-03-18 12:52:49 +00:00
|
|
|
re, err := getRegexpForGraphiteQuery(string(qNode))
|
2021-02-02 22:24:05 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-03-18 12:52:49 +00:00
|
|
|
qHeadLen := len(qHead)
|
2021-02-02 22:24:05 +00:00
|
|
|
var paths []string
|
|
|
|
for _, suffix := range suffixes {
|
|
|
|
if len(paths) > maxPaths {
|
2021-03-18 12:52:49 +00:00
|
|
|
return nil, fmt.Errorf("more than maxPath=%d paths found", maxPaths)
|
2021-02-02 22:24:05 +00:00
|
|
|
}
|
|
|
|
if !re.MatchString(suffix) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if mustMatchLeafs {
|
2021-03-18 12:52:49 +00:00
|
|
|
qHead = append(qHead[:qHeadLen], suffix...)
|
|
|
|
paths = append(paths, string(qHead))
|
2021-02-02 22:24:05 +00:00
|
|
|
continue
|
|
|
|
}
|
2021-03-18 12:52:49 +00:00
|
|
|
qHead = append(qHead[:qHeadLen], suffix...)
|
2022-06-27 09:53:46 +00:00
|
|
|
ps, err := s.searchGraphitePaths(qt, tr, qHead, qTail, maxPaths, deadline)
|
2021-02-02 22:24:05 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
paths = append(paths, ps...)
|
|
|
|
}
|
|
|
|
return paths, nil
|
|
|
|
}
|
|
|
|
|
2021-02-03 18:12:17 +00:00
|
|
|
func getRegexpForGraphiteQuery(q string) (*regexp.Regexp, error) {
|
|
|
|
parts, tail := getRegexpPartsForGraphiteQuery(q)
|
|
|
|
if len(tail) > 0 {
|
|
|
|
return nil, fmt.Errorf("unexpected tail left after parsing %q: %q", q, tail)
|
|
|
|
}
|
2021-02-02 22:24:05 +00:00
|
|
|
reStr := "^" + strings.Join(parts, "") + "$"
|
2023-01-10 05:43:04 +00:00
|
|
|
return metricsql.CompileRegexp(reStr)
|
2021-02-02 22:24:05 +00:00
|
|
|
}
|
|
|
|
|
2021-02-03 18:12:17 +00:00
|
|
|
func getRegexpPartsForGraphiteQuery(q string) ([]string, string) {
|
2021-02-02 22:24:05 +00:00
|
|
|
var parts []string
|
|
|
|
for {
|
2021-02-03 18:12:17 +00:00
|
|
|
n := strings.IndexAny(q, "*{}[,")
|
2021-02-02 22:24:05 +00:00
|
|
|
if n < 0 {
|
2021-02-03 18:12:17 +00:00
|
|
|
parts = append(parts, regexp.QuoteMeta(q))
|
|
|
|
return parts, ""
|
2021-02-02 22:24:05 +00:00
|
|
|
}
|
|
|
|
parts = append(parts, regexp.QuoteMeta(q[:n]))
|
|
|
|
q = q[n:]
|
|
|
|
switch q[0] {
|
2021-02-03 18:12:17 +00:00
|
|
|
case ',', '}':
|
|
|
|
return parts, q
|
2021-02-02 22:24:05 +00:00
|
|
|
case '*':
|
|
|
|
parts = append(parts, "[^.]*")
|
|
|
|
q = q[1:]
|
|
|
|
case '{':
|
|
|
|
var tmp []string
|
2021-02-03 18:12:17 +00:00
|
|
|
for {
|
|
|
|
a, tail := getRegexpPartsForGraphiteQuery(q[1:])
|
|
|
|
tmp = append(tmp, strings.Join(a, ""))
|
|
|
|
if len(tail) == 0 {
|
|
|
|
parts = append(parts, regexp.QuoteMeta("{"))
|
|
|
|
parts = append(parts, strings.Join(tmp, ","))
|
|
|
|
return parts, ""
|
|
|
|
}
|
|
|
|
if tail[0] == ',' {
|
|
|
|
q = tail
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if tail[0] == '}' {
|
|
|
|
if len(tmp) == 1 {
|
|
|
|
parts = append(parts, tmp[0])
|
|
|
|
} else {
|
|
|
|
parts = append(parts, "(?:"+strings.Join(tmp, "|")+")")
|
|
|
|
}
|
|
|
|
q = tail[1:]
|
|
|
|
break
|
|
|
|
}
|
|
|
|
logger.Panicf("BUG: unexpected first char at tail %q; want `.` or `}`", tail)
|
2021-02-02 22:24:05 +00:00
|
|
|
}
|
|
|
|
case '[':
|
|
|
|
n := strings.IndexByte(q, ']')
|
|
|
|
if n < 0 {
|
2021-02-03 18:12:17 +00:00
|
|
|
parts = append(parts, regexp.QuoteMeta(q))
|
|
|
|
return parts, ""
|
2021-02-02 22:24:05 +00:00
|
|
|
}
|
|
|
|
parts = append(parts, q[:n+1])
|
|
|
|
q = q[n+1:]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-22 21:16:55 +00:00
|
|
|
// GetSeriesCount returns the approximate number of unique time series.
|
|
|
|
//
|
|
|
|
// It includes the deleted series too and may count the same series
|
|
|
|
// up to two times - in db and extDB.
|
2020-07-23 17:42:57 +00:00
|
|
|
func (s *Storage) GetSeriesCount(deadline uint64) (uint64, error) {
|
|
|
|
return s.idb().GetSeriesCount(deadline)
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
|
|
|
|
2022-06-14 14:46:16 +00:00
|
|
|
// GetTSDBStatus returns TSDB status data for /api/v1/status/tsdb
|
|
|
|
func (s *Storage) GetTSDBStatus(qt *querytracer.Tracer, tfss []*TagFilters, date uint64, focusLabel string, topN, maxMetrics int, deadline uint64) (*TSDBStatus, error) {
|
|
|
|
return s.idb().GetTSDBStatus(qt, tfss, date, focusLabel, topN, maxMetrics, deadline)
|
2021-05-12 12:18:45 +00:00
|
|
|
}
|
|
|
|
|
2019-05-22 21:16:55 +00:00
|
|
|
// MetricRow is a metric to insert into storage.
|
|
|
|
type MetricRow struct {
|
|
|
|
// MetricNameRaw contains raw metric name, which must be decoded
|
2021-05-08 14:55:44 +00:00
|
|
|
// with MetricName.UnmarshalRaw.
|
2019-05-22 21:16:55 +00:00
|
|
|
MetricNameRaw []byte
|
|
|
|
|
|
|
|
Timestamp int64
|
|
|
|
Value float64
|
|
|
|
}
|
|
|
|
|
|
|
|
// CopyFrom copies src to mr.
|
|
|
|
func (mr *MetricRow) CopyFrom(src *MetricRow) {
|
|
|
|
mr.MetricNameRaw = append(mr.MetricNameRaw[:0], src.MetricNameRaw...)
|
|
|
|
mr.Timestamp = src.Timestamp
|
|
|
|
mr.Value = src.Value
|
|
|
|
}
|
|
|
|
|
|
|
|
// String returns string representation of the mr.
|
|
|
|
func (mr *MetricRow) String() string {
|
|
|
|
metricName := string(mr.MetricNameRaw)
|
|
|
|
var mn MetricName
|
2021-05-08 14:55:44 +00:00
|
|
|
if err := mn.UnmarshalRaw(mr.MetricNameRaw); err == nil {
|
2019-05-22 21:16:55 +00:00
|
|
|
metricName = mn.String()
|
|
|
|
}
|
2021-03-25 19:30:41 +00:00
|
|
|
return fmt.Sprintf("%s (Timestamp=%d, Value=%f)", metricName, mr.Timestamp, mr.Value)
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Marshal appends marshaled mr to dst and returns the result.
|
|
|
|
func (mr *MetricRow) Marshal(dst []byte) []byte {
|
|
|
|
dst = encoding.MarshalBytes(dst, mr.MetricNameRaw)
|
|
|
|
dst = encoding.MarshalUint64(dst, uint64(mr.Timestamp))
|
|
|
|
dst = encoding.MarshalUint64(dst, math.Float64bits(mr.Value))
|
|
|
|
return dst
|
|
|
|
}
|
|
|
|
|
2021-05-08 14:55:44 +00:00
|
|
|
// UnmarshalX unmarshals mr from src and returns the remaining tail from src.
|
|
|
|
//
|
|
|
|
// mr refers to src, so it remains valid until src changes.
|
|
|
|
func (mr *MetricRow) UnmarshalX(src []byte) ([]byte, error) {
|
2024-05-13 23:23:44 +00:00
|
|
|
metricNameRaw, nSize := encoding.UnmarshalBytes(src)
|
|
|
|
if nSize <= 0 {
|
|
|
|
return src, fmt.Errorf("cannot unmarshal MetricName")
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
2024-05-13 23:23:44 +00:00
|
|
|
tail := src[nSize:]
|
2021-05-08 14:55:44 +00:00
|
|
|
mr.MetricNameRaw = metricNameRaw
|
2019-05-22 21:16:55 +00:00
|
|
|
|
|
|
|
if len(tail) < 8 {
|
|
|
|
return tail, fmt.Errorf("cannot unmarshal Timestamp: want %d bytes; have %d bytes", 8, len(tail))
|
|
|
|
}
|
|
|
|
timestamp := encoding.UnmarshalUint64(tail)
|
|
|
|
tail = tail[8:]
|
2024-05-13 23:23:44 +00:00
|
|
|
mr.Timestamp = int64(timestamp)
|
2019-05-22 21:16:55 +00:00
|
|
|
|
|
|
|
if len(tail) < 8 {
|
|
|
|
return tail, fmt.Errorf("cannot unmarshal Value: want %d bytes; have %d bytes", 8, len(tail))
|
|
|
|
}
|
|
|
|
value := encoding.UnmarshalUint64(tail)
|
|
|
|
tail = tail[8:]
|
2024-05-13 23:23:44 +00:00
|
|
|
mr.Value = math.Float64frombits(value)
|
2019-05-22 21:16:55 +00:00
|
|
|
|
|
|
|
return tail, nil
|
|
|
|
}
|
|
|
|
|
2020-09-17 09:01:53 +00:00
|
|
|
// ForceMergePartitions force-merges partitions in s with names starting from the given partitionNamePrefix.
|
|
|
|
//
|
|
|
|
// Partitions are merged sequentially in order to reduce load on the system.
|
|
|
|
func (s *Storage) ForceMergePartitions(partitionNamePrefix string) error {
|
|
|
|
return s.tb.ForceMergePartitions(partitionNamePrefix)
|
|
|
|
}
|
|
|
|
|
2019-05-22 21:16:55 +00:00
|
|
|
// AddRows adds the given mrs to s.
|
2023-01-07 02:59:39 +00:00
|
|
|
//
|
|
|
|
// The caller should limit the number of concurrent AddRows calls to the number
|
|
|
|
// of available CPU cores in order to limit memory usage.
|
2024-07-17 10:07:14 +00:00
|
|
|
func (s *Storage) AddRows(mrs []MetricRow, precisionBits uint8) {
|
2019-05-22 21:16:55 +00:00
|
|
|
if len(mrs) == 0 {
|
2024-07-17 10:07:14 +00:00
|
|
|
return
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
|
|
|
|
2021-05-24 12:24:04 +00:00
|
|
|
// Add rows to the storage in blocks with limited size in order to reduce memory usage.
|
|
|
|
ic := getMetricRowsInsertCtx()
|
|
|
|
maxBlockLen := len(ic.rrs)
|
2021-05-24 12:30:39 +00:00
|
|
|
for len(mrs) > 0 {
|
2021-05-24 12:24:04 +00:00
|
|
|
mrsBlock := mrs
|
|
|
|
if len(mrs) > maxBlockLen {
|
|
|
|
mrsBlock = mrs[:maxBlockLen]
|
|
|
|
mrs = mrs[maxBlockLen:]
|
|
|
|
} else {
|
|
|
|
mrs = nil
|
|
|
|
}
|
2024-09-06 15:57:21 +00:00
|
|
|
rowsAdded := s.add(ic.rrs, ic.tmpMrs, mrsBlock, precisionBits)
|
|
|
|
|
|
|
|
// If the number of received rows is greater than the number of added
|
|
|
|
// rows, then some rows have failed to add. Check logs for the first
|
|
|
|
// error.
|
|
|
|
s.rowsAddedTotal.Add(uint64(rowsAdded))
|
|
|
|
s.rowsReceivedTotal.Add(uint64(len(mrsBlock)))
|
2021-05-24 12:24:04 +00:00
|
|
|
}
|
|
|
|
putMetricRowsInsertCtx(ic)
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
|
|
|
|
2021-05-24 12:24:04 +00:00
|
|
|
type metricRowsInsertCtx struct {
|
|
|
|
rrs []rawRow
|
|
|
|
tmpMrs []*MetricRow
|
|
|
|
}
|
|
|
|
|
|
|
|
func getMetricRowsInsertCtx() *metricRowsInsertCtx {
|
|
|
|
v := metricRowsInsertCtxPool.Get()
|
|
|
|
if v == nil {
|
|
|
|
v = &metricRowsInsertCtx{
|
|
|
|
rrs: make([]rawRow, maxMetricRowsPerBlock),
|
|
|
|
tmpMrs: make([]*MetricRow, maxMetricRowsPerBlock),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return v.(*metricRowsInsertCtx)
|
|
|
|
}
|
|
|
|
|
|
|
|
func putMetricRowsInsertCtx(ic *metricRowsInsertCtx) {
|
|
|
|
tmpMrs := ic.tmpMrs
|
|
|
|
for i := range tmpMrs {
|
|
|
|
tmpMrs[i] = nil
|
|
|
|
}
|
|
|
|
metricRowsInsertCtxPool.Put(ic)
|
|
|
|
}
|
|
|
|
|
|
|
|
var metricRowsInsertCtxPool sync.Pool
|
|
|
|
|
|
|
|
const maxMetricRowsPerBlock = 8000
|
|
|
|
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
// RegisterMetricNames registers all the metric names from mrs in the indexdb, so they can be queried later.
|
2020-11-15 22:42:27 +00:00
|
|
|
//
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
// The the MetricRow.Timestamp is used for registering the metric name at the given day according to the timestamp.
|
2020-11-15 22:42:27 +00:00
|
|
|
// Th MetricRow.Value field is ignored.
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
func (s *Storage) RegisterMetricNames(qt *querytracer.Tracer, mrs []MetricRow) {
|
2022-06-27 09:53:46 +00:00
|
|
|
qt = qt.NewChild("registering %d series", len(mrs))
|
|
|
|
defer qt.Done()
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
var metricNameBuf []byte
|
lib/index: reduce read/write load after indexDB rotation (#2177)
* lib/index: reduce read/write load after indexDB rotation
IndexDB in VM is responsible for storing TSID - ID's used for identifying
time series. The index is stored on disk and used by both ingestion and read path.
IndexDB is stored separately to data parts and is global for all stored data.
It can't be deleted partially as VM deletes data parts. Instead, indexDB is
rotated once in `retention` interval.
The rotation procedure means that `current` indexDB becomes `previous`,
and new freshly created indexDB struct becomes `current`. So in any time,
VM holds indexDB for current and previous retention periods.
When time series is ingested or queried, VM checks if its TSID is present
in `current` indexDB. If it is missing, it checks the `previous` indexDB.
If TSID was found, it gets copied to the `current` indexDB. In this way
`current` indexDB stores only series which were active during the retention
period.
To improve indexDB lookups, VM uses a cache layer called `tsidCache`. Both
write and read path consult `tsidCache` and on miss the relad lookup happens.
When rotation happens, VM resets the `tsidCache`. This is needed for ingestion
path to trigger `current` indexDB re-population. Since index re-population
requires additional resources, every index rotation event may cause some extra
load on CPU and disk. While it may be unnoticeable for most of the cases,
for systems with very high number of unique series each rotation may lead
to performance degradation for some period of time.
This PR makes an attempt to smooth out resource usage after the rotation.
The changes are following:
1. `tsidCache` is no longer reset after the rotation;
2. Instead, each entry in `tsidCache` gains a notion of indexDB to which
they belong;
3. On ingestion path after the rotation we check if requested TSID was
found in `tsidCache`. Then we have 3 branches:
3.1 Fast path. It was found, and belongs to the `current` indexDB. Return TSID.
3.2 Slow path. It wasn't found, so we generate it from scratch,
add to `current` indexDB, add it to `tsidCache`.
3.3 Smooth path. It was found but does not belong to the `current` indexDB.
In this case, we add it to the `current` indexDB with some probability.
The probability is based on time passed since the last rotation with some threshold.
The more time has passed since rotation the higher is chance to re-populate `current` indexDB.
The default re-population interval in this PR is set to `1h`, during which entries from
`previous` index supposed to slowly re-populate `current` index.
The new metric `vm_timeseries_repopulated_total` was added to identify how many TSIDs
were moved from `previous` indexDB to the `current` indexDB. This metric supposed to
grow only during the first `1h` after the last rotation.
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401
Signed-off-by: hagen1778 <roman@victoriametrics.com>
* wip
* wip
Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
2022-02-11 22:30:08 +00:00
|
|
|
var genTSID generationTSID
|
2021-05-23 13:39:55 +00:00
|
|
|
mn := GetMetricName()
|
|
|
|
defer PutMetricName(mn)
|
lib/index: reduce read/write load after indexDB rotation (#2177)
* lib/index: reduce read/write load after indexDB rotation
IndexDB in VM is responsible for storing TSID - ID's used for identifying
time series. The index is stored on disk and used by both ingestion and read path.
IndexDB is stored separately to data parts and is global for all stored data.
It can't be deleted partially as VM deletes data parts. Instead, indexDB is
rotated once in `retention` interval.
The rotation procedure means that `current` indexDB becomes `previous`,
and new freshly created indexDB struct becomes `current`. So in any time,
VM holds indexDB for current and previous retention periods.
When time series is ingested or queried, VM checks if its TSID is present
in `current` indexDB. If it is missing, it checks the `previous` indexDB.
If TSID was found, it gets copied to the `current` indexDB. In this way
`current` indexDB stores only series which were active during the retention
period.
To improve indexDB lookups, VM uses a cache layer called `tsidCache`. Both
write and read path consult `tsidCache` and on miss the relad lookup happens.
When rotation happens, VM resets the `tsidCache`. This is needed for ingestion
path to trigger `current` indexDB re-population. Since index re-population
requires additional resources, every index rotation event may cause some extra
load on CPU and disk. While it may be unnoticeable for most of the cases,
for systems with very high number of unique series each rotation may lead
to performance degradation for some period of time.
This PR makes an attempt to smooth out resource usage after the rotation.
The changes are following:
1. `tsidCache` is no longer reset after the rotation;
2. Instead, each entry in `tsidCache` gains a notion of indexDB to which
they belong;
3. On ingestion path after the rotation we check if requested TSID was
found in `tsidCache`. Then we have 3 branches:
3.1 Fast path. It was found, and belongs to the `current` indexDB. Return TSID.
3.2 Slow path. It wasn't found, so we generate it from scratch,
add to `current` indexDB, add it to `tsidCache`.
3.3 Smooth path. It was found but does not belong to the `current` indexDB.
In this case, we add it to the `current` indexDB with some probability.
The probability is based on time passed since the last rotation with some threshold.
The more time has passed since rotation the higher is chance to re-populate `current` indexDB.
The default re-population interval in this PR is set to `1h`, during which entries from
`previous` index supposed to slowly re-populate `current` index.
The new metric `vm_timeseries_repopulated_total` was added to identify how many TSIDs
were moved from `previous` indexDB to the `current` indexDB. This metric supposed to
grow only during the first `1h` after the last rotation.
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401
Signed-off-by: hagen1778 <roman@victoriametrics.com>
* wip
* wip
Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
2022-02-11 22:30:08 +00:00
|
|
|
|
2024-08-27 19:33:53 +00:00
|
|
|
var newSeriesCount uint64
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
var seriesRepopulated uint64
|
|
|
|
|
2020-11-15 22:42:27 +00:00
|
|
|
idb := s.idb()
|
2023-07-22 22:20:21 +00:00
|
|
|
generation := idb.generation
|
2020-11-15 22:42:27 +00:00
|
|
|
is := idb.getIndexSearch(noDeadline)
|
|
|
|
defer idb.putIndexSearch(is)
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
var firstWarn error
|
2020-11-15 22:42:27 +00:00
|
|
|
for i := range mrs {
|
|
|
|
mr := &mrs[i]
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
date := uint64(mr.Timestamp) / msecPerDay
|
lib/index: reduce read/write load after indexDB rotation (#2177)
* lib/index: reduce read/write load after indexDB rotation
IndexDB in VM is responsible for storing TSID - ID's used for identifying
time series. The index is stored on disk and used by both ingestion and read path.
IndexDB is stored separately to data parts and is global for all stored data.
It can't be deleted partially as VM deletes data parts. Instead, indexDB is
rotated once in `retention` interval.
The rotation procedure means that `current` indexDB becomes `previous`,
and new freshly created indexDB struct becomes `current`. So in any time,
VM holds indexDB for current and previous retention periods.
When time series is ingested or queried, VM checks if its TSID is present
in `current` indexDB. If it is missing, it checks the `previous` indexDB.
If TSID was found, it gets copied to the `current` indexDB. In this way
`current` indexDB stores only series which were active during the retention
period.
To improve indexDB lookups, VM uses a cache layer called `tsidCache`. Both
write and read path consult `tsidCache` and on miss the relad lookup happens.
When rotation happens, VM resets the `tsidCache`. This is needed for ingestion
path to trigger `current` indexDB re-population. Since index re-population
requires additional resources, every index rotation event may cause some extra
load on CPU and disk. While it may be unnoticeable for most of the cases,
for systems with very high number of unique series each rotation may lead
to performance degradation for some period of time.
This PR makes an attempt to smooth out resource usage after the rotation.
The changes are following:
1. `tsidCache` is no longer reset after the rotation;
2. Instead, each entry in `tsidCache` gains a notion of indexDB to which
they belong;
3. On ingestion path after the rotation we check if requested TSID was
found in `tsidCache`. Then we have 3 branches:
3.1 Fast path. It was found, and belongs to the `current` indexDB. Return TSID.
3.2 Slow path. It wasn't found, so we generate it from scratch,
add to `current` indexDB, add it to `tsidCache`.
3.3 Smooth path. It was found but does not belong to the `current` indexDB.
In this case, we add it to the `current` indexDB with some probability.
The probability is based on time passed since the last rotation with some threshold.
The more time has passed since rotation the higher is chance to re-populate `current` indexDB.
The default re-population interval in this PR is set to `1h`, during which entries from
`previous` index supposed to slowly re-populate `current` index.
The new metric `vm_timeseries_repopulated_total` was added to identify how many TSIDs
were moved from `previous` indexDB to the `current` indexDB. This metric supposed to
grow only during the first `1h` after the last rotation.
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401
Signed-off-by: hagen1778 <roman@victoriametrics.com>
* wip
* wip
Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
2022-02-11 22:30:08 +00:00
|
|
|
if s.getTSIDFromCache(&genTSID, mr.MetricNameRaw) {
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
// Fast path - mr.MetricNameRaw has been already registered in the current idb.
|
|
|
|
if !s.registerSeriesCardinality(genTSID.TSID.MetricID, mr.MetricNameRaw) {
|
|
|
|
// Skip row, since it exceeds cardinality limit
|
2022-06-20 10:47:43 +00:00
|
|
|
continue
|
|
|
|
}
|
2023-07-22 22:20:21 +00:00
|
|
|
if genTSID.generation < generation {
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
// The found TSID is from the previous indexdb. Create it in the current indexdb.
|
|
|
|
|
|
|
|
if err := mn.UnmarshalRaw(mr.MetricNameRaw); err != nil {
|
|
|
|
// Do not stop adding rows on error - just skip invalid row.
|
|
|
|
// This guarantees that invalid rows don't prevent
|
|
|
|
// from adding valid rows into the storage.
|
|
|
|
if firstWarn == nil {
|
|
|
|
firstWarn = fmt.Errorf("cannot umarshal MetricNameRaw %q: %w", mr.MetricNameRaw, err)
|
|
|
|
}
|
2024-09-06 15:57:21 +00:00
|
|
|
s.invalidRawMetricNames.Add(1)
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
mn.sortTags()
|
|
|
|
|
2023-07-14 06:13:21 +00:00
|
|
|
createAllIndexesForMetricName(is, mn, &genTSID.TSID, date)
|
2023-07-22 22:20:21 +00:00
|
|
|
genTSID.generation = generation
|
2023-07-14 06:13:21 +00:00
|
|
|
s.putSeriesToCache(mr.MetricNameRaw, &genTSID, date)
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
seriesRepopulated++
|
2024-08-27 19:33:53 +00:00
|
|
|
} else if !s.dateMetricIDCache.Has(generation, date, genTSID.TSID.MetricID) {
|
|
|
|
if !is.hasDateMetricIDNoExtDB(date, genTSID.TSID.MetricID) {
|
|
|
|
if err := mn.UnmarshalRaw(mr.MetricNameRaw); err != nil {
|
|
|
|
if firstWarn == nil {
|
|
|
|
firstWarn = fmt.Errorf("cannot unmarshal MetricNameRaw %q: %w", mr.MetricNameRaw, err)
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
mn.sortTags()
|
|
|
|
is.createPerDayIndexes(date, &genTSID.TSID, mn)
|
|
|
|
}
|
|
|
|
s.dateMetricIDCache.Set(generation, date, genTSID.TSID.MetricID)
|
lib/index: reduce read/write load after indexDB rotation (#2177)
* lib/index: reduce read/write load after indexDB rotation
IndexDB in VM is responsible for storing TSID - ID's used for identifying
time series. The index is stored on disk and used by both ingestion and read path.
IndexDB is stored separately to data parts and is global for all stored data.
It can't be deleted partially as VM deletes data parts. Instead, indexDB is
rotated once in `retention` interval.
The rotation procedure means that `current` indexDB becomes `previous`,
and new freshly created indexDB struct becomes `current`. So in any time,
VM holds indexDB for current and previous retention periods.
When time series is ingested or queried, VM checks if its TSID is present
in `current` indexDB. If it is missing, it checks the `previous` indexDB.
If TSID was found, it gets copied to the `current` indexDB. In this way
`current` indexDB stores only series which were active during the retention
period.
To improve indexDB lookups, VM uses a cache layer called `tsidCache`. Both
write and read path consult `tsidCache` and on miss the relad lookup happens.
When rotation happens, VM resets the `tsidCache`. This is needed for ingestion
path to trigger `current` indexDB re-population. Since index re-population
requires additional resources, every index rotation event may cause some extra
load on CPU and disk. While it may be unnoticeable for most of the cases,
for systems with very high number of unique series each rotation may lead
to performance degradation for some period of time.
This PR makes an attempt to smooth out resource usage after the rotation.
The changes are following:
1. `tsidCache` is no longer reset after the rotation;
2. Instead, each entry in `tsidCache` gains a notion of indexDB to which
they belong;
3. On ingestion path after the rotation we check if requested TSID was
found in `tsidCache`. Then we have 3 branches:
3.1 Fast path. It was found, and belongs to the `current` indexDB. Return TSID.
3.2 Slow path. It wasn't found, so we generate it from scratch,
add to `current` indexDB, add it to `tsidCache`.
3.3 Smooth path. It was found but does not belong to the `current` indexDB.
In this case, we add it to the `current` indexDB with some probability.
The probability is based on time passed since the last rotation with some threshold.
The more time has passed since rotation the higher is chance to re-populate `current` indexDB.
The default re-population interval in this PR is set to `1h`, during which entries from
`previous` index supposed to slowly re-populate `current` index.
The new metric `vm_timeseries_repopulated_total` was added to identify how many TSIDs
were moved from `previous` indexDB to the `current` indexDB. This metric supposed to
grow only during the first `1h` after the last rotation.
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401
Signed-off-by: hagen1778 <roman@victoriametrics.com>
* wip
* wip
Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
2022-02-11 22:30:08 +00:00
|
|
|
}
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
continue
|
2020-11-15 22:42:27 +00:00
|
|
|
}
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
|
|
|
|
// Slow path - search TSID for the given metricName in indexdb.
|
|
|
|
|
|
|
|
// Construct canonical metric name - it is used below.
|
2021-05-08 14:55:44 +00:00
|
|
|
if err := mn.UnmarshalRaw(mr.MetricNameRaw); err != nil {
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
// Do not stop adding rows on error - just skip invalid row.
|
|
|
|
// This guarantees that invalid rows don't prevent
|
|
|
|
// from adding valid rows into the storage.
|
|
|
|
if firstWarn == nil {
|
|
|
|
firstWarn = fmt.Errorf("cannot umarshal MetricNameRaw %q: %w", mr.MetricNameRaw, err)
|
|
|
|
}
|
2024-09-06 15:57:21 +00:00
|
|
|
s.invalidRawMetricNames.Add(1)
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
continue
|
2020-11-15 22:42:27 +00:00
|
|
|
}
|
|
|
|
mn.sortTags()
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
metricNameBuf = mn.Marshal(metricNameBuf[:0])
|
|
|
|
|
|
|
|
if is.getTSIDByMetricName(&genTSID, metricNameBuf, date) {
|
|
|
|
// Slower path - the TSID has been found in indexdb.
|
|
|
|
|
|
|
|
if !s.registerSeriesCardinality(genTSID.TSID.MetricID, mr.MetricNameRaw) {
|
|
|
|
// Skip the row, since it exceeds the configured cardinality limit.
|
2022-06-19 18:47:35 +00:00
|
|
|
continue
|
|
|
|
}
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
|
2023-07-22 22:20:21 +00:00
|
|
|
if genTSID.generation < generation {
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
// The found TSID is from the previous indexdb. Create it in the current indexdb.
|
2023-07-14 06:13:21 +00:00
|
|
|
createAllIndexesForMetricName(is, mn, &genTSID.TSID, date)
|
2023-07-22 22:20:21 +00:00
|
|
|
genTSID.generation = generation
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
seriesRepopulated++
|
|
|
|
}
|
2023-07-14 06:13:21 +00:00
|
|
|
s.putSeriesToCache(mr.MetricNameRaw, &genTSID, date)
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Slowest path - there is no TSID in indexdb for the given mr.MetricNameRaw. Create it.
|
|
|
|
generateTSID(&genTSID.TSID, mn)
|
|
|
|
|
|
|
|
if !s.registerSeriesCardinality(genTSID.TSID.MetricID, mr.MetricNameRaw) {
|
|
|
|
// Skip the row, since it exceeds the configured cardinality limit.
|
|
|
|
continue
|
2020-11-15 22:42:27 +00:00
|
|
|
}
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
|
|
|
|
// Schedule creating TSID indexes instead of creating them synchronously.
|
|
|
|
// This should keep stable the ingestion rate when new time series are ingested.
|
2023-07-14 06:13:21 +00:00
|
|
|
createAllIndexesForMetricName(is, mn, &genTSID.TSID, date)
|
2023-07-22 22:20:21 +00:00
|
|
|
genTSID.generation = generation
|
2023-07-14 06:13:21 +00:00
|
|
|
s.putSeriesToCache(mr.MetricNameRaw, &genTSID, date)
|
2024-08-27 19:33:53 +00:00
|
|
|
newSeriesCount++
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
}
|
|
|
|
|
2024-08-27 19:33:53 +00:00
|
|
|
s.newTimeseriesCreated.Add(newSeriesCount)
|
2024-02-23 22:15:21 +00:00
|
|
|
s.timeseriesRepopulated.Add(seriesRepopulated)
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
|
2023-07-22 22:20:21 +00:00
|
|
|
// There is no need in pre-filling idbNext here, since RegisterMetricNames() is rarely called.
|
|
|
|
// So it is OK to register metric names in blocking manner after indexdb rotation.
|
|
|
|
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
if firstWarn != nil {
|
|
|
|
logger.Warnf("cannot create some metrics: %s", firstWarn)
|
2020-11-15 22:42:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-06 15:57:21 +00:00
|
|
|
func (s *Storage) add(rows []rawRow, dstMrs []*MetricRow, mrs []MetricRow, precisionBits uint8) int {
|
2019-05-22 21:16:55 +00:00
|
|
|
idb := s.idb()
|
2023-07-22 22:20:21 +00:00
|
|
|
generation := idb.generation
|
2022-06-19 18:58:53 +00:00
|
|
|
is := idb.getIndexSearch(noDeadline)
|
|
|
|
defer idb.putIndexSearch(is)
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
|
|
|
|
mn := GetMetricName()
|
|
|
|
defer PutMetricName(mn)
|
|
|
|
|
2019-12-19 13:12:50 +00:00
|
|
|
var (
|
2021-03-09 07:18:19 +00:00
|
|
|
// These vars are used for speeding up bulk imports of multiple adjacent rows for the same metricName.
|
2019-12-19 13:12:50 +00:00
|
|
|
prevTSID TSID
|
|
|
|
prevMetricNameRaw []byte
|
|
|
|
)
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
var metricNameBuf []byte
|
|
|
|
|
|
|
|
var slowInsertsCount uint64
|
|
|
|
var newSeriesCount uint64
|
|
|
|
var seriesRepopulated uint64
|
|
|
|
|
2019-07-11 14:04:56 +00:00
|
|
|
minTimestamp, maxTimestamp := s.tb.getMinMaxTimestamps()
|
lib/index: reduce read/write load after indexDB rotation (#2177)
* lib/index: reduce read/write load after indexDB rotation
IndexDB in VM is responsible for storing TSID - ID's used for identifying
time series. The index is stored on disk and used by both ingestion and read path.
IndexDB is stored separately to data parts and is global for all stored data.
It can't be deleted partially as VM deletes data parts. Instead, indexDB is
rotated once in `retention` interval.
The rotation procedure means that `current` indexDB becomes `previous`,
and new freshly created indexDB struct becomes `current`. So in any time,
VM holds indexDB for current and previous retention periods.
When time series is ingested or queried, VM checks if its TSID is present
in `current` indexDB. If it is missing, it checks the `previous` indexDB.
If TSID was found, it gets copied to the `current` indexDB. In this way
`current` indexDB stores only series which were active during the retention
period.
To improve indexDB lookups, VM uses a cache layer called `tsidCache`. Both
write and read path consult `tsidCache` and on miss the relad lookup happens.
When rotation happens, VM resets the `tsidCache`. This is needed for ingestion
path to trigger `current` indexDB re-population. Since index re-population
requires additional resources, every index rotation event may cause some extra
load on CPU and disk. While it may be unnoticeable for most of the cases,
for systems with very high number of unique series each rotation may lead
to performance degradation for some period of time.
This PR makes an attempt to smooth out resource usage after the rotation.
The changes are following:
1. `tsidCache` is no longer reset after the rotation;
2. Instead, each entry in `tsidCache` gains a notion of indexDB to which
they belong;
3. On ingestion path after the rotation we check if requested TSID was
found in `tsidCache`. Then we have 3 branches:
3.1 Fast path. It was found, and belongs to the `current` indexDB. Return TSID.
3.2 Slow path. It wasn't found, so we generate it from scratch,
add to `current` indexDB, add it to `tsidCache`.
3.3 Smooth path. It was found but does not belong to the `current` indexDB.
In this case, we add it to the `current` indexDB with some probability.
The probability is based on time passed since the last rotation with some threshold.
The more time has passed since rotation the higher is chance to re-populate `current` indexDB.
The default re-population interval in this PR is set to `1h`, during which entries from
`previous` index supposed to slowly re-populate `current` index.
The new metric `vm_timeseries_repopulated_total` was added to identify how many TSIDs
were moved from `previous` indexDB to the `current` indexDB. This metric supposed to
grow only during the first `1h` after the last rotation.
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401
Signed-off-by: hagen1778 <roman@victoriametrics.com>
* wip
* wip
Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
2022-02-11 22:30:08 +00:00
|
|
|
|
|
|
|
var genTSID generationTSID
|
|
|
|
|
2024-07-17 10:07:14 +00:00
|
|
|
// Log only the first error, since it has no sense in logging all errors.
|
2020-05-14 20:17:22 +00:00
|
|
|
var firstWarn error
|
2023-10-17 13:45:14 +00:00
|
|
|
|
2022-06-19 18:58:53 +00:00
|
|
|
j := 0
|
2019-05-22 21:16:55 +00:00
|
|
|
for i := range mrs {
|
|
|
|
mr := &mrs[i]
|
|
|
|
if math.IsNaN(mr.Value) {
|
2023-10-16 07:05:37 +00:00
|
|
|
if !decimal.IsStaleNaN(mr.Value) {
|
2021-08-13 09:10:00 +00:00
|
|
|
// Skip NaNs other than Prometheus staleness marker, since the underlying encoding
|
|
|
|
// doesn't know how to work with them.
|
2024-09-06 15:57:21 +00:00
|
|
|
s.naNValueRows.Add(1)
|
2021-08-13 09:10:00 +00:00
|
|
|
continue
|
|
|
|
}
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
2019-07-26 11:10:25 +00:00
|
|
|
if mr.Timestamp < minTimestamp {
|
|
|
|
// Skip rows with too small timestamps outside the retention.
|
2020-05-14 20:17:22 +00:00
|
|
|
if firstWarn == nil {
|
2020-11-25 12:41:02 +00:00
|
|
|
metricName := getUserReadableMetricName(mr.MetricNameRaw)
|
2020-07-08 10:53:29 +00:00
|
|
|
firstWarn = fmt.Errorf("cannot insert row with too small timestamp %d outside the retention; minimum allowed timestamp is %d; "+
|
2020-11-25 12:41:02 +00:00
|
|
|
"probably you need updating -retentionPeriod command-line flag; metricName: %s",
|
|
|
|
mr.Timestamp, minTimestamp, metricName)
|
2020-05-14 20:17:22 +00:00
|
|
|
}
|
2024-02-23 22:15:21 +00:00
|
|
|
s.tooSmallTimestampRows.Add(1)
|
2019-07-26 11:10:25 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
if mr.Timestamp > maxTimestamp {
|
|
|
|
// Skip rows with too big timestamps significantly exceeding the current time.
|
2020-05-14 20:17:22 +00:00
|
|
|
if firstWarn == nil {
|
2020-11-25 12:41:02 +00:00
|
|
|
metricName := getUserReadableMetricName(mr.MetricNameRaw)
|
|
|
|
firstWarn = fmt.Errorf("cannot insert row with too big timestamp %d exceeding the current time; maximum allowed timestamp is %d; metricName: %s",
|
|
|
|
mr.Timestamp, maxTimestamp, metricName)
|
2020-05-14 20:17:22 +00:00
|
|
|
}
|
2024-02-23 22:15:21 +00:00
|
|
|
s.tooBigTimestampRows.Add(1)
|
2019-07-11 14:04:56 +00:00
|
|
|
continue
|
|
|
|
}
|
2021-05-23 13:39:55 +00:00
|
|
|
dstMrs[j] = mr
|
|
|
|
r := &rows[j]
|
2019-05-22 21:16:55 +00:00
|
|
|
j++
|
|
|
|
r.Timestamp = mr.Timestamp
|
|
|
|
r.Value = mr.Value
|
|
|
|
r.PrecisionBits = precisionBits
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
|
|
|
|
// Search for TSID for the given mr.MetricNameRaw and store it at r.TSID.
|
2019-12-19 13:12:50 +00:00
|
|
|
if string(mr.MetricNameRaw) == string(prevMetricNameRaw) {
|
|
|
|
// Fast path - the current mr contains the same metric name as the previous mr, so it contains the same TSID.
|
|
|
|
// This path should trigger on bulk imports when many rows contain the same MetricNameRaw.
|
|
|
|
r.TSID = prevTSID
|
|
|
|
continue
|
|
|
|
}
|
lib/index: reduce read/write load after indexDB rotation (#2177)
* lib/index: reduce read/write load after indexDB rotation
IndexDB in VM is responsible for storing TSID - ID's used for identifying
time series. The index is stored on disk and used by both ingestion and read path.
IndexDB is stored separately to data parts and is global for all stored data.
It can't be deleted partially as VM deletes data parts. Instead, indexDB is
rotated once in `retention` interval.
The rotation procedure means that `current` indexDB becomes `previous`,
and new freshly created indexDB struct becomes `current`. So in any time,
VM holds indexDB for current and previous retention periods.
When time series is ingested or queried, VM checks if its TSID is present
in `current` indexDB. If it is missing, it checks the `previous` indexDB.
If TSID was found, it gets copied to the `current` indexDB. In this way
`current` indexDB stores only series which were active during the retention
period.
To improve indexDB lookups, VM uses a cache layer called `tsidCache`. Both
write and read path consult `tsidCache` and on miss the relad lookup happens.
When rotation happens, VM resets the `tsidCache`. This is needed for ingestion
path to trigger `current` indexDB re-population. Since index re-population
requires additional resources, every index rotation event may cause some extra
load on CPU and disk. While it may be unnoticeable for most of the cases,
for systems with very high number of unique series each rotation may lead
to performance degradation for some period of time.
This PR makes an attempt to smooth out resource usage after the rotation.
The changes are following:
1. `tsidCache` is no longer reset after the rotation;
2. Instead, each entry in `tsidCache` gains a notion of indexDB to which
they belong;
3. On ingestion path after the rotation we check if requested TSID was
found in `tsidCache`. Then we have 3 branches:
3.1 Fast path. It was found, and belongs to the `current` indexDB. Return TSID.
3.2 Slow path. It wasn't found, so we generate it from scratch,
add to `current` indexDB, add it to `tsidCache`.
3.3 Smooth path. It was found but does not belong to the `current` indexDB.
In this case, we add it to the `current` indexDB with some probability.
The probability is based on time passed since the last rotation with some threshold.
The more time has passed since rotation the higher is chance to re-populate `current` indexDB.
The default re-population interval in this PR is set to `1h`, during which entries from
`previous` index supposed to slowly re-populate `current` index.
The new metric `vm_timeseries_repopulated_total` was added to identify how many TSIDs
were moved from `previous` indexDB to the `current` indexDB. This metric supposed to
grow only during the first `1h` after the last rotation.
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401
Signed-off-by: hagen1778 <roman@victoriametrics.com>
* wip
* wip
Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
2022-02-11 22:30:08 +00:00
|
|
|
if s.getTSIDFromCache(&genTSID, mr.MetricNameRaw) {
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
// Fast path - the TSID for the given mr.MetricNameRaw has been found in cache and isn't deleted.
|
|
|
|
// There is no need in checking whether r.TSID.MetricID is deleted, since tsidCache doesn't
|
|
|
|
// contain MetricName->TSID entries for deleted time series.
|
|
|
|
// See Storage.DeleteSeries code for details.
|
|
|
|
|
|
|
|
if !s.registerSeriesCardinality(r.TSID.MetricID, mr.MetricNameRaw) {
|
|
|
|
// Skip row, since it exceeds cardinality limit
|
2022-06-20 10:47:43 +00:00
|
|
|
j--
|
|
|
|
continue
|
|
|
|
}
|
lib/index: reduce read/write load after indexDB rotation (#2177)
* lib/index: reduce read/write load after indexDB rotation
IndexDB in VM is responsible for storing TSID - ID's used for identifying
time series. The index is stored on disk and used by both ingestion and read path.
IndexDB is stored separately to data parts and is global for all stored data.
It can't be deleted partially as VM deletes data parts. Instead, indexDB is
rotated once in `retention` interval.
The rotation procedure means that `current` indexDB becomes `previous`,
and new freshly created indexDB struct becomes `current`. So in any time,
VM holds indexDB for current and previous retention periods.
When time series is ingested or queried, VM checks if its TSID is present
in `current` indexDB. If it is missing, it checks the `previous` indexDB.
If TSID was found, it gets copied to the `current` indexDB. In this way
`current` indexDB stores only series which were active during the retention
period.
To improve indexDB lookups, VM uses a cache layer called `tsidCache`. Both
write and read path consult `tsidCache` and on miss the relad lookup happens.
When rotation happens, VM resets the `tsidCache`. This is needed for ingestion
path to trigger `current` indexDB re-population. Since index re-population
requires additional resources, every index rotation event may cause some extra
load on CPU and disk. While it may be unnoticeable for most of the cases,
for systems with very high number of unique series each rotation may lead
to performance degradation for some period of time.
This PR makes an attempt to smooth out resource usage after the rotation.
The changes are following:
1. `tsidCache` is no longer reset after the rotation;
2. Instead, each entry in `tsidCache` gains a notion of indexDB to which
they belong;
3. On ingestion path after the rotation we check if requested TSID was
found in `tsidCache`. Then we have 3 branches:
3.1 Fast path. It was found, and belongs to the `current` indexDB. Return TSID.
3.2 Slow path. It wasn't found, so we generate it from scratch,
add to `current` indexDB, add it to `tsidCache`.
3.3 Smooth path. It was found but does not belong to the `current` indexDB.
In this case, we add it to the `current` indexDB with some probability.
The probability is based on time passed since the last rotation with some threshold.
The more time has passed since rotation the higher is chance to re-populate `current` indexDB.
The default re-population interval in this PR is set to `1h`, during which entries from
`previous` index supposed to slowly re-populate `current` index.
The new metric `vm_timeseries_repopulated_total` was added to identify how many TSIDs
were moved from `previous` indexDB to the `current` indexDB. This metric supposed to
grow only during the first `1h` after the last rotation.
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401
Signed-off-by: hagen1778 <roman@victoriametrics.com>
* wip
* wip
Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
2022-02-11 22:30:08 +00:00
|
|
|
r.TSID = genTSID.TSID
|
2020-05-14 20:23:39 +00:00
|
|
|
prevTSID = r.TSID
|
|
|
|
prevMetricNameRaw = mr.MetricNameRaw
|
lib/index: reduce read/write load after indexDB rotation (#2177)
* lib/index: reduce read/write load after indexDB rotation
IndexDB in VM is responsible for storing TSID - ID's used for identifying
time series. The index is stored on disk and used by both ingestion and read path.
IndexDB is stored separately to data parts and is global for all stored data.
It can't be deleted partially as VM deletes data parts. Instead, indexDB is
rotated once in `retention` interval.
The rotation procedure means that `current` indexDB becomes `previous`,
and new freshly created indexDB struct becomes `current`. So in any time,
VM holds indexDB for current and previous retention periods.
When time series is ingested or queried, VM checks if its TSID is present
in `current` indexDB. If it is missing, it checks the `previous` indexDB.
If TSID was found, it gets copied to the `current` indexDB. In this way
`current` indexDB stores only series which were active during the retention
period.
To improve indexDB lookups, VM uses a cache layer called `tsidCache`. Both
write and read path consult `tsidCache` and on miss the relad lookup happens.
When rotation happens, VM resets the `tsidCache`. This is needed for ingestion
path to trigger `current` indexDB re-population. Since index re-population
requires additional resources, every index rotation event may cause some extra
load on CPU and disk. While it may be unnoticeable for most of the cases,
for systems with very high number of unique series each rotation may lead
to performance degradation for some period of time.
This PR makes an attempt to smooth out resource usage after the rotation.
The changes are following:
1. `tsidCache` is no longer reset after the rotation;
2. Instead, each entry in `tsidCache` gains a notion of indexDB to which
they belong;
3. On ingestion path after the rotation we check if requested TSID was
found in `tsidCache`. Then we have 3 branches:
3.1 Fast path. It was found, and belongs to the `current` indexDB. Return TSID.
3.2 Slow path. It wasn't found, so we generate it from scratch,
add to `current` indexDB, add it to `tsidCache`.
3.3 Smooth path. It was found but does not belong to the `current` indexDB.
In this case, we add it to the `current` indexDB with some probability.
The probability is based on time passed since the last rotation with some threshold.
The more time has passed since rotation the higher is chance to re-populate `current` indexDB.
The default re-population interval in this PR is set to `1h`, during which entries from
`previous` index supposed to slowly re-populate `current` index.
The new metric `vm_timeseries_repopulated_total` was added to identify how many TSIDs
were moved from `previous` indexDB to the `current` indexDB. This metric supposed to
grow only during the first `1h` after the last rotation.
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401
Signed-off-by: hagen1778 <roman@victoriametrics.com>
* wip
* wip
Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
2022-02-11 22:30:08 +00:00
|
|
|
|
2023-07-22 22:20:21 +00:00
|
|
|
if genTSID.generation < generation {
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
// The found TSID is from the previous indexdb. Create it in the current indexdb.
|
2022-06-19 18:58:53 +00:00
|
|
|
date := uint64(r.Timestamp) / msecPerDay
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
|
|
|
|
if err := mn.UnmarshalRaw(mr.MetricNameRaw); err != nil {
|
|
|
|
if firstWarn == nil {
|
|
|
|
firstWarn = fmt.Errorf("cannot unmarshal MetricNameRaw %q: %w", mr.MetricNameRaw, err)
|
|
|
|
}
|
|
|
|
j--
|
2024-09-06 15:57:21 +00:00
|
|
|
s.invalidRawMetricNames.Add(1)
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
continue
|
lib/index: reduce read/write load after indexDB rotation (#2177)
* lib/index: reduce read/write load after indexDB rotation
IndexDB in VM is responsible for storing TSID - ID's used for identifying
time series. The index is stored on disk and used by both ingestion and read path.
IndexDB is stored separately to data parts and is global for all stored data.
It can't be deleted partially as VM deletes data parts. Instead, indexDB is
rotated once in `retention` interval.
The rotation procedure means that `current` indexDB becomes `previous`,
and new freshly created indexDB struct becomes `current`. So in any time,
VM holds indexDB for current and previous retention periods.
When time series is ingested or queried, VM checks if its TSID is present
in `current` indexDB. If it is missing, it checks the `previous` indexDB.
If TSID was found, it gets copied to the `current` indexDB. In this way
`current` indexDB stores only series which were active during the retention
period.
To improve indexDB lookups, VM uses a cache layer called `tsidCache`. Both
write and read path consult `tsidCache` and on miss the relad lookup happens.
When rotation happens, VM resets the `tsidCache`. This is needed for ingestion
path to trigger `current` indexDB re-population. Since index re-population
requires additional resources, every index rotation event may cause some extra
load on CPU and disk. While it may be unnoticeable for most of the cases,
for systems with very high number of unique series each rotation may lead
to performance degradation for some period of time.
This PR makes an attempt to smooth out resource usage after the rotation.
The changes are following:
1. `tsidCache` is no longer reset after the rotation;
2. Instead, each entry in `tsidCache` gains a notion of indexDB to which
they belong;
3. On ingestion path after the rotation we check if requested TSID was
found in `tsidCache`. Then we have 3 branches:
3.1 Fast path. It was found, and belongs to the `current` indexDB. Return TSID.
3.2 Slow path. It wasn't found, so we generate it from scratch,
add to `current` indexDB, add it to `tsidCache`.
3.3 Smooth path. It was found but does not belong to the `current` indexDB.
In this case, we add it to the `current` indexDB with some probability.
The probability is based on time passed since the last rotation with some threshold.
The more time has passed since rotation the higher is chance to re-populate `current` indexDB.
The default re-population interval in this PR is set to `1h`, during which entries from
`previous` index supposed to slowly re-populate `current` index.
The new metric `vm_timeseries_repopulated_total` was added to identify how many TSIDs
were moved from `previous` indexDB to the `current` indexDB. This metric supposed to
grow only during the first `1h` after the last rotation.
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401
Signed-off-by: hagen1778 <roman@victoriametrics.com>
* wip
* wip
Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
2022-02-11 22:30:08 +00:00
|
|
|
}
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
mn.sortTags()
|
|
|
|
|
2023-07-14 06:13:21 +00:00
|
|
|
createAllIndexesForMetricName(is, mn, &genTSID.TSID, date)
|
2023-07-22 22:20:21 +00:00
|
|
|
genTSID.generation = generation
|
2023-07-14 06:13:21 +00:00
|
|
|
s.putSeriesToCache(mr.MetricNameRaw, &genTSID, date)
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
seriesRepopulated++
|
|
|
|
slowInsertsCount++
|
lib/index: reduce read/write load after indexDB rotation (#2177)
* lib/index: reduce read/write load after indexDB rotation
IndexDB in VM is responsible for storing TSID - ID's used for identifying
time series. The index is stored on disk and used by both ingestion and read path.
IndexDB is stored separately to data parts and is global for all stored data.
It can't be deleted partially as VM deletes data parts. Instead, indexDB is
rotated once in `retention` interval.
The rotation procedure means that `current` indexDB becomes `previous`,
and new freshly created indexDB struct becomes `current`. So in any time,
VM holds indexDB for current and previous retention periods.
When time series is ingested or queried, VM checks if its TSID is present
in `current` indexDB. If it is missing, it checks the `previous` indexDB.
If TSID was found, it gets copied to the `current` indexDB. In this way
`current` indexDB stores only series which were active during the retention
period.
To improve indexDB lookups, VM uses a cache layer called `tsidCache`. Both
write and read path consult `tsidCache` and on miss the relad lookup happens.
When rotation happens, VM resets the `tsidCache`. This is needed for ingestion
path to trigger `current` indexDB re-population. Since index re-population
requires additional resources, every index rotation event may cause some extra
load on CPU and disk. While it may be unnoticeable for most of the cases,
for systems with very high number of unique series each rotation may lead
to performance degradation for some period of time.
This PR makes an attempt to smooth out resource usage after the rotation.
The changes are following:
1. `tsidCache` is no longer reset after the rotation;
2. Instead, each entry in `tsidCache` gains a notion of indexDB to which
they belong;
3. On ingestion path after the rotation we check if requested TSID was
found in `tsidCache`. Then we have 3 branches:
3.1 Fast path. It was found, and belongs to the `current` indexDB. Return TSID.
3.2 Slow path. It wasn't found, so we generate it from scratch,
add to `current` indexDB, add it to `tsidCache`.
3.3 Smooth path. It was found but does not belong to the `current` indexDB.
In this case, we add it to the `current` indexDB with some probability.
The probability is based on time passed since the last rotation with some threshold.
The more time has passed since rotation the higher is chance to re-populate `current` indexDB.
The default re-population interval in this PR is set to `1h`, during which entries from
`previous` index supposed to slowly re-populate `current` index.
The new metric `vm_timeseries_repopulated_total` was added to identify how many TSIDs
were moved from `previous` indexDB to the `current` indexDB. This metric supposed to
grow only during the first `1h` after the last rotation.
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401
Signed-off-by: hagen1778 <roman@victoriametrics.com>
* wip
* wip
Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
2022-02-11 22:30:08 +00:00
|
|
|
}
|
2020-05-14 20:23:39 +00:00
|
|
|
continue
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
|
|
|
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
// Slow path - the TSID for the given mr.MetricNameRaw is missing in the cache.
|
|
|
|
slowInsertsCount++
|
|
|
|
|
|
|
|
date := uint64(r.Timestamp) / msecPerDay
|
|
|
|
|
|
|
|
// Construct canonical metric name - it is used below.
|
|
|
|
if err := mn.UnmarshalRaw(mr.MetricNameRaw); err != nil {
|
2021-03-31 20:12:56 +00:00
|
|
|
if firstWarn == nil {
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
firstWarn = fmt.Errorf("cannot unmarshal MetricNameRaw %q: %w", mr.MetricNameRaw, err)
|
2021-03-31 20:12:56 +00:00
|
|
|
}
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
j--
|
2024-09-06 15:57:21 +00:00
|
|
|
s.invalidRawMetricNames.Add(1)
|
2021-03-31 20:12:56 +00:00
|
|
|
continue
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
mn.sortTags()
|
|
|
|
metricNameBuf = mn.Marshal(metricNameBuf[:0])
|
|
|
|
|
|
|
|
// Search for TSID for the given mr.MetricNameRaw in the indexdb.
|
|
|
|
if is.getTSIDByMetricName(&genTSID, metricNameBuf, date) {
|
|
|
|
// Slower path - the TSID has been found in indexdb.
|
|
|
|
|
|
|
|
if !s.registerSeriesCardinality(genTSID.TSID.MetricID, mr.MetricNameRaw) {
|
|
|
|
// Skip the row, since it exceeds the configured cardinality limit.
|
2022-06-19 18:47:35 +00:00
|
|
|
j--
|
2020-05-14 20:45:04 +00:00
|
|
|
continue
|
|
|
|
}
|
2022-06-19 18:47:35 +00:00
|
|
|
|
2023-07-22 22:20:21 +00:00
|
|
|
if genTSID.generation < generation {
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
// The found TSID is from the previous indexdb. Create it in the current indexdb.
|
2023-07-14 06:13:21 +00:00
|
|
|
createAllIndexesForMetricName(is, mn, &genTSID.TSID, date)
|
2023-07-22 22:20:21 +00:00
|
|
|
genTSID.generation = generation
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
seriesRepopulated++
|
|
|
|
}
|
2023-07-14 06:13:21 +00:00
|
|
|
s.putSeriesToCache(mr.MetricNameRaw, &genTSID, date)
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
|
|
|
|
r.TSID = genTSID.TSID
|
|
|
|
prevTSID = genTSID.TSID
|
2022-01-21 10:37:57 +00:00
|
|
|
prevMetricNameRaw = mr.MetricNameRaw
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Slowest path - the TSID for the given mr.MetricNameRaw isn't found in indexdb. Create it.
|
|
|
|
generateTSID(&genTSID.TSID, mn)
|
|
|
|
|
|
|
|
if !s.registerSeriesCardinality(genTSID.TSID.MetricID, mr.MetricNameRaw) {
|
|
|
|
// Skip the row, since it exceeds the configured cardinality limit.
|
|
|
|
j--
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2023-07-14 06:13:21 +00:00
|
|
|
createAllIndexesForMetricName(is, mn, &genTSID.TSID, date)
|
2023-07-22 22:20:21 +00:00
|
|
|
genTSID.generation = generation
|
2023-07-14 06:13:21 +00:00
|
|
|
s.putSeriesToCache(mr.MetricNameRaw, &genTSID, date)
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
newSeriesCount++
|
|
|
|
|
|
|
|
r.TSID = genTSID.TSID
|
|
|
|
prevTSID = r.TSID
|
|
|
|
prevMetricNameRaw = mr.MetricNameRaw
|
|
|
|
|
|
|
|
if logNewSeries {
|
|
|
|
logger.Infof("new series created: %s", mn.String())
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
|
|
|
}
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
|
2024-02-23 22:15:21 +00:00
|
|
|
s.slowRowInserts.Add(slowInsertsCount)
|
|
|
|
s.newTimeseriesCreated.Add(newSeriesCount)
|
|
|
|
s.timeseriesRepopulated.Add(seriesRepopulated)
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
|
2023-07-22 22:20:21 +00:00
|
|
|
dstMrs = dstMrs[:j]
|
|
|
|
rows = rows[:j]
|
|
|
|
|
|
|
|
if err := s.prefillNextIndexDB(rows, dstMrs); err != nil {
|
|
|
|
if firstWarn == nil {
|
2024-07-17 10:07:14 +00:00
|
|
|
firstWarn = fmt.Errorf("cannot prefill next indexdb: %w", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if err := s.updatePerDateData(rows, dstMrs); err != nil {
|
|
|
|
if firstWarn == nil {
|
|
|
|
firstWarn = fmt.Errorf("cannot not update per-day index: %w", err)
|
2023-07-22 22:20:21 +00:00
|
|
|
}
|
|
|
|
}
|
2024-07-17 10:07:14 +00:00
|
|
|
|
2020-05-14 20:17:22 +00:00
|
|
|
if firstWarn != nil {
|
2022-06-27 09:31:16 +00:00
|
|
|
storageAddRowsLogger.Warnf("warn occurred during rows addition: %s", firstWarn)
|
2019-10-31 12:29:35 +00:00
|
|
|
}
|
2019-05-22 21:16:55 +00:00
|
|
|
|
2024-07-17 10:07:14 +00:00
|
|
|
s.tb.MustAddRows(rows)
|
2024-09-06 15:57:21 +00:00
|
|
|
|
|
|
|
return len(rows)
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
|
|
|
|
2022-06-27 09:31:16 +00:00
|
|
|
var storageAddRowsLogger = logger.WithThrottler("storageAddRows", 5*time.Second)
|
|
|
|
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
// SetLogNewSeries updates new series logging.
|
|
|
|
//
|
|
|
|
// This function must be called before any calling any storage functions.
|
|
|
|
func SetLogNewSeries(ok bool) {
|
|
|
|
logNewSeries = ok
|
|
|
|
}
|
|
|
|
|
|
|
|
var logNewSeries = false
|
|
|
|
|
2023-07-14 06:13:21 +00:00
|
|
|
func createAllIndexesForMetricName(is *indexSearch, mn *MetricName, tsid *TSID, date uint64) {
|
|
|
|
is.createGlobalIndexes(tsid, mn)
|
|
|
|
is.createPerDayIndexes(date, tsid, mn)
|
|
|
|
}
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
|
2023-07-14 06:13:21 +00:00
|
|
|
func (s *Storage) putSeriesToCache(metricNameRaw []byte, genTSID *generationTSID, date uint64) {
|
2024-05-20 02:08:30 +00:00
|
|
|
// Store the TSID for the current indexdb into cache,
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
// so future rows for that TSID are ingested via fast path.
|
|
|
|
s.putTSIDToCache(genTSID, metricNameRaw)
|
|
|
|
|
2023-07-22 22:20:21 +00:00
|
|
|
// Register the (generation, date, metricID) entry in the cache,
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
// so next time the entry is found there instead of searching for it in the indexdb.
|
2023-07-22 22:20:21 +00:00
|
|
|
s.dateMetricIDCache.Set(genTSID.generation, date, genTSID.TSID.MetricID)
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Storage) registerSeriesCardinality(metricID uint64, metricNameRaw []byte) bool {
|
2021-05-20 11:15:19 +00:00
|
|
|
if sl := s.hourlySeriesLimiter; sl != nil && !sl.Add(metricID) {
|
2024-02-23 22:15:21 +00:00
|
|
|
s.hourlySeriesLimitRowsDropped.Add(1)
|
2022-06-20 10:47:43 +00:00
|
|
|
logSkippedSeries(metricNameRaw, "-storage.maxHourlySeries", sl.MaxItems())
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
return false
|
2021-05-20 11:15:19 +00:00
|
|
|
}
|
|
|
|
if sl := s.dailySeriesLimiter; sl != nil && !sl.Add(metricID) {
|
2024-02-23 22:15:21 +00:00
|
|
|
s.dailySeriesLimitRowsDropped.Add(1)
|
2022-06-20 10:47:43 +00:00
|
|
|
logSkippedSeries(metricNameRaw, "-storage.maxDailySeries", sl.MaxItems())
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
return false
|
2021-05-20 11:15:19 +00:00
|
|
|
}
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
return true
|
2021-05-20 11:15:19 +00:00
|
|
|
}
|
|
|
|
|
2022-06-20 10:47:43 +00:00
|
|
|
func logSkippedSeries(metricNameRaw []byte, flagName string, flagValue int) {
|
2021-05-20 11:15:19 +00:00
|
|
|
select {
|
|
|
|
case <-logSkippedSeriesTicker.C:
|
2021-12-21 15:03:25 +00:00
|
|
|
// Do not use logger.WithThrottler() here, since this will result in increased CPU load
|
|
|
|
// because of getUserReadableMetricName() calls per each logSkippedSeries call.
|
2022-06-20 10:47:43 +00:00
|
|
|
userReadableMetricName := getUserReadableMetricName(metricNameRaw)
|
|
|
|
logger.Warnf("skip series %s because %s=%d reached", userReadableMetricName, flagName, flagValue)
|
2021-05-20 11:15:19 +00:00
|
|
|
default:
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var logSkippedSeriesTicker = time.NewTicker(5 * time.Second)
|
|
|
|
|
2020-11-25 12:41:02 +00:00
|
|
|
func getUserReadableMetricName(metricNameRaw []byte) string {
|
2021-05-23 13:39:55 +00:00
|
|
|
mn := GetMetricName()
|
|
|
|
defer PutMetricName(mn)
|
2021-05-08 14:55:44 +00:00
|
|
|
if err := mn.UnmarshalRaw(metricNameRaw); err != nil {
|
2020-11-25 12:41:02 +00:00
|
|
|
return fmt.Sprintf("cannot unmarshal metricNameRaw %q: %s", metricNameRaw, err)
|
|
|
|
}
|
|
|
|
return mn.String()
|
|
|
|
}
|
|
|
|
|
2023-07-22 22:20:21 +00:00
|
|
|
func (s *Storage) prefillNextIndexDB(rows []rawRow, mrs []*MetricRow) error {
|
|
|
|
d := s.nextRetentionSeconds()
|
|
|
|
if d >= 3600 {
|
|
|
|
// Fast path: nothing to pre-fill because it is too early.
|
|
|
|
// The pre-fill is started during the last hour before the indexdb rotation.
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Slower path: less than hour left for the next indexdb rotation.
|
|
|
|
// Pre-populate idbNext with the increasing probability until the rotation.
|
|
|
|
// The probability increases from 0% to 100% proportioinally to d=[3600 .. 0].
|
|
|
|
pMin := float64(d) / 3600
|
|
|
|
|
|
|
|
idbNext := s.idbNext.Load()
|
|
|
|
generation := idbNext.generation
|
|
|
|
isNext := idbNext.getIndexSearch(noDeadline)
|
|
|
|
defer idbNext.putIndexSearch(isNext)
|
|
|
|
|
|
|
|
var firstError error
|
|
|
|
var genTSID generationTSID
|
|
|
|
mn := GetMetricName()
|
|
|
|
defer PutMetricName(mn)
|
|
|
|
|
|
|
|
timeseriesPreCreated := uint64(0)
|
|
|
|
for i := range rows {
|
|
|
|
r := &rows[i]
|
|
|
|
p := float64(uint32(fastHashUint64(r.TSID.MetricID))) / (1 << 32)
|
|
|
|
if p < pMin {
|
|
|
|
// Fast path: it is too early to pre-fill indexes for the given MetricID.
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check whether the given MetricID is already present in dateMetricIDCache.
|
|
|
|
date := uint64(r.Timestamp) / msecPerDay
|
|
|
|
metricID := r.TSID.MetricID
|
|
|
|
if s.dateMetricIDCache.Has(generation, date, metricID) {
|
|
|
|
// Indexes are already pre-filled.
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check whether the given (date, metricID) is already present in idbNext.
|
|
|
|
if isNext.hasDateMetricIDNoExtDB(date, metricID) {
|
|
|
|
// Indexes are already pre-filled at idbNext.
|
|
|
|
//
|
|
|
|
// Register the (generation, date, metricID) entry in the cache,
|
|
|
|
// so next time the entry is found there instead of searching for it in the indexdb.
|
|
|
|
s.dateMetricIDCache.Set(generation, date, metricID)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Slow path: pre-fill indexes in idbNext.
|
|
|
|
metricNameRaw := mrs[i].MetricNameRaw
|
|
|
|
if err := mn.UnmarshalRaw(metricNameRaw); err != nil {
|
|
|
|
if firstError == nil {
|
|
|
|
firstError = fmt.Errorf("cannot unmarshal MetricNameRaw %q: %w", metricNameRaw, err)
|
|
|
|
}
|
2024-09-06 15:57:21 +00:00
|
|
|
s.invalidRawMetricNames.Add(1)
|
2023-07-22 22:20:21 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
mn.sortTags()
|
|
|
|
|
|
|
|
createAllIndexesForMetricName(isNext, mn, &r.TSID, date)
|
|
|
|
genTSID.TSID = r.TSID
|
|
|
|
genTSID.generation = generation
|
|
|
|
s.putSeriesToCache(metricNameRaw, &genTSID, date)
|
|
|
|
timeseriesPreCreated++
|
|
|
|
}
|
2024-02-23 22:15:21 +00:00
|
|
|
s.timeseriesPreCreated.Add(timeseriesPreCreated)
|
2023-07-22 22:20:21 +00:00
|
|
|
|
|
|
|
return firstError
|
|
|
|
}
|
|
|
|
|
2021-05-23 13:39:55 +00:00
|
|
|
func (s *Storage) updatePerDateData(rows []rawRow, mrs []*MetricRow) error {
|
2019-05-22 21:16:55 +00:00
|
|
|
var date uint64
|
2019-06-09 16:06:53 +00:00
|
|
|
var hour uint64
|
2019-05-22 21:16:55 +00:00
|
|
|
var prevTimestamp int64
|
2019-12-19 13:12:50 +00:00
|
|
|
var (
|
2021-03-09 07:18:19 +00:00
|
|
|
// These vars are used for speeding up bulk imports when multiple adjacent rows
|
2019-12-19 13:12:50 +00:00
|
|
|
// contain the same (metricID, date) pairs.
|
2020-05-14 20:45:04 +00:00
|
|
|
prevDate uint64
|
|
|
|
prevMetricID uint64
|
2019-12-19 13:12:50 +00:00
|
|
|
)
|
2023-07-22 22:20:21 +00:00
|
|
|
|
|
|
|
idb := s.idb()
|
|
|
|
generation := idb.generation
|
|
|
|
|
2023-07-20 00:37:49 +00:00
|
|
|
hm := s.currHourMetricIDs.Load()
|
|
|
|
hmPrev := s.prevHourMetricIDs.Load()
|
2021-02-04 16:46:20 +00:00
|
|
|
hmPrevDate := hmPrev.hour / 24
|
2023-07-20 00:37:49 +00:00
|
|
|
nextDayMetricIDs := &s.nextDayMetricIDs.Load().v
|
2022-02-12 14:28:46 +00:00
|
|
|
ts := fasttime.UnixTimestamp()
|
|
|
|
// Start pre-populating the next per-day inverted index during the last hour of the current day.
|
|
|
|
// pMin linearly increases from 0 to 1 during the last hour of the day.
|
|
|
|
pMin := (float64(ts%(3600*24)) / 3600) - 23
|
2020-05-14 20:45:04 +00:00
|
|
|
type pendingDateMetricID struct {
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
date uint64
|
|
|
|
tsid *TSID
|
|
|
|
mr *MetricRow
|
2020-05-14 20:45:04 +00:00
|
|
|
}
|
|
|
|
var pendingDateMetricIDs []pendingDateMetricID
|
2021-02-08 10:00:44 +00:00
|
|
|
var pendingNextDayMetricIDs []uint64
|
|
|
|
var pendingHourEntries []uint64
|
2019-05-22 21:16:55 +00:00
|
|
|
for i := range rows {
|
|
|
|
r := &rows[i]
|
|
|
|
if r.Timestamp != prevTimestamp {
|
|
|
|
date = uint64(r.Timestamp) / msecPerDay
|
2019-06-09 16:06:53 +00:00
|
|
|
hour = uint64(r.Timestamp) / msecPerHour
|
2019-05-22 21:16:55 +00:00
|
|
|
prevTimestamp = r.Timestamp
|
|
|
|
}
|
|
|
|
metricID := r.TSID.MetricID
|
2021-02-09 00:51:40 +00:00
|
|
|
if metricID == prevMetricID && date == prevDate {
|
|
|
|
// Fast path for bulk import of multiple rows with the same (date, metricID) pairs.
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
prevDate = date
|
|
|
|
prevMetricID = metricID
|
2019-06-09 16:06:53 +00:00
|
|
|
if hour == hm.hour {
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
// The row belongs to the current hour. Check for the current hour cache.
|
2019-09-24 18:10:22 +00:00
|
|
|
if hm.m.Has(metricID) {
|
2019-06-09 16:06:53 +00:00
|
|
|
// Fast path: the metricID is in the current hour cache.
|
2019-11-09 21:17:42 +00:00
|
|
|
// This means the metricID has been already added to per-day inverted index.
|
2020-05-11 22:06:17 +00:00
|
|
|
|
2022-02-12 14:28:46 +00:00
|
|
|
// Gradually pre-populate per-day inverted index for the next day during the last hour of the current day.
|
2020-05-11 22:06:17 +00:00
|
|
|
// This should reduce CPU usage spike and slowdown at the beginning of the next day
|
|
|
|
// when entries for all the active time series must be added to the index.
|
|
|
|
// This should address https://github.com/VictoriaMetrics/VictoriaMetrics/issues/430 .
|
2022-02-12 14:28:46 +00:00
|
|
|
if pMin > 0 {
|
|
|
|
p := float64(uint32(fastHashUint64(metricID))) / (1 << 32)
|
|
|
|
if p < pMin && !nextDayMetricIDs.Has(metricID) {
|
|
|
|
pendingDateMetricIDs = append(pendingDateMetricIDs, pendingDateMetricID{
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
date: date + 1,
|
|
|
|
tsid: &r.TSID,
|
|
|
|
mr: mrs[i],
|
2022-02-12 14:28:46 +00:00
|
|
|
})
|
|
|
|
pendingNextDayMetricIDs = append(pendingNextDayMetricIDs, metricID)
|
|
|
|
}
|
2020-05-11 22:06:17 +00:00
|
|
|
}
|
2019-06-02 15:34:08 +00:00
|
|
|
continue
|
|
|
|
}
|
2021-02-08 10:00:44 +00:00
|
|
|
pendingHourEntries = append(pendingHourEntries, metricID)
|
2021-02-04 16:46:20 +00:00
|
|
|
if date == hmPrevDate && hmPrev.m.Has(metricID) {
|
|
|
|
// The metricID is already registered for the current day on the previous hour.
|
|
|
|
continue
|
|
|
|
}
|
2019-06-02 15:34:08 +00:00
|
|
|
}
|
|
|
|
|
2023-07-22 22:20:21 +00:00
|
|
|
// Slower path: check global cache for (generation, date, metricID) entry.
|
|
|
|
if s.dateMetricIDCache.Has(generation, date, metricID) {
|
2019-12-19 13:12:50 +00:00
|
|
|
continue
|
|
|
|
}
|
2021-02-09 00:51:40 +00:00
|
|
|
// Slow path: store the (date, metricID) entry in the indexDB.
|
|
|
|
pendingDateMetricIDs = append(pendingDateMetricIDs, pendingDateMetricID{
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
date: date,
|
|
|
|
tsid: &r.TSID,
|
|
|
|
mr: mrs[i],
|
2021-02-09 00:51:40 +00:00
|
|
|
})
|
2020-05-14 20:45:04 +00:00
|
|
|
}
|
2021-02-08 10:00:44 +00:00
|
|
|
if len(pendingNextDayMetricIDs) > 0 {
|
|
|
|
s.pendingNextDayMetricIDsLock.Lock()
|
|
|
|
s.pendingNextDayMetricIDs.AddMulti(pendingNextDayMetricIDs)
|
|
|
|
s.pendingNextDayMetricIDsLock.Unlock()
|
|
|
|
}
|
|
|
|
if len(pendingHourEntries) > 0 {
|
|
|
|
s.pendingHourEntriesLock.Lock()
|
2022-11-07 12:04:06 +00:00
|
|
|
s.pendingHourEntries.AddMulti(pendingHourEntries)
|
2021-02-08 10:00:44 +00:00
|
|
|
s.pendingHourEntriesLock.Unlock()
|
|
|
|
}
|
2020-05-14 20:45:04 +00:00
|
|
|
if len(pendingDateMetricIDs) == 0 {
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
// Fast path - there are no new (date, metricID) entries.
|
2020-05-14 20:45:04 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Slow path - add new (date, metricID) entries to indexDB.
|
|
|
|
|
2024-02-23 22:15:21 +00:00
|
|
|
s.slowPerDayIndexInserts.Add(uint64(len(pendingDateMetricIDs)))
|
2020-05-14 20:45:04 +00:00
|
|
|
// Sort pendingDateMetricIDs by (date, metricID) in order to speed up `is` search in the loop below.
|
|
|
|
sort.Slice(pendingDateMetricIDs, func(i, j int) bool {
|
|
|
|
a := pendingDateMetricIDs[i]
|
|
|
|
b := pendingDateMetricIDs[j]
|
|
|
|
if a.date != b.date {
|
|
|
|
return a.date < b.date
|
|
|
|
}
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
return a.tsid.MetricID < b.tsid.MetricID
|
2020-05-14 20:45:04 +00:00
|
|
|
})
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
|
2020-07-23 17:42:57 +00:00
|
|
|
is := idb.getIndexSearch(noDeadline)
|
2020-05-14 20:45:04 +00:00
|
|
|
defer idb.putIndexSearch(is)
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
|
2020-05-14 20:45:04 +00:00
|
|
|
var firstError error
|
2021-02-09 21:59:14 +00:00
|
|
|
dateMetricIDsForCache := make([]dateMetricID, 0, len(pendingDateMetricIDs))
|
2021-05-23 13:39:55 +00:00
|
|
|
mn := GetMetricName()
|
2021-02-09 21:59:14 +00:00
|
|
|
for _, dmid := range pendingDateMetricIDs {
|
|
|
|
date := dmid.date
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
metricID := dmid.tsid.MetricID
|
|
|
|
if !is.hasDateMetricIDNoExtDB(date, metricID) {
|
2022-06-19 18:58:53 +00:00
|
|
|
// The (date, metricID) entry is missing in the indexDB. Add it there together with per-day indexes.
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
// It is OK if the (date, metricID) entry is added multiple times to indexdb
|
2021-02-09 00:51:40 +00:00
|
|
|
// by concurrent goroutines.
|
2021-05-23 13:39:55 +00:00
|
|
|
if err := mn.UnmarshalRaw(dmid.mr.MetricNameRaw); err != nil {
|
|
|
|
if firstError == nil {
|
|
|
|
firstError = fmt.Errorf("cannot unmarshal MetricNameRaw %q: %w", dmid.mr.MetricNameRaw, err)
|
|
|
|
}
|
2024-09-06 15:57:21 +00:00
|
|
|
s.invalidRawMetricNames.Add(1)
|
2021-05-23 13:39:55 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
mn.sortTags()
|
lib/storage: switch from global to per-day index for `MetricName -> TSID` mapping
Previously all the newly ingested time series were registered in global `MetricName -> TSID` index.
This index was used during data ingestion for locating the TSID (internal series id)
for the given canonical metric name (the canonical metric name consists of metric name plus all its labels sorted by label names).
The `MetricName -> TSID` index is stored on disk in order to make sure that the data
isn't lost on VictoriaMetrics restart or unclean shutdown.
The lookup in this index is relatively slow, since VictoriaMetrics needs to read the corresponding
data block from disk, unpack it, put the unpacked block into `indexdb/dataBlocks` cache,
and then search for the given `MetricName -> TSID` entry there. So VictoriaMetrics
uses in-memory cache for speeding up the lookup for active time series.
This cache is named `storage/tsid`. If this cache capacity is enough for all the currently ingested
active time series, then VictoriaMetrics works fast, since it doesn't need to read the data from disk.
VictoriaMetrics starts reading data from `MetricName -> TSID` on-disk index in the following cases:
- If `storage/tsid` cache capacity isn't enough for active time series.
Then just increase available memory for VictoriaMetrics or reduce the number of active time series
ingested into VictoriaMetrics.
- If new time series is ingested into VictoriaMetrics. In this case it cannot find
the needed entry in the `storage/tsid` cache, so it needs to consult on-disk `MetricName -> TSID` index,
since it doesn't know that the index has no the corresponding entry too.
This is a typical event under high churn rate, when old time series are constantly substituted
with new time series.
Reading the data from `MetricName -> TSID` index is slow, so inserts, which lead to reading this index,
are counted as slow inserts, and they can be monitored via `vm_slow_row_inserts_total` metric exposed by VictoriaMetrics.
Prior to this commit the `MetricName -> TSID` index was global, e.g. it contained entries sorted by `MetricName`
for all the time series ever ingested into VictoriaMetrics during the configured -retentionPeriod.
This index can become very large under high churn rate and long retention. VictoriaMetrics
caches data from this index in `indexdb/dataBlocks` in-memory cache for speeding up index lookups.
The `indexdb/dataBlocks` cache may occupy significant share of available memory for storing
recently accessed blocks at `MetricName -> TSID` index when searching for newly ingested time series.
This commit switches from global `MetricName -> TSID` index to per-day index. This allows significantly
reducing the amounts of data, which needs to be cached in `indexdb/dataBlocks`, since now VictoriaMetrics
consults only the index for the current day when new time series is ingested into it.
The downside of this change is increased indexdb size on disk for workloads without high churn rate,
e.g. with static time series, which do no change over time, since now VictoriaMetrics needs to store
identical `MetricName -> TSID` entries for static time series for every day.
This change removes an optimization for reducing CPU and disk IO spikes at indexdb rotation,
since it didn't work correctly - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401 .
At the same time the change fixes the issue, which could result in lost access to time series,
which stop receving new samples during the first hour after indexdb rotation - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2698
The issue with the increased CPU and disk IO usage during indexdb rotation will be addressed
in a separate commit according to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401#issuecomment-1553488685
This is a follow-up for 1f28b46ae9350795af41cbfc3ca0e8a5af084fce
2023-07-13 22:33:41 +00:00
|
|
|
is.createPerDayIndexes(date, dmid.tsid, mn)
|
2020-05-14 20:45:04 +00:00
|
|
|
}
|
2023-05-18 18:11:42 +00:00
|
|
|
dateMetricIDsForCache = append(dateMetricIDsForCache, dateMetricID{
|
|
|
|
date: date,
|
|
|
|
metricID: metricID,
|
|
|
|
})
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
2021-05-23 13:39:55 +00:00
|
|
|
PutMetricName(mn)
|
2021-02-09 21:59:14 +00:00
|
|
|
// The (date, metricID) entries must be added to cache only after they have been successfully added to indexDB.
|
2023-07-22 22:20:21 +00:00
|
|
|
s.dateMetricIDCache.Store(generation, dateMetricIDsForCache)
|
2020-05-14 20:17:22 +00:00
|
|
|
return firstError
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
|
|
|
|
2022-02-12 14:28:46 +00:00
|
|
|
func fastHashUint64(x uint64) uint64 {
|
|
|
|
x ^= x >> 12 // a
|
|
|
|
x ^= x << 25 // b
|
|
|
|
x ^= x >> 27 // c
|
|
|
|
return x * 2685821657736338717
|
|
|
|
}
|
|
|
|
|
2019-11-09 21:05:14 +00:00
|
|
|
// dateMetricIDCache is fast cache for holding (date, metricID) entries.
|
|
|
|
//
|
|
|
|
// It should be faster than map[date]*uint64set.Set on multicore systems.
|
|
|
|
type dateMetricIDCache struct {
|
2024-02-23 22:15:21 +00:00
|
|
|
syncsCount atomic.Uint64
|
|
|
|
resetsCount atomic.Uint64
|
2019-11-11 11:21:05 +00:00
|
|
|
|
2019-11-09 21:05:14 +00:00
|
|
|
// Contains immutable map
|
2023-07-20 00:37:49 +00:00
|
|
|
byDate atomic.Pointer[byDateMetricIDMap]
|
2019-11-09 21:05:14 +00:00
|
|
|
|
|
|
|
// Contains mutable map protected by mu
|
2024-01-22 13:47:07 +00:00
|
|
|
byDateMutable *byDateMetricIDMap
|
|
|
|
|
|
|
|
// Contains the number of slow accesses to byDateMutable.
|
|
|
|
// Is used for deciding when to merge byDateMutable to byDate.
|
|
|
|
// Protected by mu.
|
|
|
|
slowHits int
|
|
|
|
|
|
|
|
mu sync.Mutex
|
2019-11-09 21:05:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func newDateMetricIDCache() *dateMetricIDCache {
|
|
|
|
var dmc dateMetricIDCache
|
2021-06-03 13:19:58 +00:00
|
|
|
dmc.resetLocked()
|
2019-11-09 21:05:14 +00:00
|
|
|
return &dmc
|
|
|
|
}
|
|
|
|
|
2021-06-03 13:19:58 +00:00
|
|
|
func (dmc *dateMetricIDCache) resetLocked() {
|
2019-11-11 11:21:05 +00:00
|
|
|
// Do not reset syncsCount and resetsCount
|
2019-11-09 21:05:14 +00:00
|
|
|
dmc.byDate.Store(newByDateMetricIDMap())
|
|
|
|
dmc.byDateMutable = newByDateMetricIDMap()
|
2024-01-22 13:47:07 +00:00
|
|
|
dmc.slowHits = 0
|
2019-11-11 11:21:05 +00:00
|
|
|
|
2024-02-23 22:15:21 +00:00
|
|
|
dmc.resetsCount.Add(1)
|
2019-11-09 21:05:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (dmc *dateMetricIDCache) EntriesCount() int {
|
2023-07-20 00:37:49 +00:00
|
|
|
byDate := dmc.byDate.Load()
|
2019-11-09 21:05:14 +00:00
|
|
|
n := 0
|
|
|
|
for _, e := range byDate.m {
|
|
|
|
n += e.v.Len()
|
|
|
|
}
|
|
|
|
return n
|
|
|
|
}
|
|
|
|
|
2019-11-13 15:58:05 +00:00
|
|
|
func (dmc *dateMetricIDCache) SizeBytes() uint64 {
|
2023-07-20 00:37:49 +00:00
|
|
|
byDate := dmc.byDate.Load()
|
2019-11-13 15:58:05 +00:00
|
|
|
n := uint64(0)
|
|
|
|
for _, e := range byDate.m {
|
|
|
|
n += e.v.SizeBytes()
|
|
|
|
}
|
|
|
|
return n
|
|
|
|
}
|
|
|
|
|
2023-07-22 22:20:21 +00:00
|
|
|
func (dmc *dateMetricIDCache) Has(generation, date, metricID uint64) bool {
|
2024-06-26 15:33:38 +00:00
|
|
|
if byDate := dmc.byDate.Load(); byDate.get(generation, date).Has(metricID) {
|
|
|
|
// Fast path. The majority of calls must go here.
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
// Slow path. Acquire the lock and search the immutable map again and then
|
|
|
|
// also search the mutable map.
|
|
|
|
return dmc.hasSlow(generation, date, metricID)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (dmc *dateMetricIDCache) hasSlow(generation, date, metricID uint64) bool {
|
|
|
|
dmc.mu.Lock()
|
|
|
|
defer dmc.mu.Unlock()
|
|
|
|
|
|
|
|
// First, check immutable map again because the entry may have been moved to
|
|
|
|
// the immutable map by the time the caller acquires the lock.
|
2023-07-20 00:37:49 +00:00
|
|
|
byDate := dmc.byDate.Load()
|
2023-07-22 22:20:21 +00:00
|
|
|
v := byDate.get(generation, date)
|
2019-11-09 21:05:14 +00:00
|
|
|
if v.Has(metricID) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2024-06-26 15:33:38 +00:00
|
|
|
// Then check immutable map.
|
2024-01-22 13:47:07 +00:00
|
|
|
vMutable := dmc.byDateMutable.get(generation, date)
|
|
|
|
ok := vMutable.Has(metricID)
|
|
|
|
if ok {
|
|
|
|
dmc.slowHits++
|
|
|
|
if dmc.slowHits > (v.Len()+vMutable.Len())/2 {
|
|
|
|
// It is cheaper to merge byDateMutable into byDate than to pay inter-cpu sync costs when accessing vMutable.
|
|
|
|
dmc.syncLocked()
|
|
|
|
dmc.slowHits = 0
|
|
|
|
}
|
|
|
|
}
|
2019-11-09 21:05:14 +00:00
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
2021-02-09 21:59:14 +00:00
|
|
|
type dateMetricID struct {
|
|
|
|
date uint64
|
|
|
|
metricID uint64
|
|
|
|
}
|
|
|
|
|
2023-07-22 22:20:21 +00:00
|
|
|
func (dmc *dateMetricIDCache) Store(generation uint64, dmids []dateMetricID) {
|
2021-02-09 21:59:14 +00:00
|
|
|
var prevDate uint64
|
|
|
|
metricIDs := make([]uint64, 0, len(dmids))
|
|
|
|
dmc.mu.Lock()
|
|
|
|
for _, dmid := range dmids {
|
|
|
|
if prevDate == dmid.date {
|
|
|
|
metricIDs = append(metricIDs, dmid.metricID)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if len(metricIDs) > 0 {
|
2023-07-22 22:20:21 +00:00
|
|
|
v := dmc.byDateMutable.getOrCreate(generation, prevDate)
|
2021-02-09 21:59:14 +00:00
|
|
|
v.AddMulti(metricIDs)
|
|
|
|
}
|
|
|
|
metricIDs = append(metricIDs[:0], dmid.metricID)
|
|
|
|
prevDate = dmid.date
|
|
|
|
}
|
|
|
|
if len(metricIDs) > 0 {
|
2023-07-22 22:20:21 +00:00
|
|
|
v := dmc.byDateMutable.getOrCreate(generation, prevDate)
|
2021-02-09 21:59:14 +00:00
|
|
|
v.AddMulti(metricIDs)
|
|
|
|
}
|
|
|
|
dmc.mu.Unlock()
|
|
|
|
}
|
|
|
|
|
2023-07-22 22:20:21 +00:00
|
|
|
func (dmc *dateMetricIDCache) Set(generation, date, metricID uint64) {
|
2019-11-09 21:05:14 +00:00
|
|
|
dmc.mu.Lock()
|
2023-07-22 22:20:21 +00:00
|
|
|
v := dmc.byDateMutable.getOrCreate(generation, date)
|
2019-11-09 21:05:14 +00:00
|
|
|
v.Add(metricID)
|
|
|
|
dmc.mu.Unlock()
|
|
|
|
}
|
|
|
|
|
2021-06-03 13:19:58 +00:00
|
|
|
func (dmc *dateMetricIDCache) syncLocked() {
|
|
|
|
if len(dmc.byDateMutable.m) == 0 {
|
|
|
|
// Nothing to sync.
|
|
|
|
return
|
|
|
|
}
|
2023-07-22 22:20:21 +00:00
|
|
|
|
|
|
|
// Merge data from byDate into byDateMutable and then atomically replace byDate with the merged data.
|
2023-07-20 00:37:49 +00:00
|
|
|
byDate := dmc.byDate.Load()
|
2021-06-03 13:19:58 +00:00
|
|
|
byDateMutable := dmc.byDateMutable
|
2024-01-29 17:42:45 +00:00
|
|
|
byDateMutable.hotEntry.Store(&byDateMetricIDEntry{})
|
|
|
|
|
2024-02-23 02:04:03 +00:00
|
|
|
keepDatesMap := make(map[uint64]struct{}, len(byDateMutable.m))
|
2023-07-22 22:20:21 +00:00
|
|
|
for k, e := range byDateMutable.m {
|
2024-02-23 02:04:03 +00:00
|
|
|
keepDatesMap[k.date] = struct{}{}
|
2023-07-22 22:20:21 +00:00
|
|
|
v := byDate.get(k.generation, k.date)
|
2021-06-03 13:19:58 +00:00
|
|
|
if v == nil {
|
2023-07-22 22:20:21 +00:00
|
|
|
// Nothing to merge
|
2021-06-03 13:19:58 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
v = v.Clone()
|
|
|
|
v.Union(&e.v)
|
2023-05-06 04:45:47 +00:00
|
|
|
dme := &byDateMetricIDEntry{
|
2023-07-22 22:20:21 +00:00
|
|
|
k: k,
|
|
|
|
v: *v,
|
2021-06-03 13:19:58 +00:00
|
|
|
}
|
2023-07-22 22:20:21 +00:00
|
|
|
byDateMutable.m[k] = dme
|
2021-06-03 13:19:58 +00:00
|
|
|
}
|
2024-01-29 17:42:45 +00:00
|
|
|
|
2023-07-22 22:20:21 +00:00
|
|
|
// Copy entries from byDate, which are missing in byDateMutable
|
2024-02-23 02:04:03 +00:00
|
|
|
allDatesMap := make(map[uint64]struct{}, len(byDate.m))
|
2023-07-22 22:20:21 +00:00
|
|
|
for k, e := range byDate.m {
|
2024-02-23 02:04:03 +00:00
|
|
|
allDatesMap[k.date] = struct{}{}
|
2023-07-22 22:20:21 +00:00
|
|
|
v := byDateMutable.get(k.generation, k.date)
|
2021-06-03 13:19:58 +00:00
|
|
|
if v != nil {
|
|
|
|
continue
|
|
|
|
}
|
2023-07-22 22:20:21 +00:00
|
|
|
byDateMutable.m[k] = e
|
2019-11-09 21:05:14 +00:00
|
|
|
}
|
2023-07-22 22:20:21 +00:00
|
|
|
|
2024-01-29 17:42:45 +00:00
|
|
|
if len(byDateMutable.m) > 2 {
|
2024-02-23 02:04:03 +00:00
|
|
|
// Keep only entries for the last two dates from allDatesMap plus all the entries for byDateMutable.
|
|
|
|
dates := make([]uint64, 0, len(allDatesMap))
|
|
|
|
for date := range allDatesMap {
|
|
|
|
dates = append(dates, date)
|
2024-01-29 17:42:45 +00:00
|
|
|
}
|
|
|
|
sort.Slice(dates, func(i, j int) bool {
|
|
|
|
return dates[i] < dates[j]
|
|
|
|
})
|
2024-02-23 02:04:03 +00:00
|
|
|
if len(dates) > 2 {
|
|
|
|
dates = dates[len(dates)-2:]
|
|
|
|
}
|
|
|
|
for _, date := range dates {
|
|
|
|
keepDatesMap[date] = struct{}{}
|
|
|
|
}
|
2024-01-29 17:42:45 +00:00
|
|
|
for k := range byDateMutable.m {
|
2024-02-23 02:04:03 +00:00
|
|
|
if _, ok := keepDatesMap[k.date]; !ok {
|
2024-01-29 17:42:45 +00:00
|
|
|
delete(byDateMutable.m, k)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-22 22:20:21 +00:00
|
|
|
// Atomically replace byDate with byDateMutable
|
2019-11-09 21:05:14 +00:00
|
|
|
dmc.byDate.Store(dmc.byDateMutable)
|
2019-11-11 11:21:05 +00:00
|
|
|
dmc.byDateMutable = newByDateMetricIDMap()
|
2019-11-09 21:05:14 +00:00
|
|
|
|
2024-02-23 22:15:21 +00:00
|
|
|
dmc.syncsCount.Add(1)
|
2019-11-11 11:21:05 +00:00
|
|
|
|
2021-07-12 11:25:14 +00:00
|
|
|
if dmc.SizeBytes() > uint64(memory.Allowed())/256 {
|
2021-06-03 13:19:58 +00:00
|
|
|
dmc.resetLocked()
|
2019-11-09 21:05:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type byDateMetricIDMap struct {
|
2023-07-20 00:37:49 +00:00
|
|
|
hotEntry atomic.Pointer[byDateMetricIDEntry]
|
2023-07-22 22:20:21 +00:00
|
|
|
m map[generationDateKey]*byDateMetricIDEntry
|
|
|
|
}
|
|
|
|
|
|
|
|
type generationDateKey struct {
|
|
|
|
generation uint64
|
|
|
|
date uint64
|
2019-11-09 21:05:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func newByDateMetricIDMap() *byDateMetricIDMap {
|
|
|
|
dmm := &byDateMetricIDMap{
|
2023-07-22 22:20:21 +00:00
|
|
|
m: make(map[generationDateKey]*byDateMetricIDEntry),
|
2019-11-09 21:05:14 +00:00
|
|
|
}
|
|
|
|
dmm.hotEntry.Store(&byDateMetricIDEntry{})
|
|
|
|
return dmm
|
|
|
|
}
|
|
|
|
|
2023-07-22 22:20:21 +00:00
|
|
|
func (dmm *byDateMetricIDMap) get(generation, date uint64) *uint64set.Set {
|
2023-07-20 00:37:49 +00:00
|
|
|
hotEntry := dmm.hotEntry.Load()
|
2023-07-22 22:20:21 +00:00
|
|
|
if hotEntry.k.generation == generation && hotEntry.k.date == date {
|
2019-11-09 21:05:14 +00:00
|
|
|
// Fast path
|
|
|
|
return &hotEntry.v
|
|
|
|
}
|
|
|
|
// Slow path
|
2023-07-22 22:20:21 +00:00
|
|
|
k := generationDateKey{
|
|
|
|
generation: generation,
|
|
|
|
date: date,
|
|
|
|
}
|
|
|
|
e := dmm.m[k]
|
2019-11-09 21:05:14 +00:00
|
|
|
if e == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
dmm.hotEntry.Store(e)
|
|
|
|
return &e.v
|
|
|
|
}
|
|
|
|
|
2023-07-22 22:20:21 +00:00
|
|
|
func (dmm *byDateMetricIDMap) getOrCreate(generation, date uint64) *uint64set.Set {
|
|
|
|
v := dmm.get(generation, date)
|
2019-11-09 21:05:14 +00:00
|
|
|
if v != nil {
|
|
|
|
return v
|
|
|
|
}
|
2023-07-22 22:20:21 +00:00
|
|
|
k := generationDateKey{
|
|
|
|
generation: generation,
|
|
|
|
date: date,
|
|
|
|
}
|
2019-11-09 21:05:14 +00:00
|
|
|
e := &byDateMetricIDEntry{
|
2023-07-22 22:20:21 +00:00
|
|
|
k: k,
|
2019-11-09 21:05:14 +00:00
|
|
|
}
|
2023-07-22 22:20:21 +00:00
|
|
|
dmm.m[k] = e
|
2019-11-09 21:05:14 +00:00
|
|
|
return &e.v
|
|
|
|
}
|
|
|
|
|
|
|
|
type byDateMetricIDEntry struct {
|
2023-07-22 22:20:21 +00:00
|
|
|
k generationDateKey
|
|
|
|
v uint64set.Set
|
2019-11-09 21:05:14 +00:00
|
|
|
}
|
|
|
|
|
2022-11-07 12:04:06 +00:00
|
|
|
func (s *Storage) updateNextDayMetricIDs(date uint64) {
|
2023-07-22 22:20:21 +00:00
|
|
|
generation := s.idb().generation
|
2023-07-20 00:37:49 +00:00
|
|
|
e := s.nextDayMetricIDs.Load()
|
2020-05-11 22:06:17 +00:00
|
|
|
s.pendingNextDayMetricIDsLock.Lock()
|
|
|
|
pendingMetricIDs := s.pendingNextDayMetricIDs
|
|
|
|
s.pendingNextDayMetricIDs = &uint64set.Set{}
|
|
|
|
s.pendingNextDayMetricIDsLock.Unlock()
|
2023-07-22 22:20:21 +00:00
|
|
|
if pendingMetricIDs.Len() == 0 && e.k.generation == generation && e.k.date == date {
|
2020-05-11 22:06:17 +00:00
|
|
|
// Fast path: nothing to update.
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Slow path: union pendingMetricIDs with e.v
|
2023-07-22 22:20:21 +00:00
|
|
|
if e.k.generation == generation && e.k.date == date {
|
2020-05-11 22:06:17 +00:00
|
|
|
pendingMetricIDs.Union(&e.v)
|
2022-11-07 12:04:06 +00:00
|
|
|
} else {
|
2022-12-03 01:07:13 +00:00
|
|
|
// Do not add pendingMetricIDs from the previous day to the current day,
|
2022-11-07 12:04:06 +00:00
|
|
|
// since this may result in missing registration of the metricIDs in the per-day inverted index.
|
|
|
|
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3309
|
|
|
|
pendingMetricIDs = &uint64set.Set{}
|
2020-05-11 22:06:17 +00:00
|
|
|
}
|
2023-07-22 22:20:21 +00:00
|
|
|
k := generationDateKey{
|
|
|
|
generation: generation,
|
|
|
|
date: date,
|
|
|
|
}
|
2020-05-11 22:06:17 +00:00
|
|
|
eNew := &byDateMetricIDEntry{
|
2023-07-22 22:20:21 +00:00
|
|
|
k: k,
|
|
|
|
v: *pendingMetricIDs,
|
2020-05-11 22:06:17 +00:00
|
|
|
}
|
|
|
|
s.nextDayMetricIDs.Store(eNew)
|
|
|
|
}
|
|
|
|
|
2022-11-07 11:55:37 +00:00
|
|
|
func (s *Storage) updateCurrHourMetricIDs(hour uint64) {
|
2023-07-20 00:37:49 +00:00
|
|
|
hm := s.currHourMetricIDs.Load()
|
2019-11-08 17:37:16 +00:00
|
|
|
s.pendingHourEntriesLock.Lock()
|
|
|
|
newMetricIDs := s.pendingHourEntries
|
2022-11-07 12:04:06 +00:00
|
|
|
s.pendingHourEntries = &uint64set.Set{}
|
2019-11-08 17:37:16 +00:00
|
|
|
s.pendingHourEntriesLock.Unlock()
|
2022-06-19 17:48:42 +00:00
|
|
|
|
2022-11-07 12:04:06 +00:00
|
|
|
if newMetricIDs.Len() == 0 && hm.hour == hour {
|
2019-06-09 16:06:53 +00:00
|
|
|
// Fast path: nothing to update.
|
2019-06-02 15:34:08 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-11-08 17:37:16 +00:00
|
|
|
// Slow path: hm.m must be updated with non-empty s.pendingHourEntries.
|
2019-09-24 18:10:22 +00:00
|
|
|
var m *uint64set.Set
|
2019-06-09 16:06:53 +00:00
|
|
|
if hm.hour == hour {
|
2019-09-24 18:10:22 +00:00
|
|
|
m = hm.m.Clone()
|
2022-11-07 12:04:06 +00:00
|
|
|
m.Union(newMetricIDs)
|
2022-12-03 01:07:13 +00:00
|
|
|
} else {
|
|
|
|
m = newMetricIDs
|
|
|
|
if hour%24 == 0 {
|
|
|
|
// Do not add pending metricIDs from the previous hour to the current hour on the next day,
|
|
|
|
// since this may result in missing registration of the metricIDs in the per-day inverted index.
|
|
|
|
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3309
|
|
|
|
m = &uint64set.Set{}
|
|
|
|
}
|
2022-11-07 12:04:06 +00:00
|
|
|
}
|
2019-06-09 16:06:53 +00:00
|
|
|
hmNew := &hourMetricIDs{
|
2022-11-07 11:06:50 +00:00
|
|
|
m: m,
|
|
|
|
hour: hour,
|
2019-06-09 16:06:53 +00:00
|
|
|
}
|
|
|
|
s.currHourMetricIDs.Store(hmNew)
|
|
|
|
if hm.hour != hour {
|
|
|
|
s.prevHourMetricIDs.Store(hm)
|
2019-06-02 15:34:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-09 16:06:53 +00:00
|
|
|
type hourMetricIDs struct {
|
2022-11-07 11:06:50 +00:00
|
|
|
m *uint64set.Set
|
|
|
|
hour uint64
|
2019-06-02 15:34:08 +00:00
|
|
|
}
|
|
|
|
|
lib/index: reduce read/write load after indexDB rotation (#2177)
* lib/index: reduce read/write load after indexDB rotation
IndexDB in VM is responsible for storing TSID - ID's used for identifying
time series. The index is stored on disk and used by both ingestion and read path.
IndexDB is stored separately to data parts and is global for all stored data.
It can't be deleted partially as VM deletes data parts. Instead, indexDB is
rotated once in `retention` interval.
The rotation procedure means that `current` indexDB becomes `previous`,
and new freshly created indexDB struct becomes `current`. So in any time,
VM holds indexDB for current and previous retention periods.
When time series is ingested or queried, VM checks if its TSID is present
in `current` indexDB. If it is missing, it checks the `previous` indexDB.
If TSID was found, it gets copied to the `current` indexDB. In this way
`current` indexDB stores only series which were active during the retention
period.
To improve indexDB lookups, VM uses a cache layer called `tsidCache`. Both
write and read path consult `tsidCache` and on miss the relad lookup happens.
When rotation happens, VM resets the `tsidCache`. This is needed for ingestion
path to trigger `current` indexDB re-population. Since index re-population
requires additional resources, every index rotation event may cause some extra
load on CPU and disk. While it may be unnoticeable for most of the cases,
for systems with very high number of unique series each rotation may lead
to performance degradation for some period of time.
This PR makes an attempt to smooth out resource usage after the rotation.
The changes are following:
1. `tsidCache` is no longer reset after the rotation;
2. Instead, each entry in `tsidCache` gains a notion of indexDB to which
they belong;
3. On ingestion path after the rotation we check if requested TSID was
found in `tsidCache`. Then we have 3 branches:
3.1 Fast path. It was found, and belongs to the `current` indexDB. Return TSID.
3.2 Slow path. It wasn't found, so we generate it from scratch,
add to `current` indexDB, add it to `tsidCache`.
3.3 Smooth path. It was found but does not belong to the `current` indexDB.
In this case, we add it to the `current` indexDB with some probability.
The probability is based on time passed since the last rotation with some threshold.
The more time has passed since rotation the higher is chance to re-populate `current` indexDB.
The default re-population interval in this PR is set to `1h`, during which entries from
`previous` index supposed to slowly re-populate `current` index.
The new metric `vm_timeseries_repopulated_total` was added to identify how many TSIDs
were moved from `previous` indexDB to the `current` indexDB. This metric supposed to
grow only during the first `1h` after the last rotation.
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401
Signed-off-by: hagen1778 <roman@victoriametrics.com>
* wip
* wip
Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
2022-02-11 22:30:08 +00:00
|
|
|
type generationTSID struct {
|
|
|
|
TSID TSID
|
|
|
|
|
|
|
|
// generation stores the indexdb.generation value to identify to which indexdb belongs this TSID
|
|
|
|
generation uint64
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Storage) getTSIDFromCache(dst *generationTSID, metricName []byte) bool {
|
2019-05-22 21:16:55 +00:00
|
|
|
buf := (*[unsafe.Sizeof(*dst)]byte)(unsafe.Pointer(dst))[:]
|
|
|
|
buf = s.tsidCache.Get(buf[:0], metricName)
|
|
|
|
return uintptr(len(buf)) == unsafe.Sizeof(*dst)
|
|
|
|
}
|
|
|
|
|
lib/index: reduce read/write load after indexDB rotation (#2177)
* lib/index: reduce read/write load after indexDB rotation
IndexDB in VM is responsible for storing TSID - ID's used for identifying
time series. The index is stored on disk and used by both ingestion and read path.
IndexDB is stored separately to data parts and is global for all stored data.
It can't be deleted partially as VM deletes data parts. Instead, indexDB is
rotated once in `retention` interval.
The rotation procedure means that `current` indexDB becomes `previous`,
and new freshly created indexDB struct becomes `current`. So in any time,
VM holds indexDB for current and previous retention periods.
When time series is ingested or queried, VM checks if its TSID is present
in `current` indexDB. If it is missing, it checks the `previous` indexDB.
If TSID was found, it gets copied to the `current` indexDB. In this way
`current` indexDB stores only series which were active during the retention
period.
To improve indexDB lookups, VM uses a cache layer called `tsidCache`. Both
write and read path consult `tsidCache` and on miss the relad lookup happens.
When rotation happens, VM resets the `tsidCache`. This is needed for ingestion
path to trigger `current` indexDB re-population. Since index re-population
requires additional resources, every index rotation event may cause some extra
load on CPU and disk. While it may be unnoticeable for most of the cases,
for systems with very high number of unique series each rotation may lead
to performance degradation for some period of time.
This PR makes an attempt to smooth out resource usage after the rotation.
The changes are following:
1. `tsidCache` is no longer reset after the rotation;
2. Instead, each entry in `tsidCache` gains a notion of indexDB to which
they belong;
3. On ingestion path after the rotation we check if requested TSID was
found in `tsidCache`. Then we have 3 branches:
3.1 Fast path. It was found, and belongs to the `current` indexDB. Return TSID.
3.2 Slow path. It wasn't found, so we generate it from scratch,
add to `current` indexDB, add it to `tsidCache`.
3.3 Smooth path. It was found but does not belong to the `current` indexDB.
In this case, we add it to the `current` indexDB with some probability.
The probability is based on time passed since the last rotation with some threshold.
The more time has passed since rotation the higher is chance to re-populate `current` indexDB.
The default re-population interval in this PR is set to `1h`, during which entries from
`previous` index supposed to slowly re-populate `current` index.
The new metric `vm_timeseries_repopulated_total` was added to identify how many TSIDs
were moved from `previous` indexDB to the `current` indexDB. This metric supposed to
grow only during the first `1h` after the last rotation.
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1401
Signed-off-by: hagen1778 <roman@victoriametrics.com>
* wip
* wip
Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
2022-02-11 22:30:08 +00:00
|
|
|
func (s *Storage) putTSIDToCache(tsid *generationTSID, metricName []byte) {
|
2019-05-22 21:16:55 +00:00
|
|
|
buf := (*[unsafe.Sizeof(*tsid)]byte)(unsafe.Pointer(tsid))[:]
|
|
|
|
s.tsidCache.Set(metricName, buf)
|
|
|
|
}
|
|
|
|
|
2023-07-22 22:20:21 +00:00
|
|
|
func (s *Storage) mustOpenIndexDBTables(path string) (next, curr, prev *indexDB) {
|
2023-04-14 05:11:56 +00:00
|
|
|
fs.MustMkdirIfNotExist(path)
|
2022-09-13 10:10:33 +00:00
|
|
|
fs.MustRemoveTemporaryDirs(path)
|
2019-05-22 21:16:55 +00:00
|
|
|
|
2023-07-22 22:20:21 +00:00
|
|
|
// Search for the three most recent tables - the prev, curr and next.
|
2023-04-15 05:08:43 +00:00
|
|
|
des := fs.MustReadDir(path)
|
2019-05-22 21:16:55 +00:00
|
|
|
var tableNames []string
|
2023-03-18 04:03:34 +00:00
|
|
|
for _, de := range des {
|
|
|
|
if !fs.IsDirOrSymlink(de) {
|
2019-05-22 21:16:55 +00:00
|
|
|
// Skip non-directories.
|
|
|
|
continue
|
|
|
|
}
|
2023-03-18 04:03:34 +00:00
|
|
|
tableName := de.Name()
|
2019-05-22 21:16:55 +00:00
|
|
|
if !indexDBTableNameRegexp.MatchString(tableName) {
|
|
|
|
// Skip invalid directories.
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
tableNames = append(tableNames, tableName)
|
|
|
|
}
|
|
|
|
sort.Slice(tableNames, func(i, j int) bool {
|
|
|
|
return tableNames[i] < tableNames[j]
|
|
|
|
})
|
2023-07-22 22:20:21 +00:00
|
|
|
switch len(tableNames) {
|
|
|
|
case 0:
|
|
|
|
prevName := nextIndexDBTableName()
|
2019-05-22 21:16:55 +00:00
|
|
|
currName := nextIndexDBTableName()
|
2023-07-22 22:20:21 +00:00
|
|
|
nextName := nextIndexDBTableName()
|
|
|
|
tableNames = append(tableNames, prevName, currName, nextName)
|
|
|
|
case 1:
|
|
|
|
currName := nextIndexDBTableName()
|
|
|
|
nextName := nextIndexDBTableName()
|
|
|
|
tableNames = append(tableNames, currName, nextName)
|
|
|
|
case 2:
|
|
|
|
nextName := nextIndexDBTableName()
|
|
|
|
tableNames = append(tableNames, nextName)
|
|
|
|
default:
|
|
|
|
// Remove all the tables except the last three tables.
|
|
|
|
for _, tn := range tableNames[:len(tableNames)-3] {
|
|
|
|
pathToRemove := filepath.Join(path, tn)
|
|
|
|
logger.Infof("removing obsolete indexdb dir %q...", pathToRemove)
|
|
|
|
fs.MustRemoveAll(pathToRemove)
|
|
|
|
logger.Infof("removed obsolete indexdb dir %q", pathToRemove)
|
|
|
|
}
|
|
|
|
fs.MustSyncPath(path)
|
2019-05-22 21:16:55 +00:00
|
|
|
|
2023-07-22 22:20:21 +00:00
|
|
|
tableNames = tableNames[len(tableNames)-3:]
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
|
|
|
|
2023-07-22 22:20:21 +00:00
|
|
|
// Open tables
|
|
|
|
nextPath := filepath.Join(path, tableNames[2])
|
|
|
|
currPath := filepath.Join(path, tableNames[1])
|
|
|
|
prevPath := filepath.Join(path, tableNames[0])
|
2019-05-22 21:16:55 +00:00
|
|
|
|
2023-07-22 22:20:21 +00:00
|
|
|
next = mustOpenIndexDB(nextPath, s, &s.isReadOnly)
|
|
|
|
curr = mustOpenIndexDB(currPath, s, &s.isReadOnly)
|
|
|
|
prev = mustOpenIndexDB(prevPath, s, &s.isReadOnly)
|
2019-05-22 21:16:55 +00:00
|
|
|
|
2023-07-22 22:20:21 +00:00
|
|
|
return next, curr, prev
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var indexDBTableNameRegexp = regexp.MustCompile("^[0-9A-F]{16}$")
|
|
|
|
|
|
|
|
func nextIndexDBTableName() string {
|
2024-02-23 22:47:39 +00:00
|
|
|
n := indexDBTableIdx.Add(1)
|
2019-05-22 21:16:55 +00:00
|
|
|
return fmt.Sprintf("%016X", n)
|
|
|
|
}
|
|
|
|
|
2024-02-23 22:47:39 +00:00
|
|
|
var indexDBTableIdx = func() *atomic.Uint64 {
|
|
|
|
var x atomic.Uint64
|
|
|
|
x.Store(uint64(time.Now().UnixNano()))
|
|
|
|
return &x
|
|
|
|
}()
|
2024-09-16 08:05:08 +00:00
|
|
|
|
|
|
|
// shouldDeleteMissingMetricID checks if metricID index entry is missing
|
|
|
|
//
|
|
|
|
// Broken index entry should be deleted by caller
|
|
|
|
// There are the following expected cases when this may happen:
|
|
|
|
//
|
|
|
|
// 1. The corresponding metricID -> metricName/tsid entry isn't visible for search yet.
|
|
|
|
// The solution is to wait for some time and try the search again.
|
|
|
|
// It is OK if newly registered time series isn't visible for search during some time.
|
|
|
|
// This should resolve https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5959
|
|
|
|
//
|
|
|
|
// 2. The metricID -> metricName/tsid entry doesn't exist in the indexdb.
|
|
|
|
// This is possible after unclean shutdown or after restoring of indexdb from a snapshot.
|
|
|
|
// In this case the metricID must be deleted, so new metricID is registered
|
|
|
|
// again when new sample for the given metric is ingested next time.
|
|
|
|
func (s *Storage) shouldDeleteMissingMetricID(metricID uint64) bool {
|
|
|
|
ct := fasttime.UnixTimestamp()
|
|
|
|
s.missingMetricIDsLock.Lock()
|
|
|
|
defer s.missingMetricIDsLock.Unlock()
|
|
|
|
|
|
|
|
if ct > s.missingMetricIDsResetDeadline {
|
|
|
|
s.missingMetricIDs = nil
|
|
|
|
s.missingMetricIDsResetDeadline = ct + 2*60
|
|
|
|
}
|
|
|
|
deleteDeadline, ok := s.missingMetricIDs[metricID]
|
|
|
|
if !ok {
|
|
|
|
if s.missingMetricIDs == nil {
|
|
|
|
s.missingMetricIDs = make(map[uint64]uint64)
|
|
|
|
}
|
|
|
|
deleteDeadline = ct + 60
|
|
|
|
s.missingMetricIDs[metricID] = deleteDeadline
|
|
|
|
}
|
|
|
|
// Cannot find index entry for the given metricID for the last 60 seconds.
|
|
|
|
// It is likely the indexDB contains incomplete set of metricID -> metricName/tsid entries
|
|
|
|
// after unclean shutdown or after restoring from a snapshot.
|
|
|
|
// Mark the metricID as deleted, so it is created again when new sample
|
|
|
|
// for the given time series is ingested next time.
|
|
|
|
|
|
|
|
return ct > deleteDeadline
|
|
|
|
}
|