Digging the new Concurrency Facade from Laravel & the Early Review

Digging the new Concurrency Facade from Laravel & the Early Review

·

3 min read

Hey guys,

From LaraconUS 2024, Taylor shared with us a lot of cool stuff. From the code, and the framework to the infrastructure. PHP and Laravel are getting hot more than ever 🔥

A new Facade has been shared & released in the latest version: the Concurrency

Let’s check it out and I’ll share my early review on that.

The Concurrency Facade

Introduction

The Concurrency facade helps us to run tasks concurrently.

  • Before, tasks need to be run synchronously.

  • After laravel/framework v11.23.2, with Concurrency, we can run the tasks asynchronously*

    • learn more about the * in the Digging Deeper section below.

With that, we can dispatch and run our tasks (instead of one by one) and we can have

Usage

// $res is a Collection<Result>
$res = Concurrency::run([
    fn () => MyTask::run(),
    fn () => MyOtherTask::run(), 
]);

$res[0] // access the result

Digging deeper into the Concurrency Facade

Let’s check out the implementation.

At first glance, there are 2 drivers that we can use:

Since I always have a thing for the in-house stuff, I’ll only review ProcessDriver for this blog posting.

How does the ProcessDriver work?

Looking at the run driver, it will serialize our closure and send it to a new Console command that Laravel introduced.

That command will invoke the closure and return the result.

So we can see, 1 concurrent task = 1 console command process.

Considerations / Problems / Flaws

  • Each task process will bootstrap a whole application again.

    • This could increase the number of DB connections.
  • Having many processes would decrease the overall server's performance.

    • Probably worse in the serverless environment (AWS Lambda)
  • It might have some out-of-context issues if the concurrent task has dependencies (e.g.: using $this->something reference)

  • It uses the Promise.all approach instead of the Promise.allSettled.

    • So if there is an error, we won't get all the successful results (get an Exception instead)

Reviews

Small tasks

It’s not probably a good idea to use Concurrency for many small tasks, reasons:

  • It has to bootstrap the whole application for each task, the overall bootstrap can take from 10~70ms depending on the server’s gig & codebase’s size.

  • Many processes will slow down the overall server’s performance.

  • There is no way to get successful results if there is an error in the middle. Perhaps this is something Laravel will need to improve soon.

I was thinking of doing something like:

Concurrency::run(
    $users->map(
        fn (User $user) => fn () => SendWelcomeEmailJob::dispatch($user)
    )
);

But after digging a bit, I’m still sticking with the normal foreach and dispatch 🥹 It’s way more optimal for the time being.

Large Tasks

If we can manage how many tasks we’re going to dispatch. Perhaps we can use Concurrency for large tasks (e.g.: concurrent 3rd party API calls).

Conclusion

Well, guys, I’m still sticking with normal Laravel stuff and Queue Jobs at the time being hehe. Things are too early for the very best, let’s wait for a while for the improvements from Laravel’s Core Team and the lovely huge Laravel Community.

Currently, PHP is not a language designed to run things concurrently in a process. Most of the things are workaround solutions.

  • The same goes for fork from Spatie, creating processes to run tasks.

If you are using Octane x Swoole, then you can use the concurrency from Swoole which utilizes the Event Loop (the brain of JS), totally better.

Thanks for reading!

Â