{"record":{"id":"f278c7280fb2f0e6","repo":"we-promise/sure","slug":"rate-limit-exceeded","errorCode":null,"errorMessage":"Rate limit exceeded","messagePattern":"Rate limit exceeded","errorType":"exception","errorClass":"Provider::Binance::RateLimitError","httpStatus":429,"severity":"warning","filePath":"app/models/provider/binance.rb","lineNumber":184,"sourceCode":"    def sign(params)\n      query_string = params.is_a?(Hash) ? URI.encode_www_form(params.sort) : params\n      OpenSSL::HMAC.hexdigest(\"sha256\", api_secret, query_string)\n    end\n\n    def auth_headers\n      { \"X-MBX-APIKEY\" => api_key }\n    end\n\n    def handle_response(response)\n      parsed = response.parsed_response\n\n      case response.code\n      when 200..299\n        parsed\n      when 401\n        raise AuthenticationError, extract_error_message(parsed) || \"Unauthorized\"\n      when 429\n        raise RateLimitError, \"Rate limit exceeded\"\n      else\n        msg = extract_error_message(parsed) || \"API error: #{response.code}\"\n        raise InvalidSymbolError, msg if parsed.is_a?(Hash) && parsed[\"code\"] == -1121\n        raise ApiError, msg\n      end\n    end\n\n    def extract_error_message(parsed)\n      return parsed if parsed.is_a?(String)\n      return nil unless parsed.is_a?(Hash)\n      parsed[\"msg\"] || parsed[\"message\"] || parsed[\"error\"]\n    end\nend\n","sourceCodeStart":166,"sourceCodeEnd":198,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/provider/binance.rb#L166-L198","documentation":"Raised by Provider::Binance#handle_response when Binance returns HTTP 429. Binance enforces IP-based request-weight limits (e.g. 6,000 weight/min per IP on spot); each endpoint costs weight, and when the bucket is exhausted every request gets 429. Sustained 429s escalate to HTTP 418 (IP auto-ban), so this error must be handled with backoff, not immediate retries.","triggerScenarios":"Calling fetch_security_prices or account endpoints in a tight loop (backfilling many securities) so weight accumulates past the per-minute cap; batch imports that page klines per symbol without a delay; multiple app instances or jobs sharing one egress IP; running alongside other Binance tooling on the same server.","commonSituations":"A nightly backfill job iterating hundreds of symbols; Sidekiq concurrency multiplying requests; deploying to a shared NAT gateway whose IP is also used by other Binance clients; forgetting that Binance counts weight per IP, not per API key; getting intermittent 429s that become permanent 418 because code retries without waiting.","solutions":["Retry with exponential backoff honoring the Retry-After header (start >= 60s for weight-limit bans; 2s for transient bursts).","Throttle request emission: include/throttle via Provider::RateLimitable or set a min interval between calls in the loop.","Reduce weight: fetch wider kline intervals/ranges per request instead of many small calls, and batch symbols where the endpoint allows.","If the IP is shared, lower per-process concurrency (Sidekiq concurrency, job queues) or move Binance calls to a single dedicated worker.","If requests now fail with 418, the IP is temporarily banned — stop retrying and wait out the ban window (typically 2 min to 3 days) before resuming at a lower rate."],"exampleFix":"# before\nprices = provider.fetch_security_prices(symbol: s, exchange_operating_mic: mic, start_date: from, end_date: to)\n\n# after\nattempts = 0\nbegin\n  attempts += 1\n  prices = provider.fetch_security_prices(symbol: s, exchange_operating_mic: mic, start_date: from, end_date: to)\nrescue Provider::Binance::RateLimitError\n  raise if attempts >= 5\n  sleep(2**attempts)\n  retry\nend","handlingStrategy":"retry","validationCode":null,"typeGuard":"def binance_rate_limited?(err)\n  err.is_a?(Provider::Binance::RateLimitError)\nend","tryCatchPattern":"attempts = 0\nbegin\n  attempts += 1\n  result = provider.fetch_security_prices(symbol: s, exchange_operating_mic: mic, start_date: from, end_date: to)\nrescue Provider::Binance::RateLimitError\n  raise if attempts >= 5\n  sleep(2**attempts + rand(2))\n  retry\nend","preventionTips":["Throttle request emission via Provider::RateLimitable or a min interval between calls.","Prefer fewer, wider kline requests over many narrow ones to lower request weight.","Serialize Binance calls through one worker/queue so concurrency cannot burst past the IP weight cap.","Monitor for HTTP 418 follow-ons: on 418 stop retrying entirely and wait out the IP ban.","Add jittered exponential backoff on 429; never retry immediately."],"tags":["binance","rate-limit","http-429","backoff","throttling"],"backgroundTag":"rate-limit-429","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}