fix(querier): size cache buckets by step instead of window

Edges now depend only on the step, which is part of the cache key, so a
narrower window no longer re-cuts wider cached buckets.

Assisted-by: Claude Opus 5.5
This commit is contained in:
Tushar Vats
2026-10-30 18:15:02 +05:30
parent 1bd3213522
commit e9850f9edf
2 changed files with 15 additions and 16 deletions

View File

@@ -17,6 +17,8 @@ import (
"github.com/SigNoz/signoz/pkg/valuer"
)
const stepsPerBucket = 300
// BucketCache is the interface for bucket-based caching.
type BucketCache interface {
// cached portion + list of gaps to fetch
@@ -176,7 +178,7 @@ func (bc *bucketCache) Put(ctx context.Context, orgID valuer.UUID, q qbtypes.Que
}
// Convert trimmed result to buckets with adjusted boundaries
bucketWidthMs := bucketWidth(startMs, endMs, stepMs)
bucketWidthMs := bucketWidth(stepMs)
freshBuckets := bc.resultToBuckets(ctx, trimmedResult, cachableStartMs, cachableEndMs, bucketWidthMs)
// If no fresh buckets and no existing data, don't cache
@@ -701,13 +703,10 @@ func (bc *bucketCache) splitIntoBuckets(ctx context.Context, tsData *qbtypes.Tim
return buckets
}
// bucketWidth is the query window rounded up to whole steps, so bucket edges at its multiples stay step-aligned.
func bucketWidth(startMs, endMs, stepMs uint64) uint64 {
widthMs := endMs - startMs
if stepMs > 0 && widthMs%stepMs != 0 {
widthMs += stepMs - widthMs%stepMs
}
return widthMs
// bucketWidth depends only on the step, which is part of the cache key, so every window of a query
// cuts at the same edges. Zero means no cutting.
func bucketWidth(stepMs uint64) uint64 {
return stepMs * stepsPerBucket
}
// statsShare is the duration-weighted part of stats for [fromMs, toMs) within [startMs, endMs).

View File

@@ -1467,7 +1467,7 @@ func TestBucketCache_NoCache(t *testing.T) {
}
func TestBucketCache_Put_OverlappingBuckets(t *testing.T) {
// Buckets are cut at multiples of the query window, so a 4s window fills 4s-aligned buckets.
// Buckets are cut at multiples of 300 steps, so a 10ms step fills 3s-aligned buckets.
testCases := []struct {
name string
windows []qbtypes.TimeRange
@@ -1476,27 +1476,27 @@ func TestBucketCache_Put_OverlappingBuckets(t *testing.T) {
{
name: "SlidingWindows_FillAlignedBuckets",
windows: []qbtypes.TimeRange{{From: 1000, To: 5000}, {From: 2000, To: 6000}, {From: 3000, To: 7000}},
expectedBuckets: []qbtypes.TimeRange{{From: 1000, To: 4000}, {From: 4000, To: 7000}},
expectedBuckets: []qbtypes.TimeRange{{From: 1000, To: 3000}, {From: 3000, To: 6000}, {From: 6000, To: 7000}},
},
{
name: "DisjointWindows_KeepAll",
windows: []qbtypes.TimeRange{{From: 1000, To: 3000}, {From: 5000, To: 7000}},
expectedBuckets: []qbtypes.TimeRange{{From: 1000, To: 2000}, {From: 2000, To: 3000}, {From: 5000, To: 6000}, {From: 6000, To: 7000}},
expectedBuckets: []qbtypes.TimeRange{{From: 1000, To: 3000}, {From: 5000, To: 6000}, {From: 6000, To: 7000}},
},
{
name: "PartialOverlap_MergeOnlyOverlapping",
windows: []qbtypes.TimeRange{{From: 1000, To: 3000}, {From: 5000, To: 7000}, {From: 2000, To: 4000}},
expectedBuckets: []qbtypes.TimeRange{{From: 1000, To: 2000}, {From: 2000, To: 4000}, {From: 5000, To: 6000}, {From: 6000, To: 7000}},
expectedBuckets: []qbtypes.TimeRange{{From: 1000, To: 3000}, {From: 3000, To: 4000}, {From: 5000, To: 6000}, {From: 6000, To: 7000}},
},
{
name: "NarrowerWindow_RecutsWideBucket",
name: "NarrowerWindow_KeepsBucketEdges",
windows: []qbtypes.TimeRange{{From: 1000, To: 7000}, {From: 3000, To: 5000}},
expectedBuckets: []qbtypes.TimeRange{{From: 1000, To: 2000}, {From: 2000, To: 4000}, {From: 4000, To: 6000}, {From: 6000, To: 7000}},
expectedBuckets: []qbtypes.TimeRange{{From: 1000, To: 3000}, {From: 3000, To: 6000}, {From: 6000, To: 7000}},
},
{
name: "WiderWindow_AbsorbsCoveredBuckets",
windows: []qbtypes.TimeRange{{From: 2000, To: 3000}, {From: 4000, To: 5000}, {From: 1000, To: 7000}},
expectedBuckets: []qbtypes.TimeRange{{From: 1000, To: 6000}, {From: 6000, To: 7000}},
expectedBuckets: []qbtypes.TimeRange{{From: 1000, To: 3000}, {From: 3000, To: 6000}, {From: 6000, To: 7000}},
},
}
@@ -1504,7 +1504,7 @@ func TestBucketCache_Put_OverlappingBuckets(t *testing.T) {
t.Run(testCase.name, func(t *testing.T) {
memCache := createTestCache(t)
bc := NewBucketCache(instrumentationtest.New().ToProviderSettings(), memCache, cacheTTL, defaultFluxInterval)
step := qbtypes.Step{Duration: time.Second}
step := qbtypes.Step{Duration: 10 * time.Millisecond}
// Each window stamps its values with its 1-based run number.
expectedValues := map[int64]float64{}