<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sebastian Riedel</title>
    <description>The latest articles on DEV Community by Sebastian Riedel (@kraih).</description>
    <link>https://dev.to/kraih</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F69879%2F192268ec-ea49-4ace-bf4f-cf8ba5a04233.jpeg</url>
      <title>DEV Community: Sebastian Riedel</title>
      <link>https://dev.to/kraih</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kraih"/>
    <language>en</language>
    <item>
      <title>mojo.js 1.0 - from Perl to Node.js</title>
      <dc:creator>Sebastian Riedel</dc:creator>
      <pubDate>Mon, 20 Jun 2022 14:57:14 +0000</pubDate>
      <link>https://dev.to/kraih/mojojs-10-from-perl-to-nodejs-d4c</link>
      <guid>https://dev.to/kraih/mojojs-10-from-perl-to-nodejs-d4c</guid>
      <description>&lt;p&gt;After one year of development work we are happy to finally announce the very first major release of the &lt;a href="https://mojojs.org"&gt;mojo.js&lt;/a&gt; web framework for &lt;strong&gt;Node.js&lt;/strong&gt;. Now available on &lt;a href="https://github.com/mojolicious/mojo.js"&gt;GitHub&lt;/a&gt; and &lt;a href="https://www.npmjs.com/package/@mojojs/core"&gt;NPM&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here's the obligatory "hello world" single file app, with WebSockets:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;But mojo.js is not really about single file apps. As a very traditional hypermedia framework and spiritual successor to &lt;a href="https://mojolicious.org"&gt;Mojolicious&lt;/a&gt;, it strongly encourages a Model-View-Controller (MVC) layout, while also supporting these single file apps for prototyping.&lt;/p&gt;

&lt;h2&gt;
  
  
  But, Why?
&lt;/h2&gt;

&lt;p&gt;Right now almost all JavaScript web frameworks are split into two categories. On one side you have middleware frameworks, that do pretty much nothing else than routing on their own, so with every new project you have to build your own framework from scratch with dozens of middleware layers. And on the other side you have the kitchen-sink, where the framework makes every decision for you, forcing you to use React on the frontend, or MongoDB as the database, often even limiting your hosting options to a few large cloud providers. Especially the latter kind of web framework tends not to age very well.&lt;/p&gt;

&lt;p&gt;We believe that there is still ample room in the middle for mojo.js. A framework that provides just the essential building blocks for backend web services. The things that rarely change from project to project. Like routing, serving static files, server-side rendering, logging, config files, form validation... you get the idea. In ten years from now you should still feel confident relying on them.&lt;/p&gt;

&lt;p&gt;Aside from reliability, having components specifically designed to be used together allows for significant performance optimisations. That's why mojo.js is a lot faster than Express and Koa for example, despite having many more features.&lt;/p&gt;

&lt;h2&gt;
  
  
  From Perl to Node.js
&lt;/h2&gt;

&lt;p&gt;The Mojolicious project is a group of polyglot programmers who started out with Perl back in the hay days of CGI scripting. Some of us have been making mainstream web frameworks for two decades now. From &lt;a href="http://catalyst.perl.org"&gt;Catalyst&lt;/a&gt; in 2004 to &lt;a href="https://mojolicious.org"&gt;Mojolicious&lt;/a&gt; in 2010. Powering some of the largest sites on the web along the way.&lt;/p&gt;

&lt;p&gt;Ever since Perl6 (now &lt;a href="https://en.wikipedia.org/wiki/Raku_(programming_language)"&gt;Raku&lt;/a&gt;) started to become a thing we've had plans to port Mojolicious to more languages than just Perl5. But Perl6 drifted into a different direction than what we were hoping for and so those plans never truly materialised.&lt;/p&gt;

&lt;p&gt;However, at the same time JavaScript kept evolving. The language gained features like ES6 classes, &lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt;, ES modules, arrow functions, &lt;code&gt;const&lt;/code&gt;/&lt;code&gt;let&lt;/code&gt; keywords and much more. Node.js finally brought JavaScript to the server-side. On the language level, there's a pretty close relationship between Perl and JavaScript (and it's not just sharing &lt;code&gt;use strict&lt;/code&gt; or having native Regex data types). So it was inevitable that some of us would grow to like JavaScript quite a bit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not quite full-stack web framework
&lt;/h2&gt;

&lt;p&gt;I still remember when full-stack meant that the framework contained a router, template engine, and an ORM with support for a bunch of SQL databases. These days they include things like a custom React distribution and a subscription for a serverless hosting service. It's hard to escape these ecosystems again without changing your whole tech stack.&lt;/p&gt;

