gofiber/fiber · error

cache: failed to delete raw key %q after store error: %w

Error message

cache: failed to delete raw key %q after store error: %w

What it means

Raised by cleanupOnStoreError during the store path: an entry was being written, manager.setRaw for the '_body' raw key (or the main set) succeeded but the subsequent set failed, so cleanup tries to manager.del the raw '_body' key it just stored — and that delete also failed. The returned error joins the original store error with this cleanup error so neither is hidden.

Source

Thrown at middleware/cache/cache.go:853

			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 9a4c7e57fe)

Solutions

  1. Stabilize the storage backend (capacity, connectivity, ACLs) — both writes and deletes are unreliable.
  2. Out-of-band, flush orphaned '<key>_body' raw entries that the failed cleanup left behind.
  3. Reduce per-entry size (cap response bodies via cfg.MaxBytes) to avoid partial-store situations.
  4. If persistent, switch temporarily to in-memory cache (cfg.Storage == nil) which has no separate raw key.

Example fix

// before: external storage partially stores then fails cleanup
cfg := cache.Config{Storage: unstableStorage}

// after: healthy storage + body size cap
cfg := cache.Config{
  Storage:    stableStorage,
  MaxBytes:   1 << 20, // cap per-entry / total bytes
  Expiration: 5 * time.Minute,
}
Defensive patterns

Strategy: try-catch

Validate before calling

func pingStorageSetDelete(s fiber.Storage) error {
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()
    k := []byte("__ping_body__")
    if err := s.Set(ctx, string(k), []byte("x"), 5*time.Second); err != nil {
        return err
    }
    return s.Delete(ctx, string(k))
}

Type guard

null

Try / catch

app.Use(func(c fiber.Ctx) error {
    err := c.Next()
    if err != nil && strings.Contains(err.Error(), "failed to delete raw key") {
        log.Errorf("cache store + cleanup failed: %v", err)
        return c.Status(fiber.StatusServiceUnavailable).SendString("cache unavailable")
    }
    return err
})

Prevention

When it happens

Trigger: cfg.Storage != nil (external storage); setRaw of key+'_body' succeeded but manager.set(key, e) failed; the compensating manager.del of key+'_body' also failed. Reproducible by failing both set and del on the storage driver while caching a response body.

Common situations: Storage backend degrades mid-write (disk fills between setRaw and set); TLS connection reset means the follow-up del also fails; Redis failover window; an entry slightly over a quota where setRaw fit but set (msgp-encoded item) didn't, then del also broke.

Related errors


AI-assisted analysis of gofiber/fiber@9a4c7e57fe (2026-08-04). Data as JSON: /data/errors/da9179e14f929dcc.json. Report an issue: GitHub.