laravel / ideas

Issues board used for Laravel internals discussions.
938 stars 28 forks source link

Faster artisan schedule:run (now it takes over 4% of vCPU) #1919

Open misog opened 4 years ago

misog commented 4 years ago

Hi, I am using Laravel 5.6 on DigitalOcean with one vCPU, however this is probably an issue for higher Laravel versions too.

The schedule:run command is expected to be called in CRON. You can not control at which second the command is called.

This creates a big problem when you have multiple apps on one VPS or cloud server - each artisan:run (without any application code) takes 4% - 5% of CPU power (measured with htop) and if you have 20 Laravel apps on one server with one vCPU it all takes 100% of CPU every minute. Sure, it is at a peek just for a second but it can create problems with Varnish cache timeouts.

My proposal is to create a new file laravel-cron-scheduler which would be CPU efficient as an alternative to the slow artisan file.

Michael-Stokoe commented 4 years ago

if you have 20 Laravel apps on one server with one vCPU it all takes 100% of CPU every minute

This is an infrastructure problem, not a framework issue.

Most people will run <10 apps on a single app server. And fewer for each large-scale/resource-intensive application.

The only situation I can imagine running 20+ sites from one server is a clients/staging server so my clients can see drafts of sites. But I wouldn't run schedules or anything on them anyways.

If you're looking for a slim framework that can handle running 20+ apps on a $5 DO droplet, you'll probably want to look at PHP Slim or something.

thannaske commented 4 years ago

This indeed infrastructural. If you don't do anything in your schedule, the CPU most likely won't peek. It probably just peeks because you are doing some work in there.

However here's an idea for a workaround: You can just add a sleep $someSeconds && in front of your command where you execute the schedule:run command. Put in some different values for the $someSeconds placeholder and this should ensure that they won't run in a parallel manner.

Another idea would be to put all your schedule:run calls in a shell script where each command gets executed after the other one has been completed. Then just call that shell script in your crontab.

Michael-Stokoe commented 4 years ago

Further to what @thannaske said, you can also set your schedule to not overlap executions of certain commands.

When defining your schedules, tag on ->withoutOverlapping() and it will only process one instance of a particular command at any given moment. This is slower, but better optimised for lower available resources.

Docs: https://laravel.com/docs/5.8/scheduling#preventing-task-overlaps

misog commented 4 years ago

The problem is not my custom code, but that even empty Console\Kernel.php instance takes 20 * 4% CPU. I implemented simple custom schedule checker in artisan file which decides if to exit the script. It decreased the average CPU load to 2-3% per one process. Then I delayed CRON as @thannaske mentioned, with a random delay. This should be good for tens of new applications.

* * * * * sleep $(awk "BEGIN {print $(shuf -i 0-10000 -n 1) / 1000}") && artisan-cron schedule:run >> /dev/null 2>&1
mfn commented 4 years ago

empty Console\Kernel.php instance takes 20 * 4% CPU.

I implemented simple custom schedule checker in artisan file which decides

Seems like that the booting of the framework itself is slow? Did you investigate further?

There's https://github.com/laravel/ideas/issues/1399 , maybe it's related.

misog commented 4 years ago

@mfn yes, the booting is slow because the artisan file loads a lot of the framework, that is why I proposed to create a new file laravel-cron-scheduler which would be CPU efficient and would run only necessary classes.