PageSourceSearch

https://docs.flarum.org/assets/js/35448b30.80c3b580.js

js flarum.org collected 2026-09-24 18:14:28 UTC 13,898 bytes, 1 lines download raw bytes

1"use strict";(globalThis.webpackChunkflarum_docs=globalThis.webpackChunkflarum_docs||[]).push([[4],{9861(e,n,s){s.r(n),s.d(n,{assets:()=>u,contentTitle:()=>o,default:()=>l,frontMatter:()=>a,metadata:()=>r,toc:()=>d});const r=JSON.parse('{"id":"queue","title":"Queue","description":"If you\'re just starting out, you won\'t need a queue. But once you reach a certain scale, you cannot go without one.","source":"@site/docs/queue.md","sourceDirName":".","slug":"/queue","permalink":"/queue","draft":false,"unlisted":false,"editUrl":"https://github.com/flarum/docs/tree/main/docs/queue.md","tags":[],"version":"current","frontMatter":{},"sidebar":"guideSidebar","previous":{"title":"Console","permalink":"/console"},"next":{"title":"Audit","permalink":"/extensions/audit"}}');var t=s(4848),i=s(8453);const a={},o="Queue",u={},d=[{value:"Why should I care?",id:"why-should-i-care",level:2},{value:"How do I set up a queue?",id:"how-do-i-set-up-a-queue",level:2},{value:"Going further than the database driver",id:"going-further-than-the-database-driver",level:3},{value:"Monitoring the queue",id:"monitoring-the-queue",level:2},{value:"Failed jobs",id:"failed-jobs",level:2},{value:"Pausing a queue",id:"pausing-a-queue",level:2},{value:"From the admin panel",id:"from-the-admin-panel",level:3},{value:"From the command line",id:"from-the-command-line",level:3},{value:"Named queues",id:"named-queues",level:2}];function h(e){const n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,i.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.header,{children:(0,t.jsx)(n.h1,{id:"queue",children:"Queue"})}),"\n",(0,t.jsx)(n.p,{children:"If you're just starting out, you won't need a queue. But once you reach a certain scale, you cannot go without one."}),"\n",(0,t.jsx)(n.h2,{id:"why-should-i-care",children:"Why should I care?"}),"\n",(0,t.jsx)(n.p,{children:"A Flarum installation that has no queue configured, will process a wide variety of tasks during the request of a user. The best example of such a task are email notifications. Flarum Subscriptions, Friends of Flarum Follow Tags and IanM Follow Users are just a few extensions that trigger email notifications for new activity. It is probably not a mystery that having a community of ten users will not be much of an issue in this regard. However once you have thousands it is far more likely that these notifications will take a long time, and affect the interaction of users on your community."}),"\n",(0,t.jsx)(n.p,{children:"To resolve this increasing burden, you can run a Queue. A queue runs on your server, it does not interact with the user and their requests. A user request, however, can dispatch tasks to the queue."}),"\n",(0,t.jsxs)(n.p,{children:["By default, Flarum uses the ",(0,t.jsx)(n.code,{children:"sync"})," driver, which processes jobs immediately inline during the user's request \u2014 convenient, but it means the user waits for every job to complete before getting a response."]}),"\n",(0,t.jsx)(n.h2,{id:"how-do-i-set-up-a-queue",children:"How do I set up a queue?"}),"\n",(0,t.jsxs)(n.p,{children:["Since Flarum 2.x, the ",(0,t.jsx)(n.code,{children:"database"})," queue driver is built into Flarum core \u2014 no additional extension is required. To enable it, set the ",(0,t.jsx)(n.code,{children:"queue"})," driver to ",(0,t.jsx)(n.code,{children:"database"})," in your ",(0,t.jsx)(n.code,{children:"config.php"}),":"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-php",children:"'queue' => [\n    'driver' => 'database',\n],\n"})}),"\n",(0,t.jsxs)(n.p,{children:["The database queue re-uses the ",(0,t.jsx)(n.a,{href:"/scheduler",children:"scheduler"})," to process jobs, so you must have the scheduler configured to run ",(0,t.jsx)(n.strong,{children:"every minute"})," for it to work. See the ",(0,t.jsx)(n.a,{href:"/scheduler",children:"scheduler guide"})," for setup instructions."]}),"\n",(0,t.jsx)(n.h3,{id:"going-further-than-the-database-driver",children:"Going further than the database driver"}),"\n",(0,t.jsx)(n.p,{children:"For higher throughput than the database driver can sustain, two extensions build on top of Flarum's queue:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.a,{href:"https://github.com/FriendsOfFlarum/redis",children:"FoF Redis"})})," moves the queue (and, optionally, the cache, sessions and settings) onto a Redis-compatible server such as Redis or Valkey. Jobs are processed by the standard ",(0,t.jsx)(n.code,{children:"php flarum queue:work"})," worker, but against Redis instead of the database \u2014 faster, and it keeps this load off your database. Failed jobs are kept in Redis too."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:(0,t.jsx)(n.a,{href:"https://github.com/FriendsOfFlarum/horizon",children:"FoF Horizon"})})," builds on FoF Redis (it requires it) and adds ",(0,t.jsx)(n.a,{href:"https://laravel.com/docs/horizon",children:"Laravel Horizon"})," on top: supervised, auto-balancing worker processes in place of a single ",(0,t.jsx)(n.code,{children:"queue:work"}),", plus a full real-time dashboard showing throughput, wait times, job history, and per-queue metrics. It also enriches the admin queue widget with worker and status information. Choose Horizon when you're running at a scale where you want to tune worker pools per queue and watch the queue's health in detail."]}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:"Both integrate with the monitoring, failed-job, pausing and named-queue features described below."}),"\n",(0,t.jsx)(n.h2,{id:"monitoring-the-queue",children:"Monitoring the queue"}),"\n",(0,t.jsxs)(n.p,{children:["Whenever a real queue driver is active (anything other than ",(0,t.jsx)(n.code,{children:"sync"}),"), the admin dashboard shows a ",(0,t.jsx)(n.strong,{children:"queue widget"})," with an at-a-glance overview:"]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Pending"})," \u2014 jobs waiting to be processed (including jobs scheduled for later)."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Reserved"})," \u2014 jobs a worker has picked up and is currently running."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Failed"})," \u2014 jobs that exhausted their retries. When there are failures, this tile becomes a button that opens the failed-jobs view."]}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["The widget is not shown on the ",(0,t.jsx)(n.code,{children:"sync"})," driver, because there is no queue to monitor \u2014 jobs run inline."]}),"\n",(0,t.jsxs)(n.p,{children:["Queue-backed extensions can enrich this widget. ",(0,t.jsx)(n.a,{href:"https://github.com/FriendsOfFlarum/horizon",children:"FoF Horizon"}),", for example, adds worker-process, throughput and status tiles and links through to its full dashboard, all on the same card."]}),"\n",(0,t.jsx)(n.h2,{id:"failed-jobs",children:"Failed jobs"}),"\n",(0,t.jsxs)(n.p,{children:["A job that throws an exception is retried up to its configured number of attempts; once those are exhausted it is recorded as ",(0,t.jsx)(n.strong,{children:"failed"})," rather than lost. Failed jobs can be inspected and managed both from the admin dashboard and the command line."]}),"\n",(0,t.jsxs)(n.p,{children:["From the ",(0,t.jsx)(n.strong,{children:"Failed"})," tile in the dashboard widget you can:"]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"read each job's exception and details,"}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"retry"})," a single job (it is pushed back onto its queue),"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"delete"})," a single job,"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"retry all"})," failed jobs at once."]}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:"The same is available from the command line:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-sh",children:"php flarum queue:failed        # list failed jobs\nphp flarum queue:retry {id}    # retry one failed job (or `all`)\nphp flarum queue:forget {id}   # delete one failed job\nphp flarum queue:flush         # delete all failed jobs\n"})}),"\n",(0,t.jsxs)(n.p,{children:["Where failed jobs are stored depends on the driver: the built-in ",(0,t.jsx)(n.code,{children:"database"})," driver keeps them in the ",(0,t.jsx)(n.code,{children:"queue_failed_jobs"})," table, while other drivers store them in their own backend (FoF Redis, for instance, keeps them in Redis)."]}),"\n",(0,t.jsx)(n.h2,{id:"pausing-a-queue",children:"Pausing a queue"}),"\n",(0,t.jsx)(n.p,{children:"You can temporarily stop a queue from processing jobs without stopping the worker \u2014 useful during maintenance, a deploy, or when an extension is misbehaving. Paused jobs stay queued and resume processing when you unpause."}),"\n",(0,t.jsx)(n.h3,{id:"from-the-admin-panel",children:"From the admin panel"}),"\n",(0,t.jsxs)(n.p,{children:["The ",(0,t.jsx)(n.strong,{children:"Advanced"})," admin page has a queue-pause control. Toggling it pauses (and resumes) job processing for the forum."]}),"\n",(0,t.jsx)(n.h3,{id:"from-the-command-line",children:"From the command line"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-sh",children:"# Pause the default queue\nphp flarum queue:pause\n\n# Pause a specific queue (optionally prefixed with a connection)\nphp flarum queue:pause emails\nphp flarum queue:pause redis:emails\n\n# Pause every queue on the connection\nphp flarum queue:pause --all\n"})}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-sh",children:"# Resume ALL paused queues (bare command)\nphp flarum queue:resume\n\n# Resume a specific queue\nphp flarum queue:resume emails\n\n# Resume every queue on the connection\nphp flarum queue:resume --all\n"})}),"\n",(0,t.jsxs)(n.p,{children:["A bare ",(0,t.jsx)(n.code,{children:"queue:resume"})," (no queue name) resumes everything that is currently paused, including a connection-wide pause. To resume just one queue, name it."]}),"\n",(0,t.jsx)(n.admonition,{type:"info",children:(0,t.jsxs)(n.p,{children:["Workers evaluate whether a queue is paused using code loaded when they started. After enabling this on an existing install, run ",(0,t.jsx)(n.code,{children:"php flarum queue:restart"})," once so running workers pick up the change."]})}),"\n",(0,t.jsxs)(n.p,{children:["Pausing and resuming are recorded in the ",(0,t.jsx)(n.a,{href:"/extensions/audit",children:"audit log"})," (as ",(0,t.jsx)(n.code,{children:"queue.paused"})," / ",(0,t.jsx)(n.code,{children:"queue.resumed"}),", with the affected connection and queue) whenever the Audit extension is enabled, whether the action came from the admin panel or the command line."]}),"\n",(0,t.jsx)(n.h2,{id:"named-queues",children:"Named queues"}),"\n",(0,t.jsxs)(n.p,{children:["Jobs are not all equal \u2014 a time-sensitive notification should not sit behind a slow bulk export. Flarum (and the queue drivers) support ",(0,t.jsx)(n.strong,{children:"multiple named queues"})," so you can separate and prioritise work."]}),"\n",(0,t.jsxs)(n.p,{children:["Route a job class onto a named queue with the ",(0,t.jsx)(n.code,{children:"Queue"})," extender in your ",(0,t.jsx)(n.code,{children:"extend.php"}),":"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-php",children:"use Flarum\\Extend;\n\nreturn [\n    (new Extend\\Queue())\n        ->route(\\Your\\Extension\\Jobs\\SendExportJob::class, 'exports'),\n];\n"})}),"\n",(0,t.jsxs)(n.p,{children:["Every instance of that job class is then dis
1patched onto the ",(0,t.jsx)(n.code,{children:"exports"})," queue, without changing the code that dispatches it. You can route as many job classes as you like."]}),"\n",(0,t.jsx)(n.p,{children:"Routing a base or abstract class covers all of its subclasses, so a family of related jobs can be sent to one queue by routing their shared parent:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-php",children:"(new Extend\\Queue())\n    ->route(\\Your\\Extension\\Jobs\\AbstractExportJob::class, 'exports'),\n"})}),"\n",(0,t.jsx)(n.p,{children:"If a job matches more than one route through its class hierarchy, the most specific one wins \u2014 a route on the job's own class overrides one on a parent. An explicit queue passed when the job is dispatched always takes precedence over any route."}),"\n",(0,t.jsx)(n.p,{children:"You then run a worker across the queues you care about, in priority order \u2014 each queue is fully drained before the next:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-sh",children:"php flarum queue:work --queue=notifications,default,exports\n"})}),"\n",(0,t.jsxs)(n.p,{children:["Admin tooling \u2014 the dashboard widget and per-queue pausing \u2014 reads a registry of known queue names (",(0,t.jsx)(n.code,{children:"flarum.queue.queues"}),", which defaults to ",(0,t.jsx)(n.code,{children:"['default']"}),"). An extension that routes jobs to its own named queues appends them to that registry so they are covered. Refer to the extension's documentation (e.g. FoF Redis / FoF Horizon) for how to declare additional queues."]})]})}function l(e={}){const{wrapper:n}={...(0,i.R)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(h,{...e})}):h(e)}},8453(e,n,s){s.d(n,{R:()=>a,x:()=>o});var r=s(6540);const t={},i=r.createContext(t);function a(e){const n=r.useContext(i);return r.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:a(e.components),r.createElement(i.Provider,{value:n},e.children)}}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.