{"record":{"id":"da9179e14f929dcc","repo":"gofiber/fiber","slug":"cache-failed-to-delete-raw-key-q-after-store-err","errorCode":null,"errorMessage":"cache: failed to delete raw key %q after store error: %w","messagePattern":"cache: failed to delete raw key %q after store error: %w","errorType":"http","errorClass":null,"httpStatus":500,"severity":"warning","filePath":"middleware/cache/cache.go","lineNumber":965,"sourceCode":"\t\t\tspaceReserved = false // Clear flag to prevent defer from unreserving\n\t\t\tmux.Unlock()\n\t\t}\n\n\t\tcleanupOnStoreError := func(ctx context.Context, releaseEntry, rawStored bool) error {\n\t\t\tvar cleanupErr error\n\t\t\tif cfg.MaxBytes > 0 {\n\t\t\t\tmux.Lock()\n\t\t\t\t_, size := heap.remove(heapIdx)\n\t\t\t\tstoredBytes -= size\n\t\t\t\tmux.Unlock()\n\t\t\t}\n\t\t\tif releaseEntry {\n\t\t\t\tmanager.release(e)\n\t\t\t}\n\t\t\tif rawStored {\n\t\t\t\trawKey := key + \"_body\"\n\t\t\t\tif err := manager.del(ctx, rawKey); err != nil {\n\t\t\t\t\tcleanupErr = errors.Join(cleanupErr, fmt.Errorf(\"cache: failed to delete raw key %q after store error: %w\", maskKey(rawKey), err))\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn cleanupErr\n\t\t}\n\n\t\t// For external Storage we store raw body separated\n\t\tif cfg.Storage != nil {\n\t\t\tif err := manager.setRaw(reqCtx, key+\"_body\", e.body, storageExpiration); err != nil {\n\t\t\t\tif cleanupErr := cleanupOnStoreError(reqCtx, true, false); cleanupErr != nil {\n\t\t\t\t\terr = errors.Join(err, cleanupErr)\n\t\t\t\t}\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t// avoid body msgp encoding\n\t\t\te.body = nil\n\t\t\tif err := manager.set(reqCtx, key, e, storageExpiration); err != nil {\n\t\t\t\tif cleanupErr := cleanupOnStoreError(reqCtx, false, true); cleanupErr != nil {\n\t\t\t\t\terr = errors.Join(err, cleanupErr)","sourceCodeStart":947,"sourceCodeEnd":983,"githubUrl":"https://github.com/gofiber/fiber/blob/a105acad6c1e4576a77f01e02973f67e962bb58d/middleware/cache/cache.go#L947-L983","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before: letting the joined error abort the response\nif err := manager.set(reqCtx, key, e, exp); err != nil {\n    return err // cleanup failure is surfaced as the response error\n}\n\n// after: cache is advisory — downgrade cleanup failures to logs\nif err := manager.set(reqCtx, key, e, exp); err != nil {\n    log.Warn(\"cache store failed (cleanup may have leaked raw body):\", err)\n    // continue serving from handler, not from cache\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// cleanupOnStoreError returns a JOINED error (original + cleanup).\n// Unwrap and log each; never let it fail the user response.\nif err := storeCachedResponse(...); err != nil {\n    log.Warn(\"cache store path errored:\", err)\n    for ub := err; ub != nil; {\n        if u, ok := ub.(interface{ Unwrap() []error }); ok {\n            for _, e := range u.Unwrap() {\n                log.Warn(\" - \", e)\n            }\n            break\n        }\n        if u, ok := ub.(interface{ Unwrap() error }); ok {\n            ub = u.Unwrap()\n        } else {\n            break\n        }\n    }\n    // proceed uncached\n}","preventionTips":["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."],"tags":["cache","storage","cleanup","cascading","best-effort","go","fiber"],"backgroundTag":null,"analyzedSha":"a105acad6c1e4576a77f01e02973f67e962bb58d","analyzedAt":"2026-08-11T17:33:26.942Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}