&lt;p&gt;With mojo.js we don't do most of those things. While it ships with a router and server-side renderer with support for multiple template engines, there is no default database. Just a &lt;a href="https://mojojs.org/docs/Growing.md#model"&gt;workflow&lt;/a&gt; for adding your own model layer. Similarly choosing a frontend framework is entirely up to you. The static file server will deliver whatever assets you need.&lt;/p&gt;

&lt;p&gt;What's most important for us, is to provide you with a rock solid foundation. RESTful routing, WebSockets, static file server, cli, logging, config files, session management, form and JSON validation, content negotiation, TypeScript types and a testing framework. Components you can rely on for decades if necessary.&lt;/p&gt;

&lt;p&gt;Software supply chain attacks around NPM are a hot topic right now. And it can be quite problematic to audit your whole dependency tree. Thankfully, while the Perl version has no dependencies at all, in JavaScript we only have 23 trusted third party dependencies. And we are prepared to replace every single one of them if necessary, with a port of the battle-tested Perl implementation.&lt;/p&gt;

&lt;p&gt;We've also started releasing spin-off projects for standalone use outside of mojo.js, based on some of our other popular Perl projects. &lt;a href="https://www.npmjs.com/package/@mojojs/dom"&gt;@mojojs/dom&lt;/a&gt;, &lt;a href="https://www.npmjs.com/package/@mojojs/template"&gt;@mojojs/template&lt;/a&gt; and &lt;a href="https://www.npmjs.com/package/@mojojs/path"&gt;@mojojs/path&lt;/a&gt; follow the same strict rules as mojo.js itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about Mojolicious?
&lt;/h2&gt;

&lt;p&gt;Now you might be wondering if we are going to abandon Mojolicious. And the answer is a resounding no. All of us still enjoy Perl very much, and look forward to keeping it alive for at least a few more decades. 😉&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Have fun!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>node</category>
      <category>javascript</category>
      <category>mojolicious</category>
    </item>
    <item>
      <title>Playwright and Mojolicious</title>
      <dc:creator>Sebastian Riedel</dc:creator>
      <pubDate>Fri, 26 Mar 2021 17:08:37 +0000</pubDate>
      <link>https://dev.to/kraih/playwright-and-mojolicious-21hn</link>
      <guid>https://dev.to/kraih/playwright-and-mojolicious-21hn</guid>
      <description>&lt;p&gt;It's &lt;a href="http://hackweek.suse.com" rel="noopener noreferrer"&gt;Hack Week&lt;/a&gt; again at &lt;a href="https://suse.com" rel="noopener noreferrer"&gt;SUSE&lt;/a&gt;. 🥳 An annual tradition where we all work on passion projects for a whole week. Some of us make &lt;a href="https://www.youtube.com/watch?v=b0tsZB_LEQk" rel="noopener noreferrer"&gt;music&lt;/a&gt;, others use the time to experiment with the latest technologies and start new Open Source projects.&lt;/p&gt;

&lt;p&gt;My project this time was to see if there's a better way for us to do automated browser testing of our &lt;a href="https://mojolicious.org" rel="noopener noreferrer"&gt;Mojolicious&lt;/a&gt; web applications. For a long time &lt;a href="https://www.selenium.dev" rel="noopener noreferrer"&gt;Selenium&lt;/a&gt; has been the de facto standard for browser automation, but these days there are more modern alternatives available, such as &lt;a href="https://playwright.dev" rel="noopener noreferrer"&gt;Playwright&lt;/a&gt;. But how good is Playwright really? Spoiler: It's very good. But keep reading to find out why and at what cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Playwright?
&lt;/h2&gt;

&lt;p&gt;Playwright, just like Selenium before it, is a framework for browser automation. You can use it for all sorts of scripted interactions with websites, such as buying those Nvidia GPUs from online retailers faster than everyone else 😉, but it is most commonly used in test suites of web applications.&lt;/p&gt;

&lt;p&gt;The code is being developed by Microsoft as an Open Source project with Apache 2.0 license and distributed as an NPM package. So all you need is &lt;a href="https://nodejs.org" rel="noopener noreferrer"&gt;Node&lt;/a&gt;, and you can install it with a one-liner.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ npm i playwright
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are bindings for other languages, but to get the most out of Playwright you do want to be using JavaScript. Now when it comes to browser support, where Selenium would give you the choice to pick any &lt;a href="https://w3c.github.io/webdriver/" rel="noopener noreferrer"&gt;WebDriver&lt;/a&gt; compatible browser as backend, Playwright will download custom builds of Chromium, Firefox and WebKit for you. And that's all you get.&lt;/p&gt;

