关于 pg-boss
pg-boss 是面向 Node.js 应用、以 PostgreSQL 为后端的任务队列库。本技能涵盖队列配置、定时任务、工作进程、可靠性实践和故障排查。
主要功能
- 01基于 PostgreSQL 持久化任务
- 02类 Cron 与时区感知调度
- 03支持并发的批量工作进程
- 04重试与重复任务防护
- 05监控和恢复指南
适用场景
- 01安排应用定期任务
- 02批量处理队列任务
- 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 */ |
最佳实践
- 定时作业始终使用 singletonKey,以防重启时产生重复作业
- 在 PG Boss v10+ 中显式创建队列:
await boss.createQueue('my-job') - 处理 'stopped' 事件,以检测意外关停
- 使用看门狗从崩溃中自动恢复(启动新看门狗前清除旧的定时器)
- 当 PG Boss 停止运行时,在健康检查中返回 503(触发容器重启)
- 记录作业错误,但不要吞掉错误——让 PG Boss 重试
- 为不同作业类型使用独立的工作进程(轮换、电子邮件等)
- 优雅关停:在 SIGTERM/SIGINT 时调用
boss.stop({ graceful: true, timeout: 30000 }) - 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