Thavo's avatar
Level 2

Best Approach to Handling API Rate Limits

Hello Laracasts Community,

I'm currently facing a challenge with managing API rate limits when integrating with the MercadoLivre API in my Laravel application. I have a limit of 1500 requests per minute, but sometimes I need to handle over 4000 requests within that same minute. This often results in hitting the rate limit and receiving 429 Too Many Requests errors, which subsequently leads to job failures due to max attempts timeout.

Here's an overview of my setup:

<?php

namespace App\Service;

use App\Models\MercadolivreUsers;
use App\Repositories\MercadolivreRepository;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\RateLimiter;

class MercadolivreClientService
{
    protected Client $client;
    protected string $clientId;
    protected string $clientSecret;
    protected ?string $accessToken = null;
    protected ?string $refreshToken = null;
    private MercadolivreRepository $mercadolivreRepository;

    public function __construct(Client $client, MercadolivreRepository $mercadolivreRepository)
    {
        $this->client = $client;
        $this->clientId = Config::get('services.mercadolivre.mercadolivre_client_id');
        $this->clientSecret = Config::get('services.mercadolivre.mercadolivre_client_secret');
        $this->mercadolivreRepository = $mercadolivreRepository;
    }

    protected function makeApiRequest(string $endpoint, string $method, array $body = []): array
    {
        $key = $this->getRateLimitKey();

        if (RateLimiter::tooManyAttempts($key, config('mercadolivre.rate_limit.limit'))) {
            Log::warning('Rate limit exceeded for key: '.$key);
            return ['error' => 'Too many requests'];
        }

        RateLimiter::hit($key, config('mercadolivre.rate_limit.decay_minutes') * 60);

        $url = $this->makePath($endpoint, $method === 'GET' ? $body : []);
        $options = $this->buildRequestOptions($method, $body);

        try {
            $response = $this->client->request($method, $url, $options);
            return json_decode((string) $response->getBody(), true);
        } catch (ClientException $e) {
            return $this->handleClientException($e);
        }
    }

    // Other methods...

    private function getRateLimitKey(): string
    {
        return 'mercadolivre_rate_limit';
    }
}

I am encountering MaxAttemptsExceededException errors frequently, especially when there's a spike in the number of requests. The rate limit exceeded log messages are also quite frequent, indicating that jobs are being released and retried, but not efficiently handled.

What I Need

Advice on Handling Rate Limits: What are the best practices for managing API rate limits in a scenario where the rate limit is frequently hit?

Queue Management: How can I effectively hold and release queued jobs to ensure they are processed once the rate limit resets, without hitting the MaxAttemptsExceededException?

Error Handling: Is there a more efficient way to handle the 429 Too Many Requests errors without losing the jobs or overwhelming the system?

Thanks.

0 likes
1 reply
martinbean's avatar

@thavo The Laravel queue docs discusses dealing with rate limits in depth.

The simplest solution would be just to release the job back on the queue to be handled when the rate limit requests:

public function handle()
{
    try {
        // Do HTTP request
    } catch (MercadoLivreRateLimitException $e) {
        $minutes = $e->getRetryAfter(); // Get actual value from somewhere

        $this->release($minutes);
    }
}

Please or to participate in this conversation.