&lt;p&gt;They are doing it for pretty good reasons though. The browser binaries tend to work flawlessly on all supported platforms, which currently include Windows, macOS and Linux (x86). And when you are used to the sluggishness of Selenium, it almost seems magical how fast and reliable Playwright runs.&lt;/p&gt;

&lt;p&gt;This is because where Selenium sticks to open protocols, Playwright will use every trick in the book for better performance. Including &lt;a href="https://github.com/microsoft/playwright/tree/master/browser_patches" rel="noopener noreferrer"&gt;custom patches&lt;/a&gt; for those browsers, extending their DevTools protocols, and then using those protocols to control the browsers. I'm not a huge fan of the approach, but it's hard to argue with the results.&lt;/p&gt;

&lt;p&gt;Short term there are huge benefits, but having to maintain these browser patches indefinitely, if they don't get merged upstream, might hamper the longevity of the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Playwright
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;assert/strict&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;playwright&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;headless&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;slowMo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newContext&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://mojolicious.org/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text=Documentation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text=Tutorial&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://docs.mojolicious.org/Mojolicious/Guides/Tutorial&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tutorial.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you've ever done web development before, the API will be very intuitive, and it was clearly designed with &lt;code&gt;async/await&lt;/code&gt; in mind, which i'm a huge fan of. You can have multiple isolated browser contexts, with their own cookies etc., and each context can have multiple pages.&lt;/p&gt;

&lt;p&gt;Every interaction, such as &lt;code&gt;page.click()&lt;/code&gt;, will automatically wait for the element to become &lt;a href="https://playwright.dev/docs/actionability" rel="noopener noreferrer"&gt;visible&lt;/a&gt;, with a timeout that defaults to 30 seconds. This is a huge step up from Selenium, where you have to build this logic yourself, and will get it wrong in many many entertaining ways. 😅&lt;/p&gt;

&lt;p&gt;You can emulate devices such as iPhones, use geolocation, change timezones, choose between headless and headful mode for all browsers, and have the option to take screenshots or make video recordings at any time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg722khkaep13vapdeuwe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg722khkaep13vapdeuwe.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of the latest features to be added was the GUI recorder, which opens a Chromium window, and then records all user interactions while generating JavaScript code as you go. I was a bit sceptical about this at first, but it can significantly speed up test development, since you don't have to think too much about CSS selectors anymore. Even if you just end up using parts of the generated code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Playwright and Perl
&lt;/h2&gt;

&lt;p&gt;Running Playwright against live websites is very straight forward. But for automated testing of web applications you also want your test scripts to start and stop the web server for you. And this is where things get a little bit tricky if your web application happens to be written in a language other than JavaScript.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Mojolicious::&lt;/span&gt;&lt;span class="nv"&gt;Lite&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;signatures&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;get&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;index&lt;/span&gt;&lt;span class="p"&gt;'};&lt;/span&gt;

&lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;start&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="cp"&gt;__DATA__
@@ index.html.ep
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
  &amp;lt;body&amp;gt;Hello World!&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What i needed to run my Perl app was a JavaScript superdaemon with support for socket activation. Unfortunately i've not been able to find a module for the job on NPM, and had to resort to &lt;a href="https://www.npmjs.com/package/@mojolicious/server-starter" rel="noopener noreferrer"&gt;writing my own&lt;/a&gt;. And now the Mojolicious organisation is not just on CPAN, but also on NPM. 😇&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;assert/strict&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ServerStarter&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@mojolicious/server-starter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;playwright&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ServerStarter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newServer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;perl&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test.pl&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;daemon&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-l&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://*?fd=3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newContext&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;innerText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;body&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello World!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You might have noticed the odd listen location &lt;code&gt;http://*?fd=3&lt;/code&gt;. That's a Mojolicious feature we've originally developed for &lt;code&gt;systemd&lt;/code&gt; deployment with &lt;a href="https://www.freedesktop.org/software/systemd/man/systemd.socket.html" rel="noopener noreferrer"&gt;.socket files&lt;/a&gt;. The superdaemon, in that case &lt;code&gt;systemd&lt;/code&gt;, would bind the listen socket very early during system startup, and then pass it to the service the &lt;code&gt;.socket&lt;/code&gt; file belongs to as file descriptor &lt;code&gt;3&lt;/code&gt;. This has many advantages, such as services being started as unprivileged users able to use privileged ports.&lt;/p&gt;

