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, withConcurrency
, 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:
ForkDriver (uses
spatie/fork
)
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 thePromise.allSettled
.- So if there is an error, we won't get all the successful results (get an
Exception
instead)
- So if there is an error, we won't get all the successful results (get an
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!