Skip to content

Commit b61cef3

Browse files
sandy2008claude
andcommitted
fix(queryeviction): deregister evicted queries to prevent double accounting
Evicted victims were never removed from the QueryRegistry by the evictor; the only cleanup is the deferred Deregister in trackedQuery.Exec, which runs when the query goroutine eventually unwinds. Until then the already-cancelled victim remained in the evictable candidate set, so a later eviction cycle could re-pick it: cortex_query_evictions_total was double-counted for a single query, a MaxEvictionsPerCycle slot was burned on a no-op Cancel instead of relieving pressure, and in tests the second Cancel callback panicked on a double channel close. Deregister the victim before cancelling it, keeping all bookkeeping (metric, registry) ahead of the externally observable cancellation signal. Deregister is an idempotent locked delete, so the deferred Deregister in trackedQuery.Exec remains a safe no-op when the query finishes unwinding. Note the registry semantic shift: registry.Len() (and the registered_queries debug log field) now means "evictable candidates" - it no longer includes cancelled-but-still-unwinding victims. Add TestEvictedVictim_RemovedFromRegistry, which fails deterministically without this fix (expected: 0, actual: 1): the victim stayed registered after its eviction was observed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Sandy Chen <Yuxuan.Chen@morganstanley.com>
1 parent 07ec4d5 commit b61cef3

2 files changed

Lines changed: 34 additions & 0 deletions

File tree

pkg/util/queryeviction/evictor.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,11 @@ func (e *QueryEvictor) running(ctx context.Context) error {
103103
// externally observable commit point, so observers synchronized on
104104
// the cancellation must already see it in evictionsTotal.
105105
e.evictionsTotal.WithLabelValues(string(breachedResource)).Inc()
106+
// Retire the victim before cancelling it so later cycles can never
107+
// re-pick (and double-count) an already-cancelled query that is
108+
// still unwinding. trackedQuery.Exec's own deferred Deregister
109+
// remains a safe no-op.
110+
e.registry.Deregister(victim.QueryID)
106111
victim.Cancel()
107112

108113
level.Warn(e.logger).Log(

pkg/util/queryeviction/evictor_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,35 @@ func TestEvictionsTotal_IncrementedBeforeCancelObserved(t *testing.T) {
242242
assert.Equal(t, float64(1), counterAtCancel)
243243
}
244244

245+
func TestEvictedVictim_RemovedFromRegistry(t *testing.T) {
246+
reg := NewQueryRegistry(testMetricFunc)
247+
248+
ctx, cancel := context.WithCancel(context.Background())
249+
stats := &querier_stats.QueryStats{}
250+
stats.AddFetchedSamples(1000)
251+
252+
// The sync.Once keeps the callback idempotent: if the evictor wrongly
253+
// re-picked the still-registered victim on a later cycle, the second
254+
// Cancel would otherwise panic on the double close.
255+
var once sync.Once
256+
evicted := make(chan struct{})
257+
reg.Register(func() {
258+
once.Do(func() {
259+
cancel()
260+
close(evicted)
261+
})
262+
}, stats, "up", "user1", "")
263+
_ = ctx
264+
265+
startEvictor(t, newMockMonitor(0.95, 0.0), reg, testEvictorConfig(0.9, 0, 0))
266+
waitEvicted(t, evicted)
267+
268+
// The evictor deregisters the victim before cancelling it, so once the
269+
// eviction is observable the victim must already have left the registry
270+
// and can never be re-picked (and double-counted) while still unwinding.
271+
assert.Equal(t, 0, reg.Len())
272+
}
273+
245274
func TestNewQueryEvictor_ReturnsNilWhenDisabled(t *testing.T) {
246275
cfg := configs.EvictionConfig{CheckInterval: time.Second, EvictionMetric: "fetched_samples", MaxEvictionsPerCycle: 1}
247276
evictor := NewQueryEvictor(newMockMonitor(0, 0), NewQueryRegistry(testMetricFunc), cfg, log.NewNopLogger(), prometheus.NewPedanticRegistry(), "test")

0 commit comments

Comments
 (0)