gofiber/fiber · error

limiter: failed to persist state

Error message

limiter: failed to persist state: %w

What it means

Returned by the sliding-window limiter on the increment path: after rotating the window, computing the rate, and incrementing currHits, manager.set is called with ttlDuration(resetInSec, expiration). If the persist fails (marshal or Storage.SetWithContext), the error is wrapped here and the request aborts before serving.

Solutions

  1. Restore storage availability and verify credentials.
  2. Drop the Storage option for per-process memory limiting if cross-instance coordination is optional.
  3. Regenerate msgpack after middleware upgrades (`make generate`).
  4. Tune KeyGenerator to reduce key cardinality if the storage is overwhelmed.
Defensive patterns

Strategy: fallback

Prevention

When it happens

Trigger: limiter_sliding handler increments the bucket and calls manager.set with a TTL spanning the current reset plus one window (to avoid mid-window GC). The set fails — storage down, write rejected, context cancelled, or msgpack marshal error.

Common situations: Shared storage outage during sliding-window limiting, oversized TTL rejected by backend, msgpack schema drift, or storage client pool exhaustion under high cardinality of keys (many distinct clients).

Related errors


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

Appendix: source

Thrown at middleware/limiter/limiter_sliding.go:93

		// Calculate how many hits can be made based on the current rate
		remaining := maxRequests - rate

		// Update storage. Garbage collect when the next window ends.
		// |--------------------------|--------------------------|
		//               ^            ^               ^          ^
		//              ts         e.exp   End sample window   End next window
		//               <------------>
		// 				   Reset In Sec
		// resetInSec = e.exp - ts - time until end of current window.
		// duration + expiration = end of next window.
		// Because we don't want to garbage collect in the middle of a window
		// we add the expiration to the duration.
		// Otherwise, after the end of "sample window", attackers could launch
		// a new request with the full window length.
		if setErr := manager.set(reqCtx, key, e, ttlDuration(resetInSec, expiration)); setErr != nil {
			mux.Unlock()
			return fmt.Errorf("limiter: failed to persist state: %w", setErr)
		}

		// Unlock entry
		mux.Unlock()

		// Check if hits exceed the allowed maximum for this request
		if remaining < 0 {
			// Return response with Retry-After header
			// https://tools.ietf.org/html/rfc6584
			if !cfg.DisableHeaders {
				c.Set(fiber.HeaderRetryAfter, utils.FormatUint(resetInSec))
			}

			// Call LimitReached handler
			return cfg.LimitReached(c)
		}

		// Continue stack for reaching c.Response().StatusCode()

View on GitHub (pinned to a105acad6c)