gofiber/fiber · warning

cache: failed to delete expired key %q: %w

Error message

cache: failed to delete expired key %q: %w

What it means

Returned from the request-time lookup path when a cached entry has ttl==0 and exp!=0 and is now past expiration, deleteKey(reqCtx, key) was invoked to evict it on read, and the underlying manager.del (plus optional '_body' delete for external storage) failed. This is an inline lazy-expiration failure: the entry is stale but couldn't be purged from storage.

Source

Thrown at middleware/cache/cache.go:323

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

Solutions

  1. Inspect the storage backend error wrapped by %w — it tells you whether it's a connection, permission, or serialization issue.
  2. Shore up storage reliability (pool size, timeouts, network) so Delete is robust under load.
  3. Tune Expiration and MaxBytes so a fresher working set reduces lazy-expiry Deletes on the hot path.
  4. As a stopgap, run a periodic sweep/flush of the storage so stale entries don't accumulate.

Example fix

// before: shared redis with default 1 conn -> Delete timeouts under load
cfg := cache.Config{Storage: redis.New(redis.Config{PoolSize: 1})}

// after: sized pool + sane timeouts
cfg := cache.Config{
  Storage: redis.New(redis.Config{
    PoolSize: 32,
    // driver-appropriate read/write timeouts
  }),
}
Defensive patterns

Strategy: retry

Validate before calling

func pingStorage(s fiber.Storage) error {
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    return s.Delete(ctx, "__nonexistent__") // expect nil even if absent
}

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 expired key") {
        // transient storage Delete failure; the stale entry will be retried later
        log.Warnf("lazy expiry delete failed: %v", err)
        return nil
    }
    return err
})

Prevention

When it happens

Trigger: A GET request hits a key whose cached entry expired; cfg.Storage (remote) returned an error on Delete for the key or its '_body' companion. Reproducible by making the storage driver return errors on Delete while a previously-set key ages past its expiration.

Common situations: Redis Delete fails due to a transient connection reset right when a stale key is read; the '_body' raw key was already evicted by a parallel request and the backend returns an unexpected error; storage pool saturated at peak traffic.

Related errors


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