gofiber/fiber · error

cache: failed to delete key

Error message

cache: failed to delete key %q while evicting: %w

What it means

Returned from the eviction routine when deleteKey fails for the key being evicted (delErr), AND at least one of the previously-restored heap-index candidates also failed (restoreErr != nil). Fiber joins the original delete error with the per-candidate restore errors so both surfaces are visible. This is the worst-case eviction failure: the eviction itself failed and rollback was incomplete.

Solutions

  1. Read errors.Join's joined chain: the first is the original delete error, the rest are restore errors per candidate.
  2. Stabilize storage first (capacity, connectivity) before tuning MaxBytes.
  3. Raise cfg.MaxBytes to reduce eviction pressure if memory allows.
  4. Audit storage maxmemory-policy and pool sizing.
  5. Add storage error-rate alerting so this compound failure is caught early.

Example fix

// before: very tight MaxBytes amplifies storage blips into compound failures
cfg := cache.Config{MaxBytes: 1024, Storage: flakyRedis}

// after: realistic cap + stable storage
cfg := cache.Config{MaxBytes: 64 * 1024 * 1024, Storage: stableRedis}
Defensive patterns

Strategy: retry

Validate before calling

// Bound the eviction surface so compound failures are rare.
func validateCacheSizing(cfg cache.Config) error {
    if cfg.MaxBytes > 0 && cfg.MaxBytes < 16*1024 {
        return fmt.Errorf("MaxBytes %d too small; eviction runs on every write and amplifies storage blips", cfg.MaxBytes)
    }
    return nil
}

Try / catch

app.Use(func(c fiber.Ctx) error {
    err := c.Next()
    if err != nil && isCacheEvictionErr(err) {
        // compound eviction failure: degrade to bypassing cache for this request and alert
        metrics.EvictionCompoundError.Inc()
        return nil // serve uncached
    }
    return err
})

Prevention

When it happens

Trigger: The cache needs to evict to satisfy MaxBytes, the chosen key's storage Delete fails, AND when Fiber tries to put back the heap entries it had optimistically evicted, one or more of those Sets also fails. Requires storage instability sustained across multiple operations.

Common situations: Storage outage or saturation lasting longer than a single op (Redis under OOM, network partition); MaxBytes set very tight so eviction runs constantly and amplifies any storage blip into a compound failure; multi-instance contention on the same storage.

Related errors


AI-assisted analysis of gofiber/fiber@a105acad6c (2026-08-11). Data as JSON: /api/errors/a596b5c6634e24a1. Report an issue: GitHub.

Appendix: source

Thrown at middleware/cache/cache.go:774

					// Re-add entries to the heap to keep expiration tracking consistent
					var restored []evictionCandidate
					for j := i; j < len(candidates); j++ {
						candidate := candidates[j]
						candidate.heapIdx = heap.put(candidate.key, candidate.exp, candidate.size)
						restored = append(restored, candidate)
					}
					mux.Unlock()

					var restoreErr error
					for _, candidate := range restored {
						if err := refreshHeapIndex(reqCtx, candidate); err != nil {
							restoreErr = errors.Join(restoreErr, err)
						}
					}

					if restoreErr != nil {
						return errors.Join(fmt.Errorf("cache: failed to delete key %q while evicting: %w", maskKey(keyToRemove), delErr), restoreErr)
					}

					return fmt.Errorf("cache: failed to delete key %q while evicting: %w", maskKey(keyToRemove), delErr)
				}
			}
		}

		// Hand back whatever the lookup left holding. A stale entry survives to here
		// whenever the request said no-cache or it carried no expiry, and dropping
		// it would leak one pooled item per such request.
		if e != nil {
			manager.release(e)
		}
		e = manager.acquire()
		// Cache response
		e.body = utils.CopyBytes(c.Response().Body())
		e.status = c.Response().StatusCode()
		e.ctype = utils.CopyBytes(c.Response().Header.ContentType())

View on GitHub (pinned to a105acad6c)