Latest Posts

Using Laravel's sleep helper to save time

Quick Tips

API's can be a pain - throttling is an issue you'll often encounter, particularly with large datasets. One workaround is to enforce delays between execution


As part of an ongoing project, I'm dealing with an API where we are synchronising tens of thousands of products. That's not too much of a problem, except that the API we are pushing data to will only accept one request per second. By using Laravel's sleep helper, we are able to achieve a huge time saving - how ironic!

Time to sleep

The simple option would be to introduce a one second sleep between each request, but that introduces it's own problem. What is a request takes 340ms, and then we're sleeping for an extra second on top of that. We're looking at a single call taking 1.34 seconds when we could have already moved on to the next record after the second.

What's the problem with that?

Not a problem, right? Assuming I have 25,000 records to process, we're spending an average 1.4 seconds on each request, rather than the one second we're allowed. Adding this up, we're then spending an extra 400ms * 25000 requests = 10,000 seconds. In minutes, that's an extra 166 minutes wasting a total 2 hours and 46 minutes!

And the answer?

My solution to this, is to introduce microsecond sleeps. One second is 1,000,000 microseconds - unfathomable right?

By using microseconds, if the request takes 340ms (340,000 micro seconds), we can then see out the extra 660,000 micro using Laravel's sleep helper and then go straight on to make a new request. This way we can continue on to the next process as quickly as possible.

Rather than using PHP's native sleep, we can utilise Laravel's Sleep helper class to do this, meaning we don't have to introduce physical delays when testing.

function runWithDelay(\Closure $closure, float $duration = 1)
{
    // record the start time as a variable
    $startTime = microtime(true);

    // capture our return value
    $return = $closure();

    // calculate the time we have left to sleep
    $sleepTime = ($duration *= 1000000) - ((microtime(true) - $startTime) * $duration);

    if ($sleepTime > 0) {
        // sleep for the remaining time
        \Illuminate\Support\Sleep::usleep((int)$sleepTime);
    }

	    // return the response value
    return $return;
}

With the above method in my helpers file, I can now execute my API request, ensuring that we sleep for the appropriate amount of time, all the while wasting as little time as we possibly can. An example of it's usage can be found below:

DB::table('stock_sync')->chunkMap(function (array $sync) use ($connector, $response) {
    
    return runWithDelay(function () use ($connector, $sync): void {
        $response = $connector->send(new UpdateProductStock($sync['vend_id'], $sync['stock']));

        if ($response->successful()) {
            DB::table('stock_sync')->where('id', $sync['id'])->delete();
            $this->output->write('u');
            return;
        }

        $this->output->write('.');
    }, 1);
});

So what about testing?

Don't worry, Laravel has that covered! Just use the Sleep Facade's fake method \Illuminate\Support\Sleep::fake() and you won't have to wait around in your testing.

If you enjoyed this article, and would like to be notified about more articles in the future, consider subscribing!