As reported from a customer, per-project rate limiting is not respecting the limit they have set. They have limit set to 1000 events in 12 hrs but are seeing upwards of 5K errors in one day.
@getsentry/platform
@mattrobenolt @tkaemming could one of you take a look at this today? its a high priority concern
The frontend is sending this data in minute units, while the quota backend expects them to be seconds. So, what the UI is representing as "12 hour" window (720 minutes) is actually being treated as a "12 minute" (720 second) window by the backend.
The key quota query object is constructed as part of RedisQuota.get_quotas:
The parameters used to construct the quota query object are derived from BaseQuota.get_key_quota, which returns the value directly from the ProjectKey.rate_limit property:
So, we have the ProjectKey.rate_limit_window value directly propagating to the BasicRedisQuota quota query object without any transformation and being set as BasicRedisQuota.window, which is defined in second units:
The PUT method on the ProjectKeyDetailsEndpoint saves the rate limit details directly from the RateLimitSerializer, which also does no transformation of the input data:
On the front end, the rate limit windows are defined here with minute units:
Great investigation overview @tkaemming 🥇
I also checked on the customer account that reported this — the UI shows them with a "1000 event(s) in 12 hours" rate limit (as noted in the original issue comment) that was being stored as 'rate_limit_count': 1000,
'rate_limit_window': 720 on the ProjectKey instance to verify.
@tkaemming would you mind putting up a quick fix for it? we could add a data migration as well, but there's only a handful of accounts using it, so fixing it in prod would also be fine.
Yup, was planning on doing that today. I was thinking about fixing it at the model level to avoid the data migration, but I can do the data migration instead if that's your preference.
no strong opinion -- it'll never be less than 1 minute
Most helpful comment
The frontend is sending this data in minute units, while the quota backend expects them to be seconds. So, what the UI is representing as "12 hour" window (720 minutes) is actually being treated as a "12 minute" (720 second) window by the backend.
Backend Implementation
The key quota query object is constructed as part of
RedisQuota.get_quotas:https://github.com/getsentry/sentry/blob/39353021d957bb47be27b8643e8a20154abfbe1e/src/sentry/quotas/redis.py#L81-L90
The parameters used to construct the quota query object are derived from
BaseQuota.get_key_quota, which returns the value directly from theProjectKey.rate_limitproperty:https://github.com/getsentry/sentry/blob/39353021d957bb47be27b8643e8a20154abfbe1e/src/sentry/quotas/base.py#L69-L74
https://github.com/getsentry/sentry/blob/39353021d957bb47be27b8643e8a20154abfbe1e/src/sentry/models/projectkey.py#L123-L127
So, we have the
ProjectKey.rate_limit_windowvalue directly propagating to theBasicRedisQuotaquota query object without any transformation and being set asBasicRedisQuota.window, which is defined in second units:https://github.com/getsentry/sentry/blob/39353021d957bb47be27b8643e8a20154abfbe1e/src/sentry/quotas/redis.py#L29-L30
Management UI
The
PUTmethod on theProjectKeyDetailsEndpointsaves the rate limit details directly from theRateLimitSerializer, which also does no transformation of the input data:https://github.com/getsentry/sentry/blob/39353021d957bb47be27b8643e8a20154abfbe1e/src/sentry/api/endpoints/project_key_details.py#L99-L105
https://github.com/getsentry/sentry/blob/39353021d957bb47be27b8643e8a20154abfbe1e/src/sentry/api/endpoints/project_key_details.py#L36-L38
On the front end, the rate limit windows are defined here with minute units:
https://github.com/getsentry/sentry/blob/39353021d957bb47be27b8643e8a20154abfbe1e/src/sentry/static/sentry/app/views/projectKeyDetails.jsx#L234-L247
https://github.com/getsentry/sentry/blob/39353021d957bb47be27b8643e8a20154abfbe1e/src/sentry/static/sentry/app/views/projectKeyDetails.jsx#L343-L356