gofiber/fiber · warning
cache: failed to delete raw key
Error message
cache: failed to delete raw key %q after store error: %w
What it means
Raised inside the cache store path when, after the metadata entry write fails, the best-effort cleanup tries to delete the already-written raw body key (key+"_body") and that deletion also fails. The cleanup error is joined to the original store error via errors.Join, so the returned error carries both. It indicates a degraded external Storage during a cache write — the raw body is now orphaned with no metadata pointing to it.
Solutions
- Inspect the wrapped inner error (errors.Is/As) — the underlying Storage driver error names the real cause (e.g. redis: connection refused, quota exceeded); fix that first.
- Treat this as non-fatal for the request: the cache write already failed, so log the joined cleanup error and let the request proceed uncached (the handler still ran).
- Run periodic eviction of orphaned *_body keys on external Storage (TTL/expiry on raw keys) so a failed cleanup does not leak indefinitely.
- If the error is recurring, verify Storage health, connection pool sizing, and that the configured expiration is respected by the backend.
Example fix
// before: letting the joined error abort the response
if err := manager.set(reqCtx, key, e, exp); err != nil {
return err // cleanup failure is surfaced as the response error
}
// after: cache is advisory — downgrade cleanup failures to logs
if err := manager.set(reqCtx, key, e, exp); err != nil {
log.Warn("cache store failed (cleanup may have leaked raw body):", err)
// continue serving from handler, not from cache
} Defensive patterns
Strategy: try-catch
Try / catch
// cleanupOnStoreError returns a JOINED error (original + cleanup).
// Unwrap and log each; never let it fail the user response.
if err := storeCachedResponse(...); err != nil {
log.Warn("cache store path errored:", err)
for ub := err; ub != nil; {
if u, ok := ub.(interface{ Unwrap() []error }); ok {
for _, e := range u.Unwrap() {
log.Warn(" - ", e)
}
break
}
if u, ok := ub.(interface{ Unwrap() error }); ok {
ub = u.Unwrap()
} else {
break
}
}
// proceed uncached
} Prevention
- Treat cache writes as advisory; never return cache-path errors to the client.
- Give raw-body keys the same TTL as their metadata so orphaned cleanup never leaks indefinitely.
- Monitor external Storage health and failover so the two-write path rarely hits a mid-flight outage.
When it happens
Trigger: Produced only when cfg.Storage is set, manager.setRaw(key+"_body") succeeded but the following manager.set(key) failed, and then manager.del(key+"_body") in cleanupOnStoreError also returned an error. Requires a Storage backend that fails SetWithContext/DeleteWithContext mid-operation (Redis disconnect, SQLite I/O error, storage quota hit).
Common situations: External Storage (Redis, MongoDB, SQLite) hitting a transient outage or max-memory eviction mid write; storage quota/size limit exceeded on the second write; network partition between app and storage that surfaces between the two writes. Rare under memory Storage because that path sets rawStored=false.
Related errors
- cache: failed to delete key
- cache: failed to get key
- cache: failed to get raw key
- cache: failed to store key
- cache: failed to store raw key
AI-assisted analysis of gofiber/fiber@a105acad6c (2026-08-11).
Data as JSON: /api/errors/da9179e14f929dcc.
Report an issue: GitHub.
Appendix: source
Thrown at middleware/cache/cache.go:965
spaceReserved = false // Clear flag to prevent defer from unreserving
mux.Unlock()
}
cleanupOnStoreError := func(ctx context.Context, releaseEntry, rawStored bool) error {
var cleanupErr error
if cfg.MaxBytes > 0 {
mux.Lock()
_, size := heap.remove(heapIdx)
storedBytes -= size
mux.Unlock()
}
if releaseEntry {
manager.release(e)
}
if rawStored {
rawKey := key + "_body"
if err := manager.del(ctx, rawKey); err != nil {
cleanupErr = errors.Join(cleanupErr, fmt.Errorf("cache: failed to delete raw key %q after store error: %w", maskKey(rawKey), err))
}
}
return cleanupErr
}
// For external Storage we store raw body separated
if cfg.Storage != nil {
if err := manager.setRaw(reqCtx, key+"_body", e.body, storageExpiration); err != nil {
if cleanupErr := cleanupOnStoreError(reqCtx, true, false); cleanupErr != nil {
err = errors.Join(err, cleanupErr)
}
return err
}
// avoid body msgp encoding
e.body = nil
if err := manager.set(reqCtx, key, e, storageExpiration); err != nil {
if cleanupErr := cleanupOnStoreError(reqCtx, false, true); cleanupErr != nil {
err = errors.Join(err, cleanupErr)View on GitHub (pinned to a105acad6c)