lib/storage: include last sample when query at the last millisecond of the day

One millisecond shouldn't be subtracted from the `tr.MaxTimestamp`, and
related test cases will be added

Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9804
This commit is contained in:
JAYICE
2025-12-12 18:01:06 +08:00
committed by GitHub
parent ff0aaa38b7
commit 34a542c324
5 changed files with 59 additions and 6 deletions

View File

@@ -53,6 +53,7 @@ func testSpecialQueryRegression(tc *apptest.TestCase, sut apptest.PrometheusWrit
testMatchSeries(tc, sut)
testNegativeIncrease(tc, sut)
testInstantQueryWithOffsetUsingCache(tc, sut)
testQueryRangeEndAtFirstMillisecondOfDate(tc, sut)
// graphite
testComparisonNotInfNotNan(tc, sut)
@@ -334,6 +335,48 @@ func testInstantQueryWithOffsetUsingCache(tc *apptest.TestCase, sut apptest.Prom
})
}
func testQueryRangeEndAtFirstMillisecondOfDate(tc *apptest.TestCase, sut apptest.PrometheusWriteQuerier) {
t := tc.T()
// unexpected /api/v1/query_range response
// when the sample is at the last millisecond of a day, e.g. `2025-12-12 00:00:00`
// query_range with `End` at the last millisecond of that day may cause the time point to be missed.
// see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9804
// `End` should be inclusive according to https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries
sut.PrometheusAPIV1ImportPrometheus(t, []string{
`foo_bar 7 1765497600000`, // 2025-12-12 00:00:00
}, apptest.QueryOpts{})
sut.ForceFlush(t)
tc.Assert(&apptest.AssertOptions{
Msg: "unexpected /api/v1/query response",
DoNotRetry: true,
Got: func() any {
return sut.PrometheusAPIV1QueryRange(t, `foo_bar`, apptest.QueryOpts{
Start: "2025-12-11T20:00:00.000Z",
End: "2025-12-12T00:00:00.000Z",
Step: "1h",
})
},
Want: &apptest.PrometheusAPIV1QueryResponse{
Status: "success",
Data: &apptest.QueryData{
ResultType: "matrix",
Result: []*apptest.QueryResult{
{
Metric: map[string]string{"__name__": "foo_bar"},
Samples: []*apptest.Sample{
{Timestamp: 1765497600000, Value: 7},
},
},
},
},
},
})
}
func testComparisonNotInfNotNan(tc *apptest.TestCase, sut apptest.PrometheusWriteQuerier) {
t := tc.T()

View File

@@ -50,6 +50,7 @@ See also [LTS releases](https://docs.victoriametrics.com/victoriametrics/lts-rel
* BUGFIX: [vmsingle](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/) and `vmstorage` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/): `dateMetricIDCache` metrics were renamed to follow the naming pattern used for other caches. `vm_date_metric_id_cache_resets_total` becomes `vm_cache_resets_total{type="indexdb/date_metricID"}` and `vm_date_metric_id_cache_syncs_total` became `vm_cache_syncs_total{type="indexdb/date_metricID"}`. See PR [#10152](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/10152) for details.
* BUGFIX: [vmsingle](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/) and `vmstorage` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/): fix performance degradation caused by redundant indexdb lookups at the start of each hour. See this issue [#10114](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/10114) for details.
* BUGFIX: [vmauth](https://docs.victoriametrics.com/victoriametrics/vmauth/): verify backend network reachability with a TCP dial before marking it healthy. Previously, backends were auto-restored after `-failTimeout` even if the network was still unreachable, causing requests to hang repeatedly. `vmauth` now performs a 1s TCP dial check before returning a backend to the healthy pool. See [#9997](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9997).
* BUGFIX: [vmsingle](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/) and `vmstorage` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/): fix potential datapoint lost in response when query at the last millisecond of the day. See issue [#9804](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9804) for details.
## [v1.131.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.131.0)

View File

@@ -3183,7 +3183,7 @@ func TestStorageAdjustTimeRange(t *testing.T) {
// Time range is smaller than 40 days. When the -disablePerDayIndex flag is
// unset, the time range will not be adjusted. When the flag is set, the
// adjusted time range will be globalIndexTimeRange.
tr = TimeRange{10 * msecPerDay, 50 * msecPerDay}
tr = TimeRange{10 * msecPerDay, 50*msecPerDay - 1}
f(false, tr, tr)
f(true, tr, globalIndexTimeRange)
@@ -3202,7 +3202,7 @@ func TestStorageAdjustTimeRange(t *testing.T) {
// When the -disablePerDayIndex flag is unset, the time range will not be
// adjusted. When the flag is set, the adjusted time range will be
// globalIndexTimeRange.
tr = TimeRange{10 * msecPerDay, 51 * msecPerDay}
tr = TimeRange{10 * msecPerDay, 50 * msecPerDay}
f(false, tr, tr)
f(true, tr, globalIndexTimeRange)

View File

@@ -43,10 +43,9 @@ var (
// DateRange returns the date range for the given time range.
func (tr *TimeRange) DateRange() (uint64, uint64) {
minDate := uint64(tr.MinTimestamp) / msecPerDay
// Max timestamp may point to the first millisecond of the next day. As the
// result, the returned date range will cover one more day than needed.
// Decrementing by 1 removes this extra day.
maxDate := uint64(tr.MaxTimestamp-1) / msecPerDay
// Sample at Max timestamp should be included because `End` is inclusive.
// According to https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries
maxDate := uint64(tr.MaxTimestamp) / msecPerDay
// However, if both timestamps are the same and point to the beginning of
// the day, then maxDate will be smaller that the minDate. In this case

View File

@@ -138,6 +138,16 @@ func TestTimeRangeDateRange(t *testing.T) {
// the same as min date.
tr = TimeRange{2*msecPerDay + 654, 1*msecPerDay + 321}
f(tr, 2, 2)
// MaxTimestamp is the last millisecond of the day.
// Max date should be the next date
tr = TimeRange{1*msecPerDay + 123, 2 * msecPerDay}
f(tr, 1, 2)
// MaxTimestamp is the first millisecond of the day.
// Max date should be the next date
tr = TimeRange{1*msecPerDay + 123, 2*msecPerDay + 1}
f(tr, 1, 2)
}
func TestDateToString(t *testing.T) {