Troubleshooting¶
redis.exceptions.ResponseError: unknown command 'eval'¶
Cause: using fakeredis in tests without its lua extra installed —
fakeredis's default install doesn't emulate Lua scripting.
Fix: pip install "fakeredis[lua]" (this package's own test extra
already includes it).
429 Too Many Requests immediately on the very first request¶
Cause: almost always a burst/rate misconfiguration — e.g.
burst=0, or a cost higher than the configured rate's count (a
single request can never succeed if its cost exceeds the bucket's total
capacity).
Fix: ensure burst >= cost for token bucket, and rate.count >= cost
generally. Check docs/configuration.md#weighted-costs if using a
callable cost.
RateLimit-* headers missing from successful responses¶
Cause: the view doesn't use RateLimitHeadersMixin (they're not
added automatically just by using rate_limit() in throttle_classes —
see Architecture).
Fix: add the mixin, before the DRF base class in the MRO:
class ArticleViewSet(RateLimitHeadersMixin, viewsets.ModelViewSet):
...
For function-based views, @ratelimit() adds headers itself — no mixin
needed there.
Headers show up but are always None/missing on some requests¶
Cause: if you override get_throttles() yourself on a view that also
uses RateLimitHeadersMixin, make sure your override calls
super().get_throttles() (don't replace it entirely) so the mixin's
caching still takes effect — see
Architecture.
InvalidRateError: Invalid rate string¶
Cause: a rate string that doesn't match "<count>/<period>", or an
unrecognized period name.
Fix: valid periods are s/sec/second, m/min/minute,
h/hour, d/day. count must be a positive integer (not a decimal).
UnknownTierError when using plan tiers¶
Cause: the resolved tier name (from tier_resolver, or
request.user.plan by default) has no entry in your rate={...} mapping,
and your mapping has no entry for the DEFAULT_TIER setting to fall
back to.
Fix: either add an entry for every tier your resolver can produce, or
add one for whatever DEFAULT_TIER is configured to ("default" unless
you've changed it).
Different Django processes/workers seem to have independent limits¶
Cause: each process resolved a different Redis instance/DB — check
REDIS_URL/REDIS_CLIENT is identical across all your app server
processes (a common mistake: pointing at localhost in a
multi-container deployment where each container has its own local
Redis, rather than a shared one).
Fix: ensure every process connects to the same Redis instance/Cluster — rate limiting is inherently a cross-process concern, so a per-process-local Redis defeats the purpose entirely.
CROSSSLOT Keys in request don't hash to the same slot¶
This should not happen with the built-in algorithms (see
Architecture) — if you see
it, you're likely using a custom algorithm or key function that builds
multiple Redis keys per identity without a shared {} hash tag. Wrap the
shared identity portion in {} as drf_ratelimit_plus.algorithms.sliding_window_check
does.