mirror of
https://github.com/SigNoz/signoz.git
synced 2026-10-31 21:40:42 +00:00
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:
@@ -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).
|
||||
|
||||
@@ -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{}
|
||||
|
||||
Reference in New Issue
Block a user