&lt;p&gt;Anyway, our use case here is slightly different, but the same mechanism can be used. And by having the superdaemon activate the socket we can avoid multiple race conditions. The socket will be active before the web application process has even been spawned, meaning that &lt;code&gt;page.goto()&lt;/code&gt; can never get a connection refused error. Instead it will just be waiting for its connection to be accepted. And important for very large scale testing, with many tests running in parallel on the same machine, we can use random ports assigned to us by the operating system. Avoiding the possibility of conflicts as a result of bad timing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Combining Everything
&lt;/h2&gt;

&lt;p&gt;And for my final trick i will be using the excellent &lt;a href="https://node-tap.org" rel="noopener noreferrer"&gt;Node-Tap&lt;/a&gt;, allowing our JavaScript tests to use the &lt;a href="http://testanything.org" rel="noopener noreferrer"&gt;Test Anything Protocol&lt;/a&gt;, which happens to be the standard used in the Perl world for testing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cp"&gt;#!/usr/bin/env node
&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tap&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ServerStarter&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@mojolicious/server-starter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;playwright&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Test the Hello World app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ServerStarter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newServer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;perl&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test.pl&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;daemon&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-l&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://*?fd=3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newContext&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;innerText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;body&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello World!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You might have noticed the shebang line &lt;code&gt;#!/usr/bin/env node&lt;/code&gt;. That's another little Perl trick. When the Perl interpreter encounters a shebang line that's not &lt;code&gt;perl&lt;/code&gt; it will re-exec the script. In this case with &lt;code&gt;node&lt;/code&gt;, and as a side effect we can use standard Perl testing tools like &lt;code&gt;prove&lt;/code&gt; to run our JavaScript tests right next to normal Perl tests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ prove t/*.t t/*.js
t/just_a_perl_test.t ... ok                                                                                                     
t/test.js .. ok
All tests successful.
Files=3, Tests=4,  2 wallclock secs ( 0.03 usr  0.01 sys +  2.42 cusr  0.62 csys =  3.08 CPU)
Result: PASS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In fact, you could even run multiple of these tests in parallel with &lt;code&gt;prove -j 9 t/*.js&lt;/code&gt; to scale up effortlessly. Playwright can handle parallel runs and will perform incredibly well in headless mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  One More Thing
&lt;/h2&gt;

&lt;p&gt;And if you've made it this far i've got one more thing for you. In the &lt;a href="https://github.com/kraih/mojo-playwright" rel="noopener noreferrer"&gt;mojo-playwright&lt;/a&gt; repo on GitHub you can find a WebSocket chat application and mixed JavaScript/Perl tests that you can use for experimenting. It also contains solutions for how to set up test fixtures with &lt;a href="https://github.com/kraih/mojo-playwright/blob/master/t/wrappers/change_title.pl" rel="noopener noreferrer"&gt;wrapper scripts&lt;/a&gt; and how to run them in GitHub Actions. Have fun!&lt;/p&gt;

</description>
      <category>mojolicious</category>
      <category>perl</category>
      <category>javascript</category>
      <category>node</category>
    </item>
    <item>
      <title>High Priority Fast Lane for the Minion Job Queue</title>
      <dc:creator>Sebastian Riedel</dc:creator>
      <pubDate>Sat, 06 Mar 2021 16:10:59 +0000</pubDate>
      <link>https://dev.to/kraih/high-priority-fast-lane-for-the-minion-job-queue-4711</link>
      <guid>https://dev.to/kraih/high-priority-fast-lane-for-the-minion-job-queue-4711</guid>
      <description>&lt;p&gt;&lt;a href="https://minion.pm" rel="noopener noreferrer"&gt;Minion&lt;/a&gt; is a high performance job queue designed for use with &lt;a href="https://mojolicious.org" rel="noopener noreferrer"&gt;Mojolicious&lt;/a&gt;. In this article i will introduce you to one of the new features in the &lt;a href="https://github.com/mojolicious/minion/compare/v10.16...v10.17#diff-bbd4b6a86bc65b6ac8e79e97afc61499158edb40f7bb404c70637b46d80a7ad2R2" rel="noopener noreferrer"&gt;10.17 release&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvc44n7i03qpzpmdcuif7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvc44n7i03qpzpmdcuif7.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Recently in one of my &lt;a href="http://open.qa" rel="noopener noreferrer"&gt;work projects&lt;/a&gt; at &lt;a href="https://suse.com" rel="noopener noreferrer"&gt;SUSE&lt;/a&gt; i ran into a job queue congestion issue. We have a lot of very slow background jobs to perform various maintenance tasks, such as cleaning up old files from disk that are no longer needed. Some of these jobs can take over an hour to finish and they are not particularly time critical, so very low priority.&lt;/p&gt;

&lt;p&gt;Now, due to some bad luck we had a large backlog of these one hour jobs. And at some point there were no higher priority jobs left in the queue. So the Minion worker ended up processing only one hour jobs at full capacity.&lt;/p&gt;

&lt;p&gt;This wouldn't have been a problem if we didn't also have time critical high priority jobs come in right at that moment. With no extra capacity left, they had to wait the full hour. And since those high priority jobs were created by users of the web service, they were of course assuming something was broken when they didn't receive a result in a timely manner. And we ended up with a whole bunch of bug reports.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Fast Lane
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft52yjtox5jzuqfdgvo78.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft52yjtox5jzuqfdgvo78.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Of course this is not a particularly uncommon problem. Larger web services would simply use multiple Minion workers with different named queues for various service levels. Probably with different hardware for paying customers and those using a free plan. But this is a smaller service that some users also install on their own machines locally. So one Minion worker is more than enough under normal circumstances.&lt;/p&gt;

&lt;p&gt;What we needed was a simpler solution. The &lt;a href="https://docs.mojolicious.org/Mojolicious/Guides/Cookbook#Pre-forking" rel="noopener noreferrer"&gt;prefork&lt;/a&gt; web server that ships with Mojolicious has this concept of spare processes. Where under heavy load it is allowed to spawn a few extra processes for request handling at peak times. While normally it would only keep a smaller number of processes preforked.&lt;/p&gt;

&lt;p&gt;The same concept can also be applied to the &lt;a href="https://docs.mojolicious.org/Minion/Command/minion/worker" rel="noopener noreferrer"&gt;Minion worker&lt;/a&gt;. We simply set aside a small number of jobs from our maximum capacity only to be used for high priority jobs. So instead of just a &lt;code&gt;--jobs 4&lt;/code&gt; setting, we now also have a &lt;code&gt;--spare 2&lt;/code&gt; setting. And at peak times there will be 6 jobs running in parallel, of which at least 2 are guaranteed to be high priority. What exactly is considered high priority can be controlled with the &lt;code&gt;--spare-min-priority 5&lt;/code&gt; setting, which will default to &lt;code&gt;1&lt;/code&gt;, since the default priority for all jobs is &lt;code&gt;0&lt;/code&gt; and lower priority jobs are usually given negative priority numbers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ script/myapp minion worker --jobs 4 --spare 2
[2021-03-06 13:50:22.02638] [30272] [info] Worker 30272 started
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Implementation
&lt;/h2&gt;

&lt;p&gt;Since Minion relies on &lt;a href="https://www.postgresql.org/" rel="noopener noreferrer"&gt;PostrgeSQL&lt;/a&gt; the actual implementation was very simple. Just &lt;a href="https://github.com/mojolicious/minion/compare/e45b12292018e52596020ba054a985c45d3a0201...4e437409c73dba574bf3979e59766540147a0f3e#diff-4ec6b10a4557406d60b8339a01a89fd99aaa83b4c1bc1fee6edd29edb7cfe5d9R124" rel="noopener noreferrer"&gt;two lines&lt;/a&gt; of Perl to spawn spare processes and a one line change in the SQL query used to dequeue jobs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;UPDATE minion_jobs SET started = NOW(), state = 'active', worker = ?
WHERE id = (
&lt;/span&gt;  SELECT id FROM minion_jobs AS j
  WHERE delayed &amp;lt;= NOW() AND id = COALESCE(?, id) AND (parents = '{}' OR NOT EXISTS (
    SELECT 1 FROM minion_jobs WHERE id = ANY (j.parents) AND (
      state = 'active' OR (state = 'failed' AND NOT j.lax)
      OR (state = 'inactive' AND (expires IS NULL OR expires &amp;gt;
&lt;span class="gd"&gt;-  )) AND queue = ANY (?) AND state = 'inactive' AND task = ANY (?) AND (EXPIRES IS NULL OR expires &amp;gt; NOW())
&lt;/span&gt;&lt;span class="gi"&gt;+  )) AND priority &amp;gt;= COALESCE(?, priority) AND queue = ANY (?) AND state = 'inactive' AND task = ANY (?)
+    AND (EXPIRES IS NULL OR expires &amp;gt; NOW())
&lt;/span&gt;  ORDER BY priority DESC, id
  LIMIT 1
  FOR UPDATE SKIP LOCKED
)
&lt;span class="p"&gt;RETURNING id, args, retries, task;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Luckily the index we use for the &lt;code&gt;ORDER BY&lt;/code&gt; also works for the minimum priority check, so no further optimisations were needed. We can still dequeue thousands of jobs per second. I love PostgreSQL.&lt;/p&gt;

</description>
      <category>mojolicious</category>
      <category>perl</category>
      <category>postgres</category>
    </item>
  </channel>
</rss>
