{"componentChunkName":"component---src-gatsby-theme-chronoblog-templates-note-js","path":"/notes/bullmq-cronjobs-nodejs/","result":{"data":{"mdx":{"parent":{"__typename":"File","fields":{"gitLogLatestDate":"2026-07-04 21:01:48 +0200"}},"id":"de6492fb-1b50-51c0-a608-7c16ebdafde5","excerpt":"In-process cron ( node-cron ,  @nestjs/schedule , OS crontab) runs inside one Node process. That is fine for a single instance, but it does…","frontmatter":{"title":"Cron jobs and schedulers with BullMQ","date":"2026-07-04 18:45:00 UTC","job_ad":null,"job_ad_id":null,"job_ad_url":null,"tags":["node","bullmq","redis","cron","nestjs","queues"],"cover":{"childImageSharp":{"fluid":{"base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAPoAAAD6AG1e1JrAAAAjElEQVQoz9WRSw7CMAwFe/+btqGlcZyPm5BBIJAKGzbZ8CQvRho92fLE4EzDC3vvjJzxG4qvBGlYaZgVzAwNlVLqiyufzoGGhkglp0atB1Eb3j/8zjTPysVlohre74gI25oQiXjZCSHhloRziaiFEJRtLSxzRMSIUdmv9uScb//wlDO8c+ZfBd/O8JPvsX8Xkqofn3AAAAAASUVORK5CYII=","aspectRatio":2.0869565217391304,"src":"/static/c73bb6e15bcf737fc34d44f01809d7d4/c4ecb/cover.png","srcSet":"/static/c73bb6e15bcf737fc34d44f01809d7d4/57ab0/cover.png 192w,\n/static/c73bb6e15bcf737fc34d44f01809d7d4/f4739/cover.png 384w,\n/static/c73bb6e15bcf737fc34d44f01809d7d4/c4ecb/cover.png 768w","srcWebp":"/static/c73bb6e15bcf737fc34d44f01809d7d4/dd090/cover.webp","srcSetWebp":"/static/c73bb6e15bcf737fc34d44f01809d7d4/ae504/cover.webp 192w,\n/static/c73bb6e15bcf737fc34d44f01809d7d4/fef30/cover.webp 384w,\n/static/c73bb6e15bcf737fc34d44f01809d7d4/dd090/cover.webp 768w","sizes":"(max-width: 768px) 100vw, 768px","presentationWidth":768,"presentationHeight":366},"resize":{"src":"/static/c73bb6e15bcf737fc34d44f01809d7d4/c4ecb/cover.png"}}}},"fields":{"slug":"/notes/bullmq-cronjobs-nodejs/","readingTime":{"text":"6 min read"}},"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {\n  \"title\": \"Cron jobs and schedulers with BullMQ\",\n  \"date\": \"2026-07-04 18:45:00 UTC\",\n  \"cover\": \"./cover.png\",\n  \"tags\": [\"node\", \"bullmq\", \"redis\", \"cron\", \"nestjs\", \"queues\"],\n  \"canonical_url\": \"https://sevic.dev/notes/bullmq-cronjobs-nodejs/\"\n};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"In-process cron (\", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"node-cron\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"@nestjs/schedule\"), \", OS crontab) runs inside one Node process. That is fine for a single instance, but it does not survive restarts gracefully, deduplicate across replicas, or share infrastructure with your other background jobs.\"), mdx(\"p\", null, mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://docs.bullmq.io/\"\n  }), \"BullMQ\"), \" stores queues and schedulers in \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Redis\"), \". \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Job Schedulers\"), \" (BullMQ 5.16+) are the recommended way to enqueue recurring work on a cron pattern or fixed interval. The same workers that process one-off jobs also process scheduled ones, with retries, backoff, and concurrency you already get from BullMQ.\"), mdx(\"p\", null, \"This post covers Job Schedulers in plain Node.js, operations and pitfalls, a NestJS setup with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"@nestjs/bullmq\"), \", and a runnable demo with a fast cron heartbeat and a daily cleanup cron.\"), mdx(\"h3\", {\n    \"id\": \"prerequisites\"\n  }, \"Prerequisites\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Node.js version 26\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Redis at \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"redis://localhost:6379\"), \" (included in the demo \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"docker-compose.yml\"), \", or use \", mdx(\"a\", _extends({\n    parentName: \"li\"\n  }, {\n    \"href\": \"https://sevic.dev/notes/postgres-redis-docker-compose/\"\n  }), \"Postgres and Redis containers with Docker Compose\"), \")\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"npm i bullmq\"))), mdx(\"p\", null, \"For the NestJS section: \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"npm i @nestjs/bullmq bullmq\")), mdx(\"p\", null, \"BullMQ 2.0+ does \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"not\"), \" require a separate \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"QueueScheduler\"), \" instance. Use the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Job Scheduler\"), \" API (\", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"upsertJobScheduler\"), \"), not the deprecated \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"repeat\"), \" option on \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"queue.add()\"), \".\"), mdx(\"h3\", {\n    \"id\": \"mental-model\"\n  }, \"Mental model\"), mdx(\"table\", null, mdx(\"thead\", {\n    parentName: \"table\"\n  }, mdx(\"tr\", {\n    parentName: \"thead\"\n  }, mdx(\"th\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), \"Piece\"), mdx(\"th\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), \"Role\"))), mdx(\"tbody\", {\n    parentName: \"table\"\n  }, mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), mdx(\"strong\", {\n    parentName: \"td\"\n  }, \"Queue\")), mdx(\"td\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), \"Holds jobs waiting to run\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), mdx(\"strong\", {\n    parentName: \"td\"\n  }, \"Worker\")), mdx(\"td\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), \"Executes jobs\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), mdx(\"strong\", {\n    parentName: \"td\"\n  }, \"Job Scheduler\")), mdx(\"td\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), \"Factory that enqueues jobs on a schedule\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), mdx(\"strong\", {\n    parentName: \"td\"\n  }, \"Scheduled job\")), mdx(\"td\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), \"A job instance produced by a scheduler\")))), mdx(\"p\", null, \"A scheduler id is stable across deploys. Calling \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"upsertJobScheduler\"), \" with the same id updates the schedule in place instead of creating duplicates.\"), mdx(\"h3\", {\n    \"id\": \"queue-and-worker\"\n  }, \"Queue and worker\"), mdx(\"p\", null, \"Share one Redis connection config between the queue and the worker:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"import { Queue, Worker } from 'bullmq';\\n\\nconst connection = { host: 'localhost', port: 6379 };\\n\\nconst queue = new Queue('reports', { connection });\\n\\nconst worker = new Worker(\\n  'reports',\\n  async (job) => {\\n    console.log(`[${job.name}]`, new Date().toISOString(), job.data);\\n  },\\n  { connection },\\n);\\n\\nworker.on('failed', (job, error) => {\\n  console.error(job?.name, error.message);\\n});\\n\")), mdx(\"p\", null, \"Start the worker before or shortly after registering schedulers. If no worker is running, jobs accumulate in Redis until one picks them up.\"), mdx(\"h3\", {\n    \"id\": \"cron-schedulers\"\n  }, \"Cron schedulers\"), mdx(\"p\", null, \"BullMQ uses a \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"6-field\"), \" cron expression (optional seconds). A fast pattern for demos and heartbeats:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"await queue.upsertJobScheduler(\\n  'report-heartbeat',\\n  { pattern: '*/10 * * * * *' },\\n  {\\n    name: 'heartbeat',\\n    data: { source: 'scheduler' },\\n    opts: {\\n      attempts: 3,\\n      backoff: { type: 'exponential', delay: 1000 },\\n      removeOnComplete: 50,\\n    },\\n  },\\n);\\n\")), mdx(\"p\", null, \"Daily cleanup at 03:15 in a specific timezone:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"await queue.upsertJobScheduler(\\n  'daily-cleanup',\\n  { pattern: '0 15 3 * * *', tz: 'Europe/Berlin' },\\n  {\\n    name: 'cleanup',\\n    data: { scope: 'stale-sessions' },\\n    opts: { attempts: 3 },\\n  },\\n);\\n\")), mdx(\"p\", null, \"Set \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"tz\"), \" when the job must fire at a local wall-clock time. For millisecond intervals instead of cron, use \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"every\"), \" (mutually exclusive with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"pattern\"), \").\"), mdx(\"p\", null, \"Other useful repeat options:\"), mdx(\"table\", null, mdx(\"thead\", {\n    parentName: \"table\"\n  }, mdx(\"tr\", {\n    parentName: \"thead\"\n  }, mdx(\"th\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), \"Option\"), mdx(\"th\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), \"Purpose\"))), mdx(\"tbody\", {\n    parentName: \"table\"\n  }, mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), mdx(\"inlineCode\", {\n    parentName: \"td\"\n  }, \"limit\")), mdx(\"td\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), \"Maximum number of iterations\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), mdx(\"inlineCode\", {\n    parentName: \"td\"\n  }, \"immediately\")), mdx(\"td\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), \"Run once now, then follow the schedule\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), mdx(\"inlineCode\", {\n    parentName: \"td\"\n  }, \"startDate\"), \" / \", mdx(\"inlineCode\", {\n    parentName: \"td\"\n  }, \"endDate\")), mdx(\"td\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), \"Bound the scheduler to a time window\")))), mdx(\"h3\", {\n    \"id\": \"register-schedulers-on-startup\"\n  }, \"Register schedulers on startup\"), mdx(\"p\", null, \"Keep scheduler registration in a dedicated bootstrap script or \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"onModuleInit\"), \" hook so deploys upsert the same ids:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"// scheduler.js\\nimport { Queue } from 'bullmq';\\n\\nconst connection = { host: 'localhost', port: 6379 };\\nconst queue = new Queue('reports', { connection });\\n\\nawait queue.upsertJobScheduler(\\n  'report-heartbeat',\\n  { pattern: '*/10 * * * * *' },\\n  { name: 'heartbeat', data: { source: 'scheduler' } },\\n);\\n\\nawait queue.upsertJobScheduler(\\n  'daily-cleanup',\\n  { pattern: '0 15 3 * * *', tz: 'Europe/Berlin' },\\n  { name: 'cleanup', data: { scope: 'stale-sessions' } },\\n);\\n\\nconst schedulers = await queue.getJobSchedulers();\\nconsole.log(\\n  'Active schedulers:',\\n  schedulers.map((item) => ({ key: item.key, pattern: item.pattern })),\\n);\\n\\nawait queue.close();\\n\")), mdx(\"p\", null, \"To remove a scheduler:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"await queue.removeJobScheduler('daily-cleanup');\\n\")), mdx(\"p\", null, \"Shut down cleanly on \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SIGINT\"), \" / \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"SIGTERM\"), \": \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"await worker.close()\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"await queue.close()\"), \".\"), mdx(\"h3\", {\n    \"id\": \"nestjs-with-nestjsbullmq\"\n  }, \"NestJS with \", mdx(\"inlineCode\", {\n    parentName: \"h3\"\n  }, \"@nestjs/bullmq\")), mdx(\"p\", null, \"NestJS wraps BullMQ queues and workers as providers. Register Redis once, register the queue, inject it into a service that upserts schedulers on startup, and process jobs in a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"@Processor\"), \" class.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-ts\"\n  }), \"// app.module.ts\\nimport { Module } from '@nestjs/common';\\nimport { BullModule } from '@nestjs/bullmq';\\nimport { ReportsProcessor } from './reports.processor';\\nimport { ReportsSchedulerService } from './reports-scheduler.service';\\n\\n@Module({\\n  imports: [\\n    BullModule.forRoot({\\n      connection: { host: 'localhost', port: 6379 },\\n    }),\\n    BullModule.registerQueue({ name: 'reports' }),\\n  ],\\n  providers: [ReportsProcessor, ReportsSchedulerService],\\n})\\nexport class AppModule {}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-ts\"\n  }), \"// reports-scheduler.service.ts\\nimport { Injectable, OnModuleInit } from '@nestjs/common';\\nimport { InjectQueue } from '@nestjs/bullmq';\\nimport { Queue } from 'bullmq';\\n\\n@Injectable()\\nexport class ReportsSchedulerService implements OnModuleInit {\\n  constructor(@InjectQueue('reports') private readonly reportsQueue: Queue) {}\\n\\n  async onModuleInit() {\\n    await this.reportsQueue.upsertJobScheduler(\\n      'report-heartbeat',\\n      { pattern: '*/10 * * * * *' },\\n      { name: 'heartbeat', data: { source: 'nestjs' } },\\n    );\\n\\n    await this.reportsQueue.upsertJobScheduler(\\n      'daily-cleanup',\\n      { pattern: '0 15 3 * * *', tz: 'Europe/Berlin' },\\n      { name: 'cleanup', data: { scope: 'stale-sessions' } },\\n    );\\n  }\\n}\\n\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-ts\"\n  }), \"// reports.processor.ts\\nimport { Processor, WorkerHost } from '@nestjs/bullmq';\\nimport { Job } from 'bullmq';\\n\\n@Processor('reports')\\nexport class ReportsProcessor extends WorkerHost {\\n  async process(job: Job): Promise<void> {\\n    console.log(`[${job.name}]`, new Date().toISOString(), job.data);\\n  }\\n}\\n\")), mdx(\"p\", null, mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ReportsSchedulerService\"), \" runs when the Nest app boots, so schedulers are upserted on every deploy. \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ReportsProcessor\"), \" is the worker; Nest registers it automatically unless you set \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"manualRegistration\"), \" on \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"BullModule.forRoot\"), \".\"), mdx(\"p\", null, mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"@nestjs/schedule\"), \" (\", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"@Cron()\"), \") is still a good fit for trivial timers inside one instance. Prefer BullMQ schedulers when you already use Redis queues, run multiple replicas, or need the same retry and observability model as the rest of your background jobs.\"), mdx(\"h3\", {\n    \"id\": \"pitfalls\"\n  }, \"Pitfalls\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Worker must be running\"), \" - schedulers enqueue jobs; something must consume them.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Busy queues slip\"), \" - BullMQ creates the next scheduled job when the previous one \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"starts\"), \" processing. Under load, ticks can be less frequent than \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"every\"), \" or the cron interval suggests.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"inlineCode\", {\n    parentName: \"strong\"\n  }, \"pattern\"), \" vs \", mdx(\"inlineCode\", {\n    parentName: \"strong\"\n  }, \"every\")), \" - mutually exclusive; pick one per scheduler.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Timezone\"), \" - omit \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"tz\"), \" and cron runs in the server default zone; set it explicitly for \\\"9 AM local\\\" jobs.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Legacy \", mdx(\"inlineCode\", {\n    parentName: \"strong\"\n  }, \"repeat\"), \" on \", mdx(\"inlineCode\", {\n    parentName: \"strong\"\n  }, \"add()\")), \" - deprecated from BullMQ 5.16; use \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"upsertJobScheduler\"), \" for new code.\")), mdx(\"h3\", {\n    \"id\": \"when-to-use-what\"\n  }, \"When to use what\"), mdx(\"table\", null, mdx(\"thead\", {\n    parentName: \"table\"\n  }, mdx(\"tr\", {\n    parentName: \"thead\"\n  }, mdx(\"th\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), \"Approach\"), mdx(\"th\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), \"Good for\"))), mdx(\"tbody\", {\n    parentName: \"table\"\n  }, mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), mdx(\"inlineCode\", {\n    parentName: \"td\"\n  }, \"@nestjs/schedule\"), \" / \", mdx(\"inlineCode\", {\n    parentName: \"td\"\n  }, \"node-cron\")), mdx(\"td\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), \"Single instance, simple in-process timers\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), mdx(\"strong\", {\n    parentName: \"td\"\n  }, \"BullMQ Job Schedulers\")), mdx(\"td\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), \"Multi-instance apps, shared Redis, retries with async jobs\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), \"External cron + HTTP\"), mdx(\"td\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), \"Fire-and-forget HTTP triggers without queue semantics\")))));\n}\n;\nMDXContent.isMDXComponent = true;"}},"pageContext":{"id":"de6492fb-1b50-51c0-a608-7c16ebdafde5"}},"staticQueryHashes":["1961101537","2542493696"]}