gofiber/fiber · warning
cache: failed to delete expired key
Error message
cache: failed to delete expired key %q: %w
What it means
Returned from the request hot path (around cache.go:365) when an expired entry is detected and deleteKey fails to remove it from storage. This is the inline expired-entry cleanup: the entry's ttl==0, exp!=0, and ts>=exp mark it as expired; the cache attempts to delete it so future requests miss and re-fetch, but the storage delete errored. The masked key is logged to avoid leaking sensitive path content.
Solutions
- Inspect the wrapped delete error to find the backend cause.
- Add health checks and reconnect logic to the storage client.
- Ensure reqCtx is derived from the request lifecycle, not a longer-lived context.
- For Redis, confirm maxmemory-policy and connection pool health.
- If using in-memory only (Storage == nil), this path is unreachable because deleteKey short-circuits.
Example fix
// before: shared storage with no delete timeout, occasional blips
cfg := cache.Config{Storage: redisClient}
// after: client with sane dial/read/write timeouts
redisClient := redis.NewClient(&redis.Options{DialTimeout: 2*time.Second, ReadTimeout: 500*time.Millisecond}) Defensive patterns
Strategy: try-catch
Validate before calling
// Confirm storage Delete works before relying on inline expiration.
func probeDelete(s fiber.Storage) error {
if err := s.Set("probe:del", []byte("v"), time.Second); err != nil { return err }
return s.Delete("probe:del")
} Try / catch
app.Use(func(c fiber.Ctx) error {
err := c.Next()
if err != nil && isCacheDeleteErr(err) {
// treat a delete failure on expired key as non-fatal; expire on next pass
return nil
}
return err
}) Prevention
- Use a storage client with bounded timeouts and reconnect logic.
- Ensure reqCtx derives from the request lifecycle, not a longer-lived context.
- Monitor delete error rate as a leading indicator of storage trouble.
- If Storage is nil (in-memory), this path is unreachable.
When it happens
Trigger: cfg.Storage is set and the backend's Delete returns an error during inline expiration; storage backend temporarily unreachable; the request context (reqCtx) is canceled before the delete completes; multi-instance deployment where another node raced the same key.
Common situations: Redis restart during a traffic spike hitting expired keys; storage network blip; client disconnect cancels reqCtx; storage Delete latency spike under load.
Related errors
- cache: failed to delete cached response for key
- cache: failed to delete key
- cache: failed to delete private response for key
- cache: failed to delete stale vary manifest
- cache: failed to delete key
AI-assisted analysis of gofiber/fiber@a105acad6c (2026-08-11).
Data as JSON: /api/errors/6de1f45b0a0e9feb.
Report an issue: GitHub.
Appendix: source
Thrown at middleware/cache/cache.go:365
handleMinFresh(ts)
}
if e != nil && e.ttl == 0 && e.forceRevalidate {
revalidate = true
oldHeapIdx = e.heapidx
if cfg.Storage != nil {
manager.release(e)
}
e = nil
}
if e != nil && e.ttl == 0 && e.exp != 0 && ts >= e.exp {
unlock()
if err := deleteKey(reqCtx, key); err != nil {
if cfg.Storage != nil {
manager.release(e)
}
return fmt.Errorf("cache: failed to delete expired key %q: %w", maskKey(key), err)
}
relock()
removeHeapEntry(key, e.heapidx)
if cfg.Storage != nil {
manager.release(e)
}
e = nil
unlock()
c.Set(cfg.CacheHeader, cacheUnreachable)
goto continueRequest
}
if e != nil {
entryHasPrivate := e != nil && e.private
if !entryHasPrivate && cfg.StoreResponseHeaders && len(e.headers) > 0 {
if cc, ok := lookupCachedHeader(e.headers, fiber.HeaderCacheControl); ok && hasDirective(utils.UnsafeString(cc), privateDirective) {
entryHasPrivate = true
}View on GitHub (pinned to a105acad6c)