instructure/canvas-lms · error · Pv4TooManyRequests
rate limit exceeded
Error message
rate limit exceeded
What it means
Pv4Client#fetch raises Pv4TooManyRequests when the PV4 page-view service returns HTTP 429, meaning the client exceeded the service's rate limit. The library surfaces this as a typed error instead of retrying, letting callers decide on backoff.
Solutions
- Implement exponential backoff and retry the request after the delay indicated by the 429 response
- Cache page-view history results to reduce repeat calls to PV4
- Throttle/queue calls to PageView.for_user in background jobs
- Ask the PV4 service operators to raise the rate limit if legitimate traffic is being throttled
Example fix
// before views = PageView.for_user(user) // after begin views = PageView.for_user(user) rescue Pv4TooManyRequests sleep(backoff) retry end
Defensive patterns
Strategy: retry
Try / catch
retries = 0 begin views = PageView.for_user(user) rescue Pv4TooManyRequests retries += 1 raise if retries > 3 sleep(2**retries) retry end
Prevention
- Cache page-view history responses
- Avoid polling loops against PageView.for_user
- Move bulk history reads into throttled background jobs
- Respect Retry-After semantics from the PV4 service
When it happens
Trigger: Calling Pv4Client#fetch (via PageView.for_user) too frequently — e.g. polling page-view history in a tight loop or a burst of user requests hitting the same PV4 service.
Common situations: Automated scripts or monitoring repeatedly loading page-view history; many concurrent users requesting history during peak load; missing caching of history results on the Canvas side.
Related errors
- failed to load page view history due to service timeout
- resource not found
- could not retrieve configuration, the server response timed…
- Couldn't send LTI AGS grade progress metric
- Failed to fetch results for context #
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/9eb64fd4d89ae60f.
Report an issue: GitHub.
Appendix: source
Thrown at app/models/page_view/pv4_client.rb:64
params = "start_time=#{start_time.utc.iso8601(PRECISION)}"
params << "&end_time=#{end_time.utc.iso8601(PRECISION)}"
params << "&#{cached_root_account_uuids_for(user:)}"
params << "&last_page_view_id=#{last_page_view_id}" if last_page_view_id
params << "&limit=#{limit}" if limit
response = CanvasHttp.get(
@uri.merge("users/#{user.global_id}/page_views?#{params}").to_s,
request_headers
)
case response.code.to_i
when 400
raise Pv4BadRequest, "invalid request"
when 401
raise Pv4Unauthorized, "unauthorized request"
when 404
raise Pv4NotFound, "resource not found"
when 429
raise Pv4TooManyRequests, "rate limit exceeded"
end
json =
begin
response.body.empty? ? {} : JSON.parse(response.body)
rescue JSON::ParserError
{}
end
raise Pv4EmptyResponse, "the response is empty or does not contain expected keys" unless json["page_views"]
json["page_views"].map! do |pv|
pv["session_id"] = pv.delete("sessionid")
vhost = pv.delete("vhost")
http_request = pv.delete("http_request")
pv["url"] = if vhost.present? && http_request.present?
"#{HostUrl.protocol}://#{vhost}#{http_request}"
elsif http_request.present?
"#{HostUrl.protocol}:#{http_request}"View on GitHub (pinned to 1c9f0bb801)