mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2026-07-29 20:12:30 +03:00
Compare commits
1 Commits
zero-date
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3888cbdecc |
2
.github/workflows/codeql-analysis-go.yml
vendored
2
.github/workflows/codeql-analysis-go.yml
vendored
@@ -54,7 +54,7 @@ jobs:
|
||||
restore-keys: go-artifacts-${{ runner.os }}-codeql-analyze-${{ steps.go.outputs.go-version }}-
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
|
||||
uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
|
||||
with:
|
||||
languages: go
|
||||
|
||||
|
||||
@@ -39,8 +39,6 @@ VictoriaMetrics has the following prominent features:
|
||||
* Easy and fast backups from [instant snapshots](https://medium.com/@valyala/how-victoriametrics-makes-instant-snapshots-for-multi-terabyte-time-series-data-e1f3fb0e0282)
|
||||
can be done with [vmbackup](https://docs.victoriametrics.com/victoriametrics/vmbackup/) / [vmrestore](https://docs.victoriametrics.com/victoriametrics/vmrestore/) tools.
|
||||
See [this article](https://medium.com/@valyala/speeding-up-backups-for-big-time-series-databases-533c1a927883) for more details.
|
||||
* It supports storage and retrieval of samples with timestamps that fall within the `[1970-01-02T00:00:00.000Z, 2262-03-31T23:59:59.999Z]` time range with millisecond precision.
|
||||
See [Retention](#retention) for details.
|
||||
* It implements a PromQL-like query language - [MetricsQL](https://docs.victoriametrics.com/victoriametrics/metricsql/), which provides improved functionality on top of PromQL.
|
||||
* It provides a global query view. Multiple Prometheus instances or any other data sources may ingest data into VictoriaMetrics. Later this data may be queried via a single query.
|
||||
* It provides high performance and good vertical and horizontal scalability for both
|
||||
@@ -1542,9 +1540,6 @@ It is safe to extend `-retentionPeriod` on existing data. If `-retentionPeriod`
|
||||
value than before, then data outside the configured period will be eventually deleted.
|
||||
|
||||
VictoriaMetrics does not support indefinite retention, but you can specify an arbitrarily high duration, e.g. `-retentionPeriod=100y`.
|
||||
Just keep in mind that VictoriaMetrics does not support samples with negative timestamps. Timestamps from `1970-01-01` are also not
|
||||
supported because this date has a special meaning internally. It therefore will reject samples with timestamps before
|
||||
`1970-01-02T00:00:00.000Z` if the retention period includes dates before this timestamp.
|
||||
|
||||
By default, VictoriaMetrics doesn't accept samples with timestamps bigger than `now+2d`, e.g. 2 days in the future.
|
||||
If you need accepting samples with bigger timestamps, then specify the desired "future retention" via `-futureRetention` command-line flag.
|
||||
@@ -1556,9 +1551,6 @@ For example, the following command starts VictoriaMetrics, which accepts samples
|
||||
/path/to/victoria-metrics -futureRetention=1y
|
||||
```
|
||||
|
||||
VictoriaMetrics does not support stamples after `2262-03-31T23:59:59.999Z`. And if the future retention includes dates after this timestamp,
|
||||
the samples for those dates will be rejected.
|
||||
|
||||
By default, VictoriaMetrics accepts samples with timestamps as old as the configured `-retentionPeriod` allows, e.g. it accepts backfilled
|
||||
historical data as long as it fits into the retention. If you need rejecting samples with historical timestamps older than the specified
|
||||
duration, then specify the desired duration via the `-maxBackfillAge` command-line flag. This can be useful for limiting ingestion of
|
||||
|
||||
@@ -1118,14 +1118,6 @@ func searchAndMerge[T any](qt *querytracer.Tracer, s *Storage, tr TimeRange, sea
|
||||
qt = qt.NewChild("search indexDBs: timeRange=%v", &tr)
|
||||
defer qt.Done()
|
||||
|
||||
var zeroValue T
|
||||
if tr.MinTimestamp < minUnixMilli {
|
||||
tr.MinTimestamp = minUnixMilli
|
||||
}
|
||||
if tr.MaxTimestamp < tr.MinTimestamp {
|
||||
return zeroValue, nil
|
||||
}
|
||||
|
||||
var idbts []indexDBWithType
|
||||
|
||||
ptws := s.tb.GetPartitions(tr)
|
||||
|
||||
@@ -402,10 +402,6 @@ func TestStorageDeletePendingSeries(t *testing.T) {
|
||||
defer testRemoveAll(t)
|
||||
|
||||
const numMonths = 10
|
||||
start := time.Date(1971, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
middle := start.AddDate(0, (numMonths-1)/2, 0)
|
||||
end := start.AddDate(0, numMonths-1, 0)
|
||||
|
||||
s := MustOpenStorage(t.Name(), OpenOptions{})
|
||||
|
||||
var metricGroupName = []byte("metric")
|
||||
@@ -460,7 +456,7 @@ func TestStorageDeletePendingSeries(t *testing.T) {
|
||||
assertCountMonthsWithLabels := func(count int) {
|
||||
t.Helper()
|
||||
|
||||
ts := start
|
||||
ts := time.Unix(0, 0)
|
||||
n := 0
|
||||
for range numMonths {
|
||||
lns, err := s.SearchLabelNames(nil, nil, TimeRange{ts.UnixMilli(), ts.UnixMilli()}, 1e5, 1e9, noDeadline)
|
||||
@@ -485,7 +481,7 @@ func TestStorageDeletePendingSeries(t *testing.T) {
|
||||
var search Search
|
||||
defer search.MustClose()
|
||||
|
||||
search.Init(nil, s, []*TagFilters{tfs}, TimeRange{start.UnixMilli(), math.MaxInt64}, 1e5, noDeadline)
|
||||
search.Init(nil, s, []*TagFilters{tfs}, TimeRange{0, math.MaxInt64}, 1e5, noDeadline)
|
||||
n := 0
|
||||
for search.NextMetricBlock() {
|
||||
var b Block
|
||||
@@ -502,6 +498,10 @@ func TestStorageDeletePendingSeries(t *testing.T) {
|
||||
// Verify no metrics exist
|
||||
assertCountRows(0)
|
||||
|
||||
start := time.Unix(0, 0)
|
||||
middle := start.AddDate(0, (numMonths-1)/2, 0)
|
||||
end := start.AddDate(0, numMonths-1, 0)
|
||||
|
||||
// Add some rows and flush, so next DeleteSeries() can delete them
|
||||
addRows(start, middle, false)
|
||||
s.DebugFlush()
|
||||
@@ -3385,190 +3385,53 @@ func TestStorageQueryWithoutIndex(t *testing.T) {
|
||||
testStorageSearchWithoutIndex(t, &opts)
|
||||
}
|
||||
|
||||
func TestStorageAddRowsWithZeroDate(t *testing.T) {
|
||||
func TestStorageAddRows_SamplesWithZeroDate(t *testing.T) {
|
||||
defer testRemoveAll(t)
|
||||
|
||||
f := func(t *testing.T, disablePerDayIndex bool) {
|
||||
t.Helper()
|
||||
|
||||
s := MustOpenStorage(t.Name(), OpenOptions{
|
||||
DisablePerDayIndex: disablePerDayIndex,
|
||||
})
|
||||
defer s.MustClose()
|
||||
|
||||
mn := MetricName{MetricGroup: []byte("metric")}
|
||||
mr := MetricRow{MetricNameRaw: mn.marshalRaw(nil)}
|
||||
for range 10 {
|
||||
mr.Timestamp = rand.Int63n(msecPerDay)
|
||||
mr.Value = float64(rand.Intn(1000))
|
||||
s.AddRows([]MetricRow{mr}, defaultPrecisionBits)
|
||||
s.DebugFlush()
|
||||
// Reset TSID cache so that insertion takes the path that involves
|
||||
// checking whether the index contains metricName->TSID mapping.
|
||||
s.resetAndSaveTSIDCache()
|
||||
}
|
||||
|
||||
want := 1
|
||||
firstUnixDay := TimeRange{
|
||||
MinTimestamp: 0,
|
||||
MaxTimestamp: msecPerDay - 1,
|
||||
}
|
||||
if got := s.newTimeseriesCreated.Load(); got != uint64(want) {
|
||||
t.Errorf("unexpected new timeseries count: got %d, want %d", got, want)
|
||||
}
|
||||
if got := testCountAllMetricNames(s, firstUnixDay); got != want {
|
||||
t.Errorf("unexpected metric name count: got %d, want %d", got, want)
|
||||
}
|
||||
if got := testCountAllMetricIDs(s, firstUnixDay); got != want {
|
||||
t.Errorf("unexpected metric id count: got %d, want %d", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
for _, disablePerDayIndex := range []bool{false, true} {
|
||||
name := fmt.Sprintf("disablePerDayIndex=%t", disablePerDayIndex)
|
||||
t.Run(name, func(t *testing.T) {
|
||||
testStorageAddRowsWithZeroDate(t, disablePerDayIndex)
|
||||
f(t, disablePerDayIndex)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testStorageAddRowsWithZeroDate(t *testing.T, disablePerDayIndex bool) {
|
||||
s := MustOpenStorage(t.Name(), OpenOptions{
|
||||
DisablePerDayIndex: disablePerDayIndex,
|
||||
})
|
||||
defer s.MustClose()
|
||||
|
||||
const numDays = 4
|
||||
var metricNamesAll []string
|
||||
labelNamesAll := []string{"__name__", "label"}
|
||||
var labelValuesAll []string
|
||||
mrs := make([]MetricRow, numDays)
|
||||
for day := range numDays {
|
||||
metricName := fmt.Sprintf("metric_%02d", day)
|
||||
labelName := fmt.Sprintf("label_%02d", day)
|
||||
labelValue := fmt.Sprintf("value_%02d", day)
|
||||
|
||||
if day != 0 {
|
||||
metricNamesAll = append(metricNamesAll, metricName)
|
||||
labelNamesAll = append(labelNamesAll, labelName)
|
||||
labelValuesAll = append(labelValuesAll, labelValue)
|
||||
}
|
||||
|
||||
mn := MetricName{
|
||||
MetricGroup: []byte(metricName),
|
||||
Tags: []Tag{
|
||||
{Key: []byte(labelName), Value: []byte("value")},
|
||||
{Key: []byte("label"), Value: []byte(labelValue)},
|
||||
},
|
||||
}
|
||||
mn.sortTags()
|
||||
|
||||
mrs[day].MetricNameRaw = mn.marshalRaw(nil)
|
||||
mrs[day].Timestamp = int64(day * msecPerDay)
|
||||
}
|
||||
|
||||
s.AddRows(mrs, defaultPrecisionBits)
|
||||
s.DebugFlush()
|
||||
if got, want := s.newTimeseriesCreated.Load(), uint64(numDays-1); got != want {
|
||||
t.Fatalf("unexpected new timeseries count: got %d, want %d", got, want)
|
||||
}
|
||||
if got, want := s.tooSmallTimestampRows.Load(), uint64(1); got != want {
|
||||
t.Fatalf("unexpected rows with too small timestamp: got %d, want %d", got, want)
|
||||
}
|
||||
|
||||
assertMetricNames := func(tr TimeRange, want []string) {
|
||||
t.Helper()
|
||||
tfs := NewTagFilters()
|
||||
if err := tfs.Add(nil, []byte("metric_.*"), false, true); err != nil {
|
||||
t.Fatalf("unexpected error in TagFilters.Add: %v", err)
|
||||
}
|
||||
got, err := s.SearchMetricNames(nil, []*TagFilters{tfs}, tr, 1e9, noDeadline)
|
||||
if err != nil {
|
||||
t.Fatalf("SearchMetricNames(%v, %v) failed unexpectedly: %v", tfs, &tr, err)
|
||||
}
|
||||
for i, name := range got {
|
||||
var mn MetricName
|
||||
if err := mn.UnmarshalString(name); err != nil {
|
||||
t.Fatalf("Could not unmarshal metric name %q: %v", name, err)
|
||||
}
|
||||
got[i] = string(mn.MetricGroup)
|
||||
}
|
||||
slices.Sort(got)
|
||||
slices.Sort(want)
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Fatalf("unexpected metric names (-want, +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
assertLabelNames := func(tr TimeRange, want []string) {
|
||||
t.Helper()
|
||||
tfs := NewTagFilters()
|
||||
if err := tfs.Add(nil, []byte("metric_.*"), false, true); err != nil {
|
||||
t.Fatalf("unexpected error in TagFilters.Add: %v", err)
|
||||
}
|
||||
got, err := s.SearchLabelNames(nil, []*TagFilters{tfs}, tr, 1e9, 1e9, noDeadline)
|
||||
if err != nil {
|
||||
t.Fatalf("SearchLabelNames(%v, %v) failed unexpectedly: %s", tfs, &tr, err)
|
||||
}
|
||||
slices.Sort(got)
|
||||
slices.Sort(want)
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Fatalf("unexpected label names (-want, +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
assertLabelValues := func(tr TimeRange, want []string) {
|
||||
t.Helper()
|
||||
tfs := NewTagFilters()
|
||||
if err := tfs.Add([]byte("label"), []byte("value_.*"), false, true); err != nil {
|
||||
t.Fatalf("unexpected error in TagFilters.Add: %v", err)
|
||||
}
|
||||
got, err := s.SearchLabelValues(nil, "label", []*TagFilters{tfs}, tr, 1e9, 1e9, noDeadline)
|
||||
if err != nil {
|
||||
t.Fatalf("SearchLabelValues(%v, %v) failed unexpectedly: %s", tfs, tr, err)
|
||||
}
|
||||
slices.Sort(got)
|
||||
slices.Sort(want)
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Fatalf("unexpected label values (-want, +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
assertData := func(tr TimeRange, want []MetricRow) {
|
||||
t.Helper()
|
||||
tfs := NewTagFilters()
|
||||
if err := tfs.Add(nil, []byte("metric_.*"), false, true); err != nil {
|
||||
t.Fatalf("TagFilters.Add() failed unexpectedly: %v", err)
|
||||
}
|
||||
if err := testAssertSearchResult(s, tr, tfs, want); err != nil {
|
||||
t.Fatalf("Search(%v, %v) failed unexpectedly: %v", tfs, tr, err)
|
||||
}
|
||||
}
|
||||
|
||||
var tr TimeRange
|
||||
|
||||
// Empty time range.
|
||||
// Expect empty search results
|
||||
tr = TimeRange{}
|
||||
assertMetricNames(tr, nil)
|
||||
assertLabelNames(tr, []string{})
|
||||
assertLabelValues(tr, []string{})
|
||||
assertData(tr, nil)
|
||||
|
||||
// First day time range.
|
||||
// Expect empty search results
|
||||
tr = TimeRange{
|
||||
MinTimestamp: 0,
|
||||
MaxTimestamp: msecPerDay - 1,
|
||||
}
|
||||
assertMetricNames(tr, nil)
|
||||
assertLabelNames(tr, []string{})
|
||||
assertLabelValues(tr, []string{})
|
||||
assertData(tr, nil)
|
||||
|
||||
// Second day time range.
|
||||
tr = TimeRange{
|
||||
MinTimestamp: msecPerDay,
|
||||
MaxTimestamp: 2*msecPerDay - 1,
|
||||
}
|
||||
if disablePerDayIndex {
|
||||
// Expect index search results for all days if per-day index is
|
||||
// disabled.
|
||||
assertMetricNames(tr, metricNamesAll)
|
||||
assertLabelNames(tr, labelNamesAll)
|
||||
assertLabelValues(tr, labelValuesAll)
|
||||
} else {
|
||||
// Expect index search results on second day only if per-day index is
|
||||
// enabled.
|
||||
assertMetricNames(tr, []string{"metric_01"})
|
||||
assertLabelNames(tr, []string{"__name__", "label", "label_01"})
|
||||
assertLabelValues(tr, []string{"value_01"})
|
||||
}
|
||||
assertData(tr, mrs[1:2])
|
||||
|
||||
// First two days time range.
|
||||
// Expect results on second day only.
|
||||
tr = TimeRange{
|
||||
MinTimestamp: 0,
|
||||
MaxTimestamp: 2*msecPerDay - 1,
|
||||
}
|
||||
if disablePerDayIndex {
|
||||
// Expect index search results for all days if per-day index is
|
||||
// disabled.
|
||||
assertMetricNames(tr, metricNamesAll)
|
||||
assertLabelNames(tr, labelNamesAll)
|
||||
assertLabelValues(tr, labelValuesAll)
|
||||
} else {
|
||||
// Expect index search results on second day only if per-day index is
|
||||
// enabled.
|
||||
assertMetricNames(tr, []string{"metric_01"})
|
||||
assertLabelNames(tr, []string{"__name__", "label", "label_01"})
|
||||
assertLabelValues(tr, []string{"value_01"})
|
||||
}
|
||||
assertData(tr, mrs[1:2])
|
||||
}
|
||||
|
||||
// testSearchMetricIDs returns metricIDs for the given tfss and tr.
|
||||
//
|
||||
// The returned metricIDs are sorted. The function panics in in case of error.
|
||||
|
||||
@@ -429,8 +429,9 @@ func (tb *table) getMinMaxIngestionTimestamps() (int64, int64) {
|
||||
func (tb *table) getMinMaxTimestampsForAge(minAgeMsecs int64) (int64, int64) {
|
||||
now := int64(fasttime.UnixTimestamp() * 1000)
|
||||
minTimestamp := now - minAgeMsecs
|
||||
if minTimestamp < minUnixMilli {
|
||||
minTimestamp = minUnixMilli
|
||||
if minTimestamp < 0 {
|
||||
// Negative timestamps aren't supported by the storage.
|
||||
minTimestamp = 0
|
||||
}
|
||||
maxTimestamp := int64(maxUnixMilli)
|
||||
if maxUnixMilli-now > tb.s.futureRetentionMsecs {
|
||||
|
||||
@@ -40,6 +40,12 @@ type TimeRange struct {
|
||||
MaxTimestamp int64
|
||||
}
|
||||
|
||||
// Zero time range and zero date are used to force global index search.
|
||||
var (
|
||||
globalIndexTimeRange = TimeRange{}
|
||||
globalIndexDate = uint64(0)
|
||||
)
|
||||
|
||||
// DateRange returns the date range for the given time range.
|
||||
func (tr *TimeRange) DateRange() (uint64, uint64) {
|
||||
minDate := uint64(tr.MinTimestamp) / msecPerDay
|
||||
@@ -111,29 +117,10 @@ func (tr *TimeRange) contains(timestamp int64) bool {
|
||||
return tr.MinTimestamp <= timestamp && timestamp <= tr.MaxTimestamp
|
||||
}
|
||||
|
||||
// Zero time range and zero date are used to force global index search.
|
||||
var (
|
||||
globalIndexDate = uint64(0)
|
||||
globalIndexTimeRange = TimeRange{}
|
||||
)
|
||||
|
||||
const (
|
||||
msecPerDay = 24 * 3600 * 1000
|
||||
msecPerHour = 3600 * 1000
|
||||
|
||||
// minUnixMilli is the min millisecond that is allowed to be used as the
|
||||
// sample timestamp.
|
||||
//
|
||||
// It corresponds to the first millisecond of the second day of the Unix
|
||||
// Epoch, i.e. 1970-01-02T00:00:00.000Z.
|
||||
//
|
||||
// The first day of the Unix Epoch is reserved: zero date and zero time
|
||||
// range are used for indicating that the the global index search is
|
||||
// required. See globalIndexDate and globalIndexTimeRange above.
|
||||
//
|
||||
// Negative timestamps aren't supported.
|
||||
minUnixMilli = msecPerDay
|
||||
|
||||
// maxUnixMilli is the max millisecond that is allowed to be used as the
|
||||
// sample timestamp.
|
||||
//
|
||||
@@ -143,6 +130,6 @@ const (
|
||||
// time.UnixMicro(math.MaxInt64/1000) == 2262-04-11 23:47:16.854775 UTC.
|
||||
//
|
||||
// Round it to the last millisecond of the last complete partition:
|
||||
// 2262-03-31T23:59:59.999Z.
|
||||
// 2262-03-31 23:59:59.999 UTC.
|
||||
maxUnixMilli = 9222422399999
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user