mcpField
P
pg-boss

pg-boss

github.com/blink-new开发84 次安装
代码仓库

关于 pg-boss

pg-boss 是面向 Node.js 应用、以 PostgreSQL 为后端的任务队列库。本技能涵盖队列配置、定时任务、工作进程、可靠性实践和故障排查。

主要功能

  1. 01基于 PostgreSQL 持久化任务
  2. 02类 Cron 与时区感知调度
  3. 03支持并发的批量工作进程
  4. 04重试与重复任务防护
  5. 05监控和恢复指南

适用场景

  1. 01安排应用定期任务
  2. 02批量处理队列任务
  3. 03意外停止后恢复工作进程

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/blink-new/claude --skill pg-boss

安装后在 Claude Code 中输入/pg-boss确认已加载。

详细介绍

PG Boss 作业队列

PG Boss 是一个基于 PostgreSQL 的 Node.js 作业队列。它会将作业存储在数据库中,以提供持久性和可靠性。

快速设置

import { PgBoss } from 'pg-boss';

const boss = new PgBoss({
  connectionString: process.env.DATABASE_URL,
  schema: 'pgboss',
  monitorIntervalSeconds: 30,
});

await boss.start();

常见模式

1. 定时作业(类似 Cron)
// v12: queue must exist before schedule/work
await boss.createQueue('my-job', {
  retryLimit: 3,
  retryDelay: 60,
  expireInSeconds: 600,
  deleteAfterSeconds: 7 * 24 * 60 * 60,
});

// Schedule job to run every minute
await boss.schedule('my-job', '* * * * *', {}, {
  tz: 'UTC',
  singletonKey: 'my-job-schedule',  // Prevents duplicates on restart
});

// Register handler
await boss.work('my-job', { pollingIntervalSeconds: 10 }, async (jobs) => {
  for (const job of jobs) {
    // Process job
  }
});
2. 作业入队
// Queue a single job
await boss.send('process-user', { userId: '123' });

// Queue with options
await boss.send('send-email', { to: '[email protected]' }, {
  retryLimit: 5,
  expireInSeconds: 300,
});
3. 并发批处理
await boss.work(
  'batch-job',
  { batchSize: 10 },  // Process 10 jobs at once
  async (jobs) => {
    await Promise.all(jobs.map(job => processJob(job)));
  }
);

可靠性模式

单例模式(防止重复)
// Use getPgBoss() singleton instead of creating new instances
let boss: PgBoss | null = null;

export async function getPgBoss(): Promise<PgBoss> {
  if (boss) return boss;
  
  boss = new PgBoss({ connectionString: process.env.DATABASE_URL });
  await boss.start();
  return boss;
}
用于自动恢复的看门狗

PG Boss 可能会意外停止(连接断开、崩溃)。添加一个看门狗:

let boss: PgBoss | null = null;
let watchdogInterval: NodeJS.Timeout | null = null;

// Listen for unexpected stops
boss.on('stopped', () => {
  console.error('[PG Boss] Stopped unexpectedly');
  boss = null;
  // Watchdog will attempt recovery
});

// Watchdog checks every 60s
watchdogInterval = setInterval(async () => {
  if (!boss && workerRegistrationFn) {
    console.log('[PG Boss] Attempting recovery...');
    await workerRegistrationFn();
  }
}, 60000);
健康检查集成

当 PG Boss 停止运行时,让健康检查失败(触发容器重启):

app.get('/health', (c) => {
  const pgBossRunning = boss !== null;
  
  if (!pgBossRunning) {
    return c.json({ status: 'degraded', pgBossRunning: false }, 503);
  }
  
  return c.json({ status: 'ok', pgBossRunning: true });
});

调试

在数据库中检查 PG Boss 状态
-- Check scheduled jobs
SELECT name, cron FROM pgboss.schedule;

-- Check recent jobs
SELECT name, state, created_on, completed_on 
FROM pgboss.job 
ORDER BY created_on DESC 
LIMIT 20;

-- Check job queue size
SELECT name, COUNT(*) 
FROM pgboss.job 
WHERE state = 'created' 
GROUP BY name;
通过 Node.js
const boss = await getPgBoss();

// Get queue size
const pending = await boss.getQueueSize('my-job');

// Get job by ID
const job = await boss.getJobById(jobId);
Railway 专项调试
# Check environment variables
railway variables --service api

# Get public DATABASE_URL for direct access
railway variables --service Postgres | grep DATABASE_PUBLIC_URL

# Query database directly
DATABASE_URL="postgresql://..." node -e "
const { Pool } = require('pg');
const pool = new Pool({ connectionString: process.env.DATABASE_URL, ssl: { rejectUnauthorized: false } });
// Run queries
"

常见问题

问题原因修复方法
作业未运行PG Boss 未启动检查健康检查端点,确认 DATABASE_URL
作业重复缺少 singletonKey在 schedule() 调用中添加 singletonKey
找不到队列在 createQueue() 之前调用了 schedule()先调用 boss.createQueue(name)
作业卡在“created”状态未注册工作进程为该队列调用 boss.work()
连接错误数据库 URL 错误或已过期检查 DATABASE_URL 和 SSL 设置
作业未持久化架构错误确认 schema: 'pgboss' 选项
工作进程轮询过于频繁选项名称错误使用 pollingIntervalSeconds(而非 newJobCheckIntervalSeconds)
Next.js 构建失败(stream/net/tls)instrumentation 打包了仅限服务端的依赖项在 instrumentation 动态导入中使用 /* webpackIgnore: true */

最佳实践

  1. 定时作业始终使用 singletonKey,以防重启时产生重复作业
  2. 在 PG Boss v10+ 中显式创建队列:await boss.createQueue('my-job')
  3. 处理 'stopped' 事件,以检测意外关停
  4. 使用看门狗从崩溃中自动恢复(启动新看门狗前清除旧的定时器)
  5. 当 PG Boss 停止运行时,在健康检查中返回 503(触发容器重启)
  6. 记录作业错误,但不要吞掉错误——让 PG Boss 重试
  7. 为不同作业类型使用独立的工作进程(轮换、电子邮件等)
  8. 优雅关停:在 SIGTERM/SIGINT 时调用 boss.stop({ graceful: true, timeout: 30000 })
  9. Next.js instrumentation:使用 import(/* webpackIgnore: true */ ...) 加载仅限服务端的作业模块

文件结构示例

apps/api/src/
├── lib/
│   └── pgboss.ts          # Singleton, getPgBoss(), stopPgBoss()
├── workers/
│   ├── index.ts           # registerAllWorkers()
│   ├── rollover/
│   │   ├── index.ts       # registerRolloverWorkers()
│   │   ├── timezone-check.ts
│   │   └── batch-processor.ts
│   └── email/
│       ├── index.ts       # registerEmailWorkers()
│       └── send-email.ts
└── index.ts               # Calls registerAllWorkers() on startup

时区感知型调度

对于需要在特定本地时间运行的作业(例如午夜轮换):

import { toZonedTime } from 'date-fns-tz';

// Check if it's midnight in a timezone
const zonedNow = toZonedTime(new Date(), userTimezone);
const hour = zonedNow.getHours();
const minute = zonedNow.getMinutes();

// 10-minute window for reliability
const isMidnightWindow = hour === 0 && minute <= 10;

幂等性

使用日志表防止重复处理:

// Check if already processed
const existing = await db.query.logs.findFirst({
  where: and(
    eq(logs.timezone, timezone),
    eq(logs.date, targetDate)
  ),
});

if (existing) return; // Already processed

// Process and log
await processJobs();
await db.insert(logs).values({ timezone, date: targetDate, status: 'completed' });

评论

支持 Markdown