{"id":20145,"date":"2020-05-09T10:00:35","date_gmt":"2020-05-09T04:30:35","guid":{"rendered":"https:\/\/www.the-next-tech.com\/?p=20145"},"modified":"2020-05-12T10:12:21","modified_gmt":"2020-05-12T04:42:21","slug":"make-your-software-ready-to-scale-using-laravel-queues-from-the-basics-to-horizon","status":"publish","type":"post","link":"https:\/\/www.the-next-tech.com\/development\/make-your-software-ready-to-scale-using-laravel-queues-from-the-basics-to-horizon\/","title":{"rendered":"Make your software ready to scale using Laravel Queues \u2014 from the basics to Horizon"},"content":{"rendered":"<p>This guide is for all PHP developers that have an application online with real users, but they need a deeper understanding of how to introduce (or drastically improve) scalability in their system using Laravel queues.<\/p>\n<p>The first time I read about Laravel was in late 2013 at the beginning of version 5.x of the framework. I wasn\u2019t yet a developer involved in significant projects, and one of the aspects of modern frameworks, <a href=\"https:\/\/www.the-next-tech.com\/top-10\/top-tips-and-tricks-to-stay-productive-with-laravel\/\">especially in Laravel<\/a>, that sounded the most mysterious to me was \u201cQueues\u201d.<\/p>\n<p>Reading the documentation, I guessed at the potential, but without real development experience it stayed just a theory in my mind.<\/p>\n<p>Today I\u2019m working on<em>\u00a0<\/em><em>a real time big data tool <\/em>that executes thousands of jobs every hour, so my knowledge of this architecture is much better than in the past.<\/p>\n<p>In this article I\u2019m going to show you how I discovered queues and jobs, and what configurations helped me to treat a large amount of data in real time while keeping server resources cost-friendly.<\/p>\n<h3>A gentle introduction<\/h3>\n<p>When a PHP application receives an incoming http request, our code is executed sequentially, <strong>step by step,<\/strong> until the request\u2019s execution ends and a response is returned back to the client (e.g. the browser).<\/p>\n<p>That synchronous behavior is really intuitive, predictable, and simple to understand.<\/p>\n<p>I launch an http request to my endpoint, the application retrieves data from the database, converts it into an appropriate format, execute some additional task, and sends it back. It\u2019s linear.<\/p>\n<p>Queues and jobs introduce asynchronous behaviors that break this linear flow. That\u2019s why I think these functions seemed a little strange to me at the beginning.<\/p>\n<p>But sometimes a time-consuming task involves completing an execution cycle due to an incoming http request, e.g., sending an email notification to all team members of a project.<\/p>\n<p>It could mean sending six or ten emails and it could take four or five seconds to be completed. So every time a user clicks on that button, they need to wait five seconds before they can continue using the app.<\/p>\n<p>More the application grows, the worse this problem gets.<br \/>\n<span class=\"seethis_lik\"><span>Also read:<\/span> <a href=\"https:\/\/www.the-next-tech.com\/finance\/what-is-walmart-credit-card-grace-period\/\">What Is Walmart Credit Card Grace Period? Explained<\/a><\/span>\n<h3>What is a &#8220;Job&#8221;?<\/h3>\n<p>Good question.<\/p>\n<p>A Job is a class that implements the \u201c<em>handle<\/em>\u201d method that will contains the logic we want to execute at the time the job is scheduled to be executed.<\/p>\n<pre class=\"prettyprint\">use Illuminate\\Contracts\\Queue\\ShouldQueue;\r\nuse Illuminate\\Foundation\\Bus\\Dispatchable;\r\nuse Illuminate\\Queue\\InteractsWithQueue;\r\nuse Illuminate\\Bus\\Queueable;\r\n\r\nclass CallExternalAPI implements ShouldQueue\r\n{\r\n    use Dispatchable;\r\n    use InteractsWithQueue;\r\n    use Queueable;\r\n        \r\n    \/**\r\n     * @var string\r\n     *\/\r\n    protected $url;\r\n\r\n    \/**\r\n     * Create a new job instance.\r\n     *\r\n     * @param array $Data\r\n     *\/\r\n    public function __construct($url)\r\n    {\r\n        $this-&gt;url = $url;\r\n    }\r\n    \r\n\r\n    \/**\r\n     * Execute what you want.\r\n     *\r\n     * @return void\r\n     * @throws \\Throwable\r\n     *\/\r\n    public function handle()\r\n    {\r\n        file_get_contents($this-&gt;url);\r\n    }\r\n}<\/pre>\n<p>As mentioned above the main reason to encapsulate a piece of code into a <strong>Job <\/strong>is to execute a time consuming task without forcing the user to wait its execution.<\/p>\n<h3>What do you mean with \u201ctime consuming tasks\u201d?<\/h3>\n<p>This is a legitimate question.<\/p>\n<p>Sending emails is the most common example used in articles that talk about queues, but I want to tell you what I needed to do in my real experience.<\/p>\n<p>As product owner it\u2019s really important for me to keep users\u2019 journey information in sync with our marketing and customer support tools. So, based on user actions, we update user information to various external software via APIs (a.k.a. external http calls) for marketing and customer care purposes.<\/p>\n<p>One of the most used endpoint in my application could sends 10 emails and execute 3 http call to external services to be completed. No user would wait all this time, much more likely they would stop using my application.<\/p>\n<p>Thanks to queues, I can encapsulate all these tasks in dedicated classes, pass in the contructor the information needed to do their job, and schedule their execution for later in the background so my controller can return a response immediately.<\/p>\n<pre class=\"prettyprint\">class ProjectController \r\n{\r\n    public function store(Request $request)\r\n    {\r\n        $project = Project::create($request-&gt;all());\r\n        \r\n        \/\/ Defer NotifyMembers, TagUserActive, NotifyToProveSource \r\n        \/\/ passing the information needed to do their job\r\n        Notification::queue(new NotifyMembers($project-&gt;owners));\r\n        $this-&gt;dispatch(new TagUserAsActive($project-&gt;owners));\r\n        $this-&gt;dispatch(new NotifyToProveSource($project-&gt;owners));\r\n        \r\n        return $project;\r\n    }\r\n}<\/pre>\n<p>I don\u2019t need to wait until all of these processes are completed before returning a response; rather, I\u2019ll wait only for the time needed to publish them in the queue.<\/p>\n<p>This could mean the difference between 10 seconds and 10 milliseconds!!!<\/p>\n<h3>Who executes these jobs after posting them in the queue?<\/h3>\n<p>This is a classic \u201cpublisher\/consumer\u201d architecture. We\u2019ve just published our jobs in the queue from the controller, so now we are going to understand how the queue is consumed, and finally jobs executed.<\/p>\n<p><img loading=\"lazy\" class=\"aligncenter size-medium\" src=\"https:\/\/www.inspector.dev\/wp-content\/uploads\/2019\/09\/1_Evvsj-ibqWBiAemzFMHzdA.png\" alt=\"Laravel queue consumption\" width=\"1587\" height=\"793\" \/><\/p>\n<p>To consume a queue we need to run one of the most popular artisan command:<\/p>\n<pre class=\"prettyprint\">php artisan queue:work<\/pre>\n<p>As reported in the <a href=\"https:\/\/laravel.com\/docs\/master\/queues#running-the-queue-worker\" target=\"_blank\" rel=\"noopener\">Laravel documentation<\/a>:<\/p>\n<div class=\"whit-blockquote\">Laravel includes a queue worker that will process new jobs as they are pushed onto the queue<\/div>\n<p>Great! Laravel provides a ready-to-use interface to put jobs in a queue and a ready-to-use command to pull jobs from the queue and execute their code in the background.<\/p>\n<h3>The role of Supervisor<\/h3>\n<p>This was another \u201cstrange thing\u201d at the beginning. I think it\u2019s normal discovering new things. Also I have experienced this phase of study so I write these articles to help me organize my skills and at the same time I can help other developers to expand their knowledge.<\/p>\n<p>If a job fails wen firing an exception, the <strong>queue: work<\/strong> command will stop its work.<\/p>\n<p>To keep the <strong>queue: work<\/strong> process running permanently (consuming your queues), you should use a process monitor such as <strong>Supervisor<\/strong> to ensure that the <strong>queue: work<\/strong> command does not stop running even if a job fires an exception.<\/p>\n<p>The supervisor restarts the command after it goes down, starting again from the next job, abandoning the one that failed.<\/p>\n<p>Well.<\/p>\n<p>Jobs will be executed in the background on your server, no longer depending on an HTTP request. This introduces some changes that I had to consider when implementing the job\u2019s code.<\/p>\n<p>Here is the most important in my mind:<\/p>\n<h3 id=\"4fee\">How can I be aware if the job code fails?<\/h3>\n<p>Running in background you can\u2019t see immediately if your job generate errors.<\/p>\n<p>You will no longer have immediate feedback like running an http request from your browser. If the job fails he will do it silently, without anyone noticing.<\/p>\n<p><b>We will come back to this problem later.<\/b><\/p>\n<h3 id=\"c981\">You don\u2019t have the HTTP request<\/h3>\n<p><em>The http request is gone<\/em>. Your code will be executed from cli.<\/p>\n<p>If you need request parameters to accomplish your tasks you need to pass them in the job\u2019s constructor to use later during execution:<\/p>\n<pre class=\"prettyprint\">\/\/ A job class example\r\nclass TagUserJob implements ShouldQueue\r\n{\r\n    public $data;\r\n    \r\n    public function __construct(array $data)\r\n    {\r\n        $this-&gt;data = $data;\r\n    }\r\n}\r\n\r\n\/\/ Put the job in the queue from your controller\r\n$this-&gt;dispatch(new TagUserJob($request-&gt;all()));<\/pre>\n<h3 id=\"f850\">You don\u2019t know who the logged user is<\/h3>\n<p><em>The session is gone.<\/em>\u00a0In the same way, you won\u2019t know the identity of the user who was logged in, so if you need the user information to accomplish the task, you need to pass the user object to the job\u2019s constructor:<\/p>\n<pre class=\"prettyprint\">\/\/ A job class example\r\nclass TagUserJob implements ShouldQueue\r\n{\r\n    public $user;\r\n    \r\n    public function __construct(User $user)\r\n    {\r\n        $this-&gt;user= $user;\r\n    }\r\n}\r\n\r\n\/\/ Put the job in the queue from your controller\r\n$this-&gt;dispatch(new TagUserJob($request-&gt;user()));<\/pre>\n<h3 id=\"80b0\">Understand how to scale<\/h3>\n<p>Unfortunately in many cases it isn\u2019t enought. Using a single queue and consumer it may soon become useless.<\/p>\n<p>Queues are FIFO buffers (<strong>F<\/strong>irst\u00a0<strong>I<\/strong>n\u00a0<strong>F<\/strong>irst\u00a0<strong>O<\/strong>ut). If you schedule many jobs, also of different types, they need to wait for others to execute their scheduled tasks before being completed<\/p>\n<p>There are two ways to scale.<br \/>\n<span class=\"seethis_lik\"><span>Also read:<\/span> <a href=\"https:\/\/www.the-next-tech.com\/artificial-intelligence\/best-ai-music-generator\/\">10 Best AI Music Generator In 2025 (Royalty Free Music Generation)<\/a><\/span>\n<h3 id=\"7b6e\">Multiple consumers for a queue<\/h3>\n<p><img loading=\"lazy\" class=\"aligncenter size-medium\" src=\"https:\/\/www.inspector.dev\/wp-content\/uploads\/2019\/09\/1_B95oVhCZoVkv_2jhV8nHew.png\" alt=\"Multiple consumers for a queue\" width=\"261\" height=\"276\" \/><\/p>\n<p>In this way, five jobs will be pulled from the queue at a time, speeding up the queue\u2019s consumption.<\/p>\n<h3 id=\"e6d4\">Single-purpose queues<\/h3>\n<p>You could also create specific queues for each job \u201ctype\u201d you are launching, with a dedicated consumer for each queue.<\/p>\n<p><img loading=\"lazy\" class=\"aligncenter size-medium\" src=\"https:\/\/www.inspector.dev\/wp-content\/uploads\/2019\/09\/1_1ICT3D0C0bie7Xg-wrk7gg.png\" alt=\"Single-purpose queues\" width=\"368\" height=\"302\" \/><\/p>\n<p>In this way, each queue will be consumed independently without having to wait for the execution of the other types of jobs.<\/p>\n<h3 id=\"346d\">Towards Horizon<\/h3>\n<p><a href=\"https:\/\/laravel.com\/docs\/master\/horizon\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>Laravel Horizon<\/strong><\/a><strong>\u00a0is a queue manager<\/strong>\u00a0that gives you full control over how many queues you want to set up and the ability to organize consumers, allowing developers to put these two strategies together and implement one that fits your scalability needs.<\/p>\n<p>It all starts by running\u00a0<code>php artisan horizon<\/code>\u00a0instead\u00a0<code>php artisan queue:work<\/code>. This command scans your\u00a0<code>horizon.php<\/code>\u00a0configuration file &amp; starts a number of queue workers based on the configuration:<\/p>\n<pre class=\"prettyprint\">'production' =&gt; [\r\n    'supervisor-1' =&gt; [\r\n        'connection' =&gt; \"redis\",\r\n        'queue' =&gt; ['adveritisement', 'logs', 'phones'],\r\n        'processes' =&gt; 9,\r\n        'tries' =&gt; 3,\r\n        'balance' =&gt; 'simple', \/\/ could be simple, auto, or null\r\n    ]\r\n]<\/pre>\n<p>In the example above Horizon will start three queues with three processes assigned to consume each queue.<\/p>\n<p>As mentioned in\u00a0<a href=\"https:\/\/laravel.com\/docs\/master\/horizon#introduction\" target=\"_blank\" rel=\"noreferrer noopener\">Laravel documentation<\/a>\u00a0Horizon\u2019s code-driven approach allows my configuration to stay in source control where my team can collaborate. It\u2019s a perfect solution also using a CI tool.<\/p>\n<p>To learn the meaning of the configuration options in details consider to read this beautiful article: <a href=\"https:\/\/medium.com\/@zechdc\/laravel-horizon-number-of-workers-and-job-execution-order-21b9dbec72d7\" target=\"_blank\" rel=\"noopener\">Deep Dive into Laravel Horizon \u2014 what goes on behind the scenes<\/a><\/p>\n<h3 id=\"8440\">My own configuration<\/h3>\n<pre class=\"prettyprint\">'production' =&gt; [\r\n    'supervisor-1' =&gt; [\r\n        'connection' =&gt; 'redis',\r\n        'queue' =&gt; ['default', 'ingest', 'notifications'],\r\n        'balance' =&gt; 'auto',\r\n        'processes' =&gt; 15,\r\n        'tries' =&gt; 3,\r\n    ],\r\n]<\/pre>\n<p>I uses mainly three queues:<\/p>\n<ul>\n<li><strong>ingest\u00a0<\/strong>is for processes to analyze data from external applications;<\/li>\n<li><strong>notifications\u00a0<\/strong>is used to schedule notifications immediately if an error is detected during data ingestion;<\/li>\n<li><strong>default\u00a0<\/strong>is used for other tasks that I don\u2019t want interfering with\u00a0<em>ingest\u00a0<\/em>and\u00a0<em>notifications\u00a0<\/em>processes.<\/li>\n<\/ul>\n<p><img loading=\"lazy\" class=\"aligncenter size-medium\" src=\"https:\/\/www.inspector.dev\/wp-content\/uploads\/2019\/09\/1_XHkernGbcjpSSjNID81Mdw.png\" alt=\"laravel horizon configuration\" width=\"1179\" height=\"583\" \/><\/p>\n<p>Using\u00a0<strong>balance=auto<\/strong>\u00a0Horizon knows that the maximum number of processes to be activated is 15, which will be distributed dynamically according to the queues load.<\/p>\n<p>If the queues are empty, Horizon keeps one process active for each queue, keeping a consumer ready to process the queue immediately if a job is scheduled.<\/p>\n<h2 id=\"db64\">Conclusion<\/h2>\n<p>Understand asynchronous execution and its implications couldn&#8217;t be so easy. I hope this guide can help you to improve your application efficiency and outlook.<\/p>\n<p>But keep attention.<\/p>\n<p>Concurrent background execution could causes many other unpredictable bugs like MySQL \u201cLock wait timeout exceeded\u201d and many others design issues.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This guide is for all PHP developers that have an application online with real users, but they need a deeper<\/p>\n","protected":false},"author":948,"featured_media":21106,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"image","meta":[],"categories":[133],"tags":[1914,670,669],"_links":{"self":[{"href":"https:\/\/www.the-next-tech.com\/rest\/wp\/v2\/posts\/20145"}],"collection":[{"href":"https:\/\/www.the-next-tech.com\/rest\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.the-next-tech.com\/rest\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.the-next-tech.com\/rest\/wp\/v2\/users\/948"}],"replies":[{"embeddable":true,"href":"https:\/\/www.the-next-tech.com\/rest\/wp\/v2\/comments?post=20145"}],"version-history":[{"count":13,"href":"https:\/\/www.the-next-tech.com\/rest\/wp\/v2\/posts\/20145\/revisions"}],"predecessor-version":[{"id":21292,"href":"https:\/\/www.the-next-tech.com\/rest\/wp\/v2\/posts\/20145\/revisions\/21292"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.the-next-tech.com\/rest\/wp\/v2\/media\/21106"}],"wp:attachment":[{"href":"https:\/\/www.the-next-tech.com\/rest\/wp\/v2\/media?parent=20145"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.the-next-tech.com\/rest\/wp\/v2\/categories?post=20145"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.the-next-tech.com\/rest\/wp\/v2\/tags?post=20145"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}