{"id":"6de1f45b0a0e9feb","repo":"gofiber/fiber","slug":"cache-failed-to-delete-expired-key-q-w","errorCode":null,"errorMessage":"cache: failed to delete expired key %q: %w","messagePattern":"cache: failed to delete expired key %q: %w","errorType":"http","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"middleware/cache/cache.go","lineNumber":323,"sourceCode":"\t\t\thandleMinFresh(ts)\n\t\t}\n\n\t\tif e != nil && e.ttl == 0 && e.forceRevalidate {\n\t\t\trevalidate = true\n\t\t\toldHeapIdx = e.heapidx\n\t\t\tif cfg.Storage != nil {\n\t\t\t\tmanager.release(e)\n\t\t\t}\n\t\t\te = nil\n\t\t}\n\n\t\tif e != nil && e.ttl == 0 && e.exp != 0 && ts >= e.exp {\n\t\t\tunlock()\n\t\t\tif err := deleteKey(reqCtx, key); err != nil {\n\t\t\t\tif cfg.Storage != nil {\n\t\t\t\t\tmanager.release(e)\n\t\t\t\t}\n\t\t\t\treturn fmt.Errorf(\"cache: failed to delete expired key %q: %w\", maskKey(key), err)\n\t\t\t}\n\t\t\trelock()\n\t\t\tremoveHeapEntry(key, e.heapidx)\n\t\t\tif cfg.Storage != nil {\n\t\t\t\tmanager.release(e)\n\t\t\t}\n\t\t\te = nil\n\t\t\tunlock()\n\t\t\tc.Set(cfg.CacheHeader, cacheUnreachable)\n\t\t\tgoto continueRequest\n\t\t}\n\n\t\tif e != nil {\n\t\t\tentryHasPrivate := e != nil && e.private\n\t\t\tif !entryHasPrivate && cfg.StoreResponseHeaders && len(e.headers) > 0 {\n\t\t\t\tif cc, ok := lookupCachedHeader(e.headers, fiber.HeaderCacheControl); ok && hasDirective(utils.UnsafeString(cc), privateDirective) {\n\t\t\t\t\tentryHasPrivate = true\n\t\t\t\t}","sourceCodeStart":305,"sourceCodeEnd":341,"githubUrl":"https://github.com/gofiber/fiber/blob/9a4c7e57fe0b080a04235d28a4b0d2b4b353d58c/middleware/cache/cache.go#L305-L341","documentation":"Returned from the request-time lookup path when a cached entry has ttl==0 and exp!=0 and is now past expiration, deleteKey(reqCtx, key) was invoked to evict it on read, and the underlying manager.del (plus optional '_body' delete for external storage) failed. This is an inline lazy-expiration failure: the entry is stale but couldn't be purged from storage.","triggerScenarios":"A GET request hits a key whose cached entry expired; cfg.Storage (remote) returned an error on Delete for the key or its '_body' companion. Reproducible by making the storage driver return errors on Delete while a previously-set key ages past its expiration.","commonSituations":"Redis Delete fails due to a transient connection reset right when a stale key is read; the '_body' raw key was already evicted by a parallel request and the backend returns an unexpected error; storage pool saturated at peak traffic.","solutions":["Inspect the storage backend error wrapped by %w — it tells you whether it's a connection, permission, or serialization issue.","Shore up storage reliability (pool size, timeouts, network) so Delete is robust under load.","Tune Expiration and MaxBytes so a fresher working set reduces lazy-expiry Deletes on the hot path.","As a stopgap, run a periodic sweep/flush of the storage so stale entries don't accumulate."],"exampleFix":"// before: shared redis with default 1 conn -> Delete timeouts under load\ncfg := cache.Config{Storage: redis.New(redis.Config{PoolSize: 1})}\n\n// after: sized pool + sane timeouts\ncfg := cache.Config{\n  Storage: redis.New(redis.Config{\n    PoolSize: 32,\n    // driver-appropriate read/write timeouts\n  }),\n}","handlingStrategy":"retry","validationCode":"func pingStorage(s fiber.Storage) error {\n    ctx, cancel := context.WithTimeout(context.Background(), time.Second)\n    defer cancel()\n    return s.Delete(ctx, \"__nonexistent__\") // expect nil even if absent\n}","typeGuard":"null","tryCatchPattern":"app.Use(func(c fiber.Ctx) error {\n    err := c.Next()\n    if err != nil && strings.Contains(err.Error(), \"failed to delete expired key\") {\n        // transient storage Delete failure; the stale entry will be retried later\n        log.Warnf(\"lazy expiry delete failed: %v\", err)\n        return nil\n    }\n    return err\n})","preventionTips":["Tune the storage pool/timeout so Delete is reliable under load.","Run a periodic sweep that re-attempts deletes of expired entries.","Adjust Expiration/MaxBytes so the working set fits without aggressive expiry.","Monitor storage Delete error rates as an SLO."],"tags":["cache","storage","expiration","delete","redis"],"analyzedSha":"9a4c7e57fe0b080a04235d28a4b0d2b4b353d58c","analyzedAt":"2026-08-04T21:44:03.395Z","schemaVersion":2}