<?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: Harsh</title>
    <description>The latest articles on DEV Community by Harsh (@hashpod).</description>
    <link>https://dev.to/hashpod</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4015799%2Fe720ca41-c652-4332-b2ad-34d0452865df.png</url>
      <title>DEV Community: Harsh</title>
      <link>https://dev.to/hashpod</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hashpod"/>
    <language>en</language>
    <item>
      <title>A practical guide to monitoring BullMQ queues with an agent-based approach that keeps Redis credentials inside your infrastructure.</title>
      <dc:creator>Harsh</dc:creator>
      <pubDate>Sun, 05 Jul 2026 16:09:58 +0000</pubDate>
      <link>https://dev.to/hashpod/a-practical-guide-to-monitoring-bullmq-queues-with-an-agent-based-approach-that-keeps-redis-40de</link>
      <guid>https://dev.to/hashpod/a-practical-guide-to-monitoring-bullmq-queues-with-an-agent-based-approach-that-keeps-redis-40de</guid>
      <description>&lt;p&gt;devopscover_image: &lt;a href="https://qcanary.dev/screenshots/dashboard-overview.png" rel="noopener noreferrer"&gt;https://qcanary.dev/screenshots/dashboard-overview.png&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Background jobs are the invisible backbone of your Node.js app. When they fail, they fail silently. &lt;/p&gt;

&lt;p&gt;BullMQ is a great default for Node.js background jobs. It's fast, Redis-backed, and flexible enough for emails, reports, webhooks, and retry-heavy workflows. But once BullMQ is in production, a critical question shows up quickly:&lt;/p&gt;

&lt;p&gt;How do you monitor queues without giving another service direct access to Redis?&lt;/p&gt;

&lt;p&gt;Many hosted queue dashboards ask for your Redis URL. That works, but it comes with heavy security tradeoffs:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🔑 Credential exposure. Your production Redis URL gets stored by a third party.
🔥 Firewall changes. You have to open port 6379 or set up VPC peering—just for a dashboard.
📋 Compliance surface. SOC 2 and zero-trust policies require a vendor risk assessment for that access.
🗄️ Full data access. Redis has no row-level security. Once someone has the URL, they have everything.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This post walks through an alternative: using BullMQ's built-in QueueEvents emitter to monitor queues without ever sharing a Redis credential.&lt;br&gt;
The Problem With Direct Redis Monitoring&lt;/p&gt;

&lt;p&gt;A traditional hosted queue monitor connects directly to Redis and inspects queue state. That's convenient for building dashboards, but uncomfortable for production systems.&lt;/p&gt;

&lt;p&gt;Depending on your Redis permissions and deployment model, a Redis connection can expose far more than queue metrics — keys, job state, retry metadata, delayed jobs, and sometimes payload data. Even when a vendor is trustworthy, the credential becomes another secret to audit, rotate, and protect.&lt;/p&gt;

&lt;p&gt;The better model is simple:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Keep Redis private. Send only the monitoring events you actually need.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;How BullMQ QueueEvents Changes the Game&lt;/p&gt;

&lt;p&gt;BullMQ emits lifecycle events as jobs move through a queue. QueueEvents is the BullMQ primitive for subscribing to those changes inside your own process:&lt;/p&gt;

&lt;p&gt;completed → failed → active → waiting → stalled → delayed → drained&lt;/p&gt;

&lt;p&gt;Instead of polling Redis from a hosted service, you can subscribe from inside your runtime and translate these lifecycle events into monitoring records.&lt;/p&gt;

&lt;p&gt;This eliminates:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; ✅ Inbound network access to Redis
 ✅ Redis credential sharing with vendors
 ✅ The need to inspect arbitrary Redis keys
 ✅ Firewall changes and VPC peering
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Monitoring data is buffered locally and sent over standard outbound HTTPS.&lt;br&gt;
The Agent Pattern in Practice&lt;/p&gt;

&lt;p&gt;Here's how it works in a real application. First, install the agent:&lt;br&gt;
bash&lt;/p&gt;

&lt;p&gt;npm install @qcanary/agent&lt;/p&gt;

&lt;p&gt;Then attach it to your existing BullMQ queues:&lt;br&gt;
typescript&lt;/p&gt;

&lt;p&gt;import { Queue } from "bullmq";&lt;br&gt;
import { QueueMonitor } from "@qcanary/agent";&lt;/p&gt;

&lt;p&gt;const emailQueue = new Queue("email", {&lt;br&gt;
  connection: { host: "127.0.0.1", port: 6379 },&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;const monitor = new QueueMonitor({&lt;br&gt;
  apiKey: process.env.QCANARY_API_KEY,&lt;br&gt;
  queues: [emailQueue],&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;await monitor.start();&lt;/p&gt;

&lt;p&gt;The agent does a few things under the hood:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Attaches to QueueEvents as a local subscriber — your Redis connection never leaves the process.
Buffers events into batches (100 events or 5 seconds, whichever comes first).
Ships only lightweight metadata over HTTPS: job ID, queue name, status, duration, error message.
Retries with exponential backoff on failure.
Drops oldest events if the buffer fills — monitoring should never slow down your workers.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;No job payloads. No Redis keys. No connection strings. Just events.&lt;br&gt;
Setting Up Your First Alert&lt;/p&gt;

&lt;p&gt;Dashboards are useful. Alerts are what make monitoring operational.&lt;/p&gt;

&lt;p&gt;A good first alert is failure rate:&lt;br&gt;
text&lt;/p&gt;

&lt;p&gt;Queue: email&lt;br&gt;
Condition: failure rate &amp;gt; 5%&lt;br&gt;
Window: 10 minutes&lt;br&gt;
Destination: Slack&lt;/p&gt;

&lt;p&gt;One failed job may be noise. A sustained 5% failure rate over 10 minutes usually means something changed: a provider outage, expired credentials, a bad deploy, or a slow dependency.&lt;/p&gt;

&lt;p&gt;Other useful alerts to configure:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; No activity — catch queues that silently stopped receiving work.
 Queue depth — catch growing backlogs before users feel the delay.
 Job duration — catch slow jobs and external dependency regressions.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Comparing the Approaches&lt;br&gt;
Aspect&lt;/p&gt;

&lt;p&gt;Sharing Redis URL&lt;/p&gt;

&lt;p&gt;Agent-Based (QueueEvents)&lt;br&gt;
Credential exposure Redis URL stored by vendor  None — Redis stays local&lt;br&gt;
Network changes Open port 6379 or VPC peering   None — outbound HTTPS only&lt;br&gt;
Compliance impact   Vendor risk assessment required None — no shared infrastructure&lt;br&gt;
Data sent   Direct Redis access (everything)    Only lifecycle metadata&lt;br&gt;
Payload visibility  Vendor can read all job data    Only metadata — payloads stay local&lt;br&gt;
Setup time  10 minutes + network config 5 minutes, no network config&lt;/p&gt;

&lt;p&gt;What to Monitor First&lt;/p&gt;

&lt;p&gt;Start with high-impact queues:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; Email delivery
 Billing webhooks
 User notifications
 Scheduled reports
 Data imports and exports
 Third-party sync jobs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Keep thresholds conservative at first. After a week of history, tune them based on normal production behavior.&lt;br&gt;
What's Next for QCanary? (Expanding the Ecosystem)&lt;/p&gt;

&lt;p&gt;Right now, QCanary is built specifically for BullMQ because of its clean event model. But we know the Node.js ecosystem is diverse.&lt;/p&gt;

&lt;p&gt;We are actively expanding our agent to support the original Bull library, so legacy Node.js applications can secure their monitoring without migrating their queue infrastructure. Furthermore, we are laying the groundwork to support queue systems in other languages, bringing this same zero-trust, agent-based observability to Python, Go, and beyond. &lt;/p&gt;

&lt;p&gt;If you're using Bull or another queue system, we'd love to hear from you!&lt;br&gt;
When Sharing Redis Still Makes Sense&lt;/p&gt;

&lt;p&gt;The agent pattern isn't always the right call:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; Local development — credential sharing doesn't matter on localhost.
 Full payload inspection needed — if you need to see job data in the dashboard, the agent can't help (it intentionally excludes payloads).
 Existing VPC peering — if the network boundary is already established, sharing Redis adds no new attack surface.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;For everyone else — production deployments with VPCs, compliance requirements, or zero-trust policies — the agent pattern eliminates an entire class of security risk.&lt;br&gt;
Try It Free&lt;/p&gt;

&lt;p&gt;QCanary provides real-time dashboards, Slack/email/webhook alerts, and job history for BullMQ — all without ever seeing your Redis URL.&lt;/p&gt;

&lt;p&gt;Free tier: 1 project, 3 queues, 10K events/day, 3-day history.&lt;br&gt;
Setup: Install the agent, attach to your queues, events start flowing.&lt;/p&gt;

&lt;p&gt;→ qcanary.dev&lt;br&gt;
→ qcanary.dev/docs&lt;/p&gt;

&lt;p&gt;Found this useful? Follow me for more Node.js infrastructure content. Questions? Drop them in the comments below.&lt;/p&gt;

</description>
      <category>node</category>
      <category>bullmq</category>
      <category>redis</category>
    </item>
  </channel>
</rss>
