7

I have a web service exposed using the Flask framework.

This service uses as external API, which has a limitation on the number of times it should be called per second.

In a normal scenario, multiple calls to my API leads to multiple threads getting generated and the external API getting called without any control on the number of requests per second.

Is there a way in which I can queue requests to my web service and then call the external API in a throttled way.

Any other ideas are also welcomed.


Edit:

  1. I already know the rate to the external API ( 1 request per second)

  2. I am ok if the client requesting my API has to wait a little long (few seconds / minutes depending on the load I have) before they get the results.

  3. I don't want my API clients to get failed results. i.e. I don't want them to call again and again. If the external API is already being accessed by me at the max rate possible, the requests to my API should get queued and processed when the rate comes down.

  4. I read about Celery and Redis. Will I be able to queue the web service calls in to these queues and process them later ?

Brendan Abel
  • 35,343
  • 14
  • 88
  • 118
Amit Tomar
  • 4,800
  • 6
  • 50
  • 83
  • What would you like to happen when the requests are exceeded? Do you want the requests to your API to block? Or will you return your own `http 429` response? – Brendan Abel Feb 08 '16 at 05:39
  • I want the requests to my API to wait if required. ( queued up may be !? ) and return the results as and when I can satisfy the request per second criterion of the external API. I have no issues if call to my requests take a few seconds to return. – Amit Tomar Feb 08 '16 at 05:48
  • Celery is normally used to kick off long-running processes from web requests. Generally, your clients won't get a useful response back other than to know the request went into the celery queue, unless you're actually doing something to alert your clients when the celery task finishes, like sending them an email, or sending them a message using socket.io or something. – Brendan Abel Feb 08 '16 at 06:33

1 Answers1

2

One way is to wrap the request, so that rate limit failures will result in an exponential backoff until an acceptable rate is found.

In the example below, it will keep retrying the request until it succeeds, waiting longer and longer between requests each time it fails, up to a maximum number of allowed retries (n_max). The number of seconds it waits to retry the request increases exponentially (1, 2, 4, 8, 16, 32, etc).

Here is an example that uses requests. The specifics of catching the errors and recognizing rate limit errors will depend on the library your using to make the requests and the type of errors the external api returns, but the backoff algorithm should be the same.

def call_backoff(request_func, n_max=10):
    n = 0
    while n < n_max:
        error = False
        try:
            response = request_func()
        except errors.HttpError as e:
            # You can test here if this is a rate error
            # and not some other error.  You can decide how to handle
            # other errors.
            error = True
            if not_a_rate_error:
                raise

         if response.status_code == 429:
             error = True

         if not error:
             return response

         milli = random.randint(1, 1000)
         secs = (2 ** n) + milli / 1000.0
         time.sleep(secs)
         n += 1

    # You can raise an error here if you'd like
    return None
Brendan Abel
  • 35,343
  • 14
  • 88
  • 118
  • I already know the same rate limit for the external API (Max 1 request per second). However, I don't want to return 'empty' results if the external API limit is getting exceeded. I just want them to be queued and processed a little slower may be, and then return the valid result. – Amit Tomar Feb 08 '16 at 06:04
  • With your method, clients will have to call my API again and again to get results, right ? – Amit Tomar Feb 08 '16 at 06:05
  • With the exponential backoff, it will keep retrying the request to the external api, while the request to your api blocks, so your clients won't have to retry their request. Every time it fails, it waits longer and longer to retry the next request. In the example I gave above (`n_max` is actually pretty high), it will retry up to 10 times. After the 9th try, it's going to wait over **15 minutes** before it makes a 10th request. That's probably too high, though. It's probably better to respond to your clients with an error. – Brendan Abel Feb 08 '16 at 06:11
  • @Brended This might lead to starvation as well ? While a thread is waiting for external API, other's might access it in between and thus again causing the waiting thread to get backed off ? – Amit Tomar Feb 08 '16 at 06:16
  • @AmitTomar That is a possibility, but requests aren't normally guaranteed to return in the order they were sent anyway. If you wanted requests to block in a more "fair" way, you could use a shared state variable to indicate whether your api is currently "blocking" to block new requests until the old ones complete. – Brendan Abel Feb 08 '16 at 06:27
  • Thanks! Let me give it a try. – Amit Tomar Feb 08 '16 at 06:37