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

  1. Inspect the wrapped delete error to find the backend cause.
  2. Add health checks and reconnect logic to the storage client.
  3. Ensure reqCtx is derived from the request lifecycle, not a longer-lived context.
  4. For Redis, confirm maxmemory-policy and connection pool health.
  5. 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

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


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)