{"record":{"id":"76e6c79b0e56d90a","repo":"gofiber/fiber","slug":"limiter-failed-to-persist-state-w-76e6c7","errorCode":null,"errorMessage":"limiter: failed to persist state: %w","messagePattern":"limiter: failed to persist state: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"middleware/limiter/limiter_sliding.go","lineNumber":93,"sourceCode":"\n\t\t// Calculate how many hits can be made based on the current rate\n\t\tremaining := maxRequests - rate\n\n\t\t// Update storage. Garbage collect when the next window ends.\n\t\t// |--------------------------|--------------------------|\n\t\t//               ^            ^               ^          ^\n\t\t//              ts         e.exp   End sample window   End next window\n\t\t//               <------------>\n\t\t// \t\t\t\t   Reset In Sec\n\t\t// resetInSec = e.exp - ts - time until end of current window.\n\t\t// duration + expiration = end of next window.\n\t\t// Because we don't want to garbage collect in the middle of a window\n\t\t// we add the expiration to the duration.\n\t\t// Otherwise, after the end of \"sample window\", attackers could launch\n\t\t// a new request with the full window length.\n\t\tif setErr := manager.set(reqCtx, key, e, ttlDuration(resetInSec, expiration)); setErr != nil {\n\t\t\tmux.Unlock()\n\t\t\treturn fmt.Errorf(\"limiter: failed to persist state: %w\", setErr)\n\t\t}\n\n\t\t// Unlock entry\n\t\tmux.Unlock()\n\n\t\t// Check if hits exceed the allowed maximum for this request\n\t\tif remaining < 0 {\n\t\t\t// Return response with Retry-After header\n\t\t\t// https://tools.ietf.org/html/rfc6584\n\t\t\tif !cfg.DisableHeaders {\n\t\t\t\tc.Set(fiber.HeaderRetryAfter, utils.FormatUint(resetInSec))\n\t\t\t}\n\n\t\t\t// Call LimitReached handler\n\t\t\treturn cfg.LimitReached(c)\n\t\t}\n\n\t\t// Continue stack for reaching c.Response().StatusCode()","sourceCodeStart":75,"sourceCodeEnd":111,"githubUrl":"https://github.com/gofiber/fiber/blob/a105acad6c1e4576a77f01e02973f67e962bb58d/middleware/limiter/limiter_sliding.go#L75-L111","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","solutions":["Restore storage availability and verify credentials.","Drop the Storage option for per-process memory limiting if cross-instance coordination is optional.","Regenerate msgpack after middleware upgrades (`make generate`).","Tune KeyGenerator to reduce key cardinality if the storage is overwhelmed."],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":["Omit Storage for per-process sliding-window limiting if cross-instance is optional.","Tune KeyGenerator cardinality to keep storage load manageable.","Keep storage client pools and timeouts sized for bursts."],"tags":["limiter","sliding-window","storage","rate-limiting"],"backgroundTag":null,"analyzedSha":"a105acad6c1e4576a77f01e02973f67e962bb58d","analyzedAt":"2026-08-11T17:33:26.942Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}