شرح pg-boss: طابور مهام Postgres في Node 24 (2026)
١٥ مايو ٢٠٢٦
لبناء طابور مهام (job queue) بمستوى الإنتاج في Postgres باستخدام Node.js، قم بتثبيت pg-boss@12.18.2 مقابل قاعدة بيانات Postgres 18، واستدعِ boss.createQueue() لكل طابور تحتاجه مع ضبط retryLimit و retryBackoff و deadLetter، ثم سجل معالجات مكتوبة (typed handlers) باستخدام boss.work(queue, ([job]) => ...). يستخدم pg-boss خاصية Postgres المسماة SELECT ... FOR UPDATE SKIP LOCKED لعملية سحب المهام (dequeue) الذرية، بحيث تلتقط العمال (workers) عبر عمليات Node المتعددة المهام دون تعارض — لا حاجة لـ Redis.12
سؤال "هل يجب أن أضيف Redis فقط من أجل المهام؟" يظهر في كل خلفية برمجية (backend) لـ Node.js تتجاوز بضعة آلاف طلب في الثانية (RPS). يجيب pg-boss على ذلك بالاعتماد على Postgres الذي تشغله بالفعل: نفس النسخ الاحتياطية، نفس المراقبة، ونفس مديري قواعد البيانات.1 إنه ليس بديلاً مباشراً لـ BullMQ — فسقف أداء SKIP LOCKED يقع بوضوح تحت ما يمكن أن تتحمله الطوابير المدعومة بـ Redis — ولكن بالنسبة لـ 90% من الفرق التي تشغل عشرات أو مئات المهام في الثانية مع دلالات سير عمل غنية (إعادة المحاولة مع التذبذب "jitter"، الجدولة الزمنية "cron"، طوابير الرسائل المهملة "dead-letter queues"، النشر/الاشتراك "pub/sub")، فإنه يحذف جزءاً متحركاً كاملاً من بيئة الإنتاج.
يستعرض هذا البرنامج التعليمي إعداداً كاملاً لـ TypeScript مقابل الإصدار الحالي pg-boss@12.18.2 (أحدث إصدار على npm بتاريخ 11-05-2026)3 و Node 24 LTS. تم تثبيت إصدار كل تبعية (dependency). كل كتلة برمجية قابلة للتشغيل كما هي مكتوبة.
ملخص
ستقوم بإنشاء مشروع TypeScript مقابل Node 24 LTS، وتوجيهه إلى حاوية Postgres 18، وإنشاء طابورين مكتوبين (send-email و send-email-dlq) مع إعادات محاولة بتراجع أسي (exponential-backoff)، ونقل المهام الفاشلة إلى طابور الرسائل المهملة، وجدولة مهمة cron يومية بتوقيت Europe/Dublin، وتثبيت لوحة التحكم الرسمية @pg-boss/dashboard@1.1.3 للمراقبة، والإغلاق النظيف عند إشارة SIGTERM. وقت التشغيل من البداية للنهاية: حوالي 25 دقيقة. تم التحقق في 15 مايو 2026 مقابل pg-boss 12.18.2، و dashboard 1.1.3، و Postgres 18.3.345
ما ستتعلمه
- كيف يستخدم pg-boss خاصية
SKIP LOCKEDلتقديم المهام مرة واحدة بالضبط بدون Redis - كيفية إعداد pg-boss 12 مقابل Postgres 18 في Docker مع TypeScript
- كيفية تعريف حمولات الطابور الآمنة من حيث النوع (type-safe) باستخدام generics في
send()وwork() - كيفية تكوين إعادات المحاولة مع تراجع أسي متذبذب وطابور رسائل مهملة
- كيفية جدولة مهام cron بمنطقة زمنية محددة من IANA
- كيفية تثبيت حزمة
@pg-boss/dashboardللمراقبة - كيفية التعامل مع الإغلاق السلس عند إشارة
SIGTERMحتى لا تضيع المهام قيد التنفيذ - متى تختار pg-boss مقابل البدائل المدعومة بـ Redis مثل BullMQ
المتطلبات الأساسية
- Node.js 24.15.0 أو أحدث (يتطلب pg-boss 12 إصدار
node >=22.12.0)3 - محرك Docker 20.10+ مع Compose v2 (يأتي Docker Desktop 3.4+ به تلقائياً)
- معرفة بـ PostgreSQL على مستوى
psqlوسلاسل الاتصال - منفذ TCP شاغر على
localhost:5432لحاوية Postgres
ثبّت إصدار Node 24 تحديداً — فهو خط الدعم طويل الأمد (LTS) النشط حتى أكتوبر 2026، مع دعم أمني حتى أبريل 2028.6 لا يزال Node 22 يعمل (يحتاج pg-boss فقط إلى
>=22.12.0) ولكنه الآن في مرحلة الصيانة (Maintenance LTS).6
الخطوة 1: إنشاء هيكل المشروع
أنشئ المجلد وثبّت كل تبعية. لا توجد أي من الإصدارات أدناه هي "الأحدث" (latest) — بل هي العلامات الدقيقة المنشورة على npm اعتباراً من يوم كتابة هذا البرنامج التعليمي (تم التحقق باستخدام npm view).
mkdir pg-boss-tutorial && cd pg-boss-tutorial
npm init -y
npm pkg set type=module
npm install pg-boss@12.18.2 pg@8.20.0 dotenv@17.4.2
npm install -D typescript@6.0.3 tsx@4.22.0 \
@types/node@22.19.17 @types/pg@8.20.0
سطر type=module غير قابل للتفاوض: يتم شحن pg-boss 12 كحزمة ESM نقية ("type": "module" في ملف package.json الخاص بها).3 تحميلها من مشروع CommonJS يتطلب آلية require(esm) في Node، ولهذا السبب رفع pg-boss متطلبات المحركات إلى >=22.12.0 (عندما أصبح require(esm) متاحاً بدون علامات تجريبية).3
أنشئ ملف tsconfig.json:
{
"compilerOptions": {
"target": "ES2024",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"outDir": "dist",
"rootDir": "src"
},
"include": ["src/**/*.ts"]
}
أنشئ ملف docker-compose.yaml لـ Postgres 18:
services:
postgres:
image: postgres:18-alpine
container_name: pgboss-db
environment:
POSTGRES_USER: pgboss
POSTGRES_PASSWORD: pgboss
POSTGRES_DB: pgboss
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U pgboss -d pgboss"]
interval: 2s
timeout: 3s
retries: 10
علامة postgres:18-alpine تشير حالياً إلى Postgres 18.3 على Alpine 3 (تم التحقق في Docker Hub في 15 مايو 2026).5 يدعم pg-boss رسمياً أي إصدار Postgres 13 أو أحدث، لذا فإن 18 يقع ضمن النطاق المطلوب تماماً.1
قم بتشغيل الحاوية وتأكد من أن قاعدة البيانات تعمل بشكل جيد:
docker compose up -d
docker compose ps
أخيراً، أنشئ ملف .env:
DATABASE_URL=postgresql://pgboss:pgboss@localhost:5432/pgboss
الخطوة 2: تهيئة pg-boss مع حمولات مكتوبة
أنشئ ملف src/boss.ts. هذا هو المكان الوحيد الذي ينشئ نسخة PgBoss للتطبيق بأكمله:
import 'dotenv/config';
import PgBoss from 'pg-boss';
const connectionString = process.env.DATABASE_URL;
if (!connectionString) throw new Error('DATABASE_URL is required');
export const boss = new PgBoss({
connectionString,
schema: 'pgboss',
application_name: 'pg-boss-tutorial',
// إعدادات افتراضية حكيمة للإنتاج — راجع مرجع تكوين pg-boss
monitorIntervalSeconds: 60,
maintenanceIntervalSeconds: 86_400,
superviseIntervalSeconds: 60,
warningQueueSize: 1_000,
warningSlowQuerySeconds: 5,
});
// إلزامي في الإنتاج: pg-boss هو EventEmitter وسيقوم بإيقاف عملية
// Node بحدث 'error' غير معالج إذا نسيت هذا.
boss.on('error', (err) => {
console.error('[pg-boss] internal error', err);
});
الإعدادات الافتراضية هنا مقصودة. قيم monitorIntervalSeconds=60 و maintenanceIntervalSeconds=86400 تطابق الإعدادات الافتراضية الموثقة، و warningQueueSize=1000 تجعل pg-boss يظهر تحذيراً عند كبر حجم الطابور عندما يتجاوز تراكم أي طابور 1000 مهمة — وهي إشارة أبكر بكثير لتعطل أحد العمال من انتظار تحول لوحة التحكم إلى اللون الأحمر.7
استدعاء boss.start() سيقوم، في أول تشغيل، بإنشاء مخطط pgboss، وجدول pgboss.job المقسم، وكل الجداول الداعمة.1 افتراضياً migrate: true، لذا فإن الترقيات اللاحقة من إصدار فرعي لـ pg-boss إلى الإصدار التالي ستشغل عمليات الهجرة الخاصة بها بشفافية. في بيئة الإنتاج، قد ترغب في ضبط migrate: false وتطبيق عمليات الهجرة كخطوة نشر متعمدة؛ في هذا البرنامج التعليمي سنترك الخيار الافتراضي مفعلاً.
الخطوة 3: تعريف الطوابير مع إعادات المحاولة وطابور الرسائل المهملة
منذ الإصدار 11.0 من pg-boss، يجب أن تكون الطوابير موجودة قبل استدعاء send() أو insert() عليها — حيث تمت إزالة سلوك الإنشاء التلقائي عند تقديم نموذج التقسيم (partitioning model).8 عرّف طوابيرك مركزياً بحيث يعيش العقد في ملف واحد.
أنشئ ملف src/queues.ts:
import { boss } from './boss.js';
// عقود الحمولة المكتوبة — يتم تصديرها بحيث يتشاركها المنتجون والعمال
export type SendEmailJob = {
to: string;
subject: string;
bodyHtml: string;
};
export type DailyDigestJob = {
date: string; // YYYY-MM-DD
};
export const QUEUES = {
sendEmail: 'send-email',
sendEmailDlq: 'send-email-dlq',
dailyDigest: 'daily-digest',
} as const;
export async function ensureQueues(): Promise<void> {
// يجب إنشاء طابور الرسائل المهملة قبل الطابور الذي يشير إليه
await boss.createQueue(QUEUES.sendEmailDlq, { policy: 'standard' });
await boss.createQueue(QUEUES.sendEmail, {
policy: 'standard',
retryLimit: 5, // المحاولة حتى 5 مرات قبل النقل لـ DLQ
retryDelay: 30, // تأخير أساسي 30 ثانية
retryBackoff: true, // تراجع أسي متذبذب
expireInSeconds: 600, // اعتبار المهام العالقة فاشلة بعد 10 دقائق
retentionDays: 14, // الاحتفاظ بالمهام المكتملة/الفاشلة لمدة 14 يوماً
deadLetter: QUEUES.sendEmailDlq,
});
await boss.createQueue(QUEUES.dailyDigest, {
policy: 'standard',
retryLimit: 3,
retryDelay: 60,
retryBackoff: true,
});
}
هناك ثلاث تفاصيل تستحق التوقف عندها:
- طابور الرسائل المهملة (Dead-letter queue) هو مجرد طابور آخر. لا يقوم pg-boss بإنشاء جدول DLQ مخفي؛ بل تقوم أنت بإنشاء طابور عادي وتوجيه الطابور الأساسي إليه بالاسم باستخدام خيار
deadLetter.9 عندما يتم استنفادretryLimit، يتم نقل الوظيفة الفاشلة إلى DLQ كوظيفة جديدة بياناتها هي الحمولة الأصلية. هذا يعني أنك تكتب معالج DLQ تماماً مثل أي معالج آخر. retryBackoff: trueيفعل التراجع الأسي مع التذبذب (jittered exponential backoff). إذا قمت بضبطretryBackoffدون ضبطretryDelay، فإن pg-boss يجعل التأخير الأساسي الافتراضي ثانية واحدة.10 منحنى التراجع الدقيق هو "أسي مع تذبذب"، لذا فإن الوظيفة التي تفشل بشكل دائم تستقر في DLQ على مقياس من دقيقة إلى ساعة تقريباً بدلاً من ثوانٍ - وهي فترة طويلة بما يكفي لتنجلي الانقطاعات المؤقتة في الخدمات التابعة، وقصيرة بما يكفي لترى إدخال DLQ خلال وردية عمل.expireInSecondsهو شبكة الأمان الخاصة بك للعمال الخارجين عن السيطرة. إذا تم جلب وظيفة ولكن عملية العامل توقفت قبل أن ينتهي المعالج، فستقوم حلقة الإشراف بتمييز الوظيفة كفاشلة بعد انتهاء هذه المهلة و(إذا بقيت محاولات إعادة) ستعيد وضعها في الطابور.7
الخطوة 4: إرسال ومعالجة الوظائف بمعالجات آمنة النوع
أنشئ src/workers.ts لجانب المستهلك:
import type { Job } from 'pg-boss';
import { boss } from './boss.js';
{
QUEUES,
type SendEmailJob,
type DailyDigestJob,
} from './queues.js';
// Stand-in for your real email provider
async function deliverEmail(payload: SendEmailJob): Promise<void> {
console.log(`[email] -> ${payload.to}: ${payload.subject}`);
if (payload.to.endsWith('@bounce.example')) {
throw new Error('mailbox does not exist');
}
}
export async function registerWorkers(): Promise<void> {
// Single-job handler (batchSize defaults to 1). The handler ALWAYS
// receives an array since pg-boss v10, so destructure it.
await boss.work<SendEmailJob>(
QUEUES.sendEmail,
{ batchSize: 1, pollingIntervalSeconds: 2 },
async ([job]: Job<SendEmailJob>[]) => {
await deliverEmail(job.data);
},
);
// Dead-letter handler: log, page on-call, or move to a Slack channel
await boss.work<SendEmailJob>(
QUEUES.sendEmailDlq,
async ([job]: Job<SendEmailJob>[]) => {
console.error(
`[dlq] giving up on email to ${job.data.to} (job ${job.id})`,
);
// In production: emit metric, page on-call, or stash in a "needs human"
// table. The job stays here until you explicitly resolve it.
},
);
// Batched handler — process 50 digest jobs per fetch
await boss.work<DailyDigestJob>(
QUEUES.dailyDigest,
{ batchSize: 50, pollingIntervalSeconds: 5 },
async (jobs: Job<DailyDigestJob>[]) => {
console.log(`[digest] processing batch of ${jobs.length}`);
for (const job of jobs) {
// Real work goes here
await new Promise((r) => setTimeout(r, 10));
}
},
);
}
يقوم النوع العام Job<T> بتمرير نوع الحمولة إلى المعالج الخاص بك، لذا فإن job.data.to يتم تعريفه كـ string. إذا حاول منتج ما استخدام send() مع كائن غير صحيح، فسوف يرفضه TypeScript في وقت الترجمة. يعمل نفس النوع العام أيضاً على boss.send<SendEmailJob>(...).
نقطتان تقنيتان غالباً ما يتم إغفالهما:
- تمت إزالة
teamSizeوteamConcurrencyوteamRefillفي الإصدار 10. الدروس التعليمية الأقدم من 2024 لا تزال تذكرهم؛ لكنهم لم يعودوا موجودين.11 يتم التحكم في الضغط العكسي (Backpressure) من خلال الجمع بينbatchSizeوعدد استدعاءاتboss.work()التي تسجلها وعدد العمليات التي تقوم بتشغيلها. - المعالج يحصل دائماً على مصفوفة. تفكيك الوظيفة الواحدة (
([job]) => ...) هو نمط الهجرة للإصدار 10+ من توقيع(job) => ...القديم.11 إذا نسيت التفكيك معbatchSize: 1، فستعمل بصمت على المصفوفة ككل.
الآن قم بربط كل شيء معاً في src/index.ts:
import { boss } from './boss.js';
import { ensureQueues, QUEUES } from './queues.js';
import { registerWorkers } from './workers.js';
async function main(): Promise<void> {
await boss.start();
console.log('[pg-boss] started');
await ensureQueues();
await registerWorkers();
// Producer: queue a few jobs
const id1 = await boss.send(QUEUES.sendEmail, {
to: 'alice@example.com',
subject: 'Hello from pg-boss',
bodyHtml: '<p>It works.</p>',
});
console.log(`[send] queued job ${id1}`);
const id2 = await boss.send(QUEUES.sendEmail, {
to: 'broken@bounce.example', // will fail and eventually hit DLQ
subject: 'This one is going to fail',
bodyHtml: '<p>Bounce me.</p>',
});
console.log(`[send] queued job ${id2}`);
}
main().catch((err) => {
console.error(err);
process.exit(1);
});
قم بتشغيله:
npx tsx src/index.ts
يجب أن ترى وظيفة واحدة تنجح على الفور، والوظيفة المرتدة تعيد المحاولة بتأخيرات متزايدة، وفي النهاية يظهر سطر [dlq] بعد استنفاد المحاولات. اترك العملية تعمل للخطوات التالية.
الخطوة 5: جدولة وظيفة cron مع منطقة زمنية
استخدم boss.schedule(name, cron, data, options) لتسجيل وظيفة متكررة. معلمة name هي اسم الطابور الذي تستقر فيه الوظيفة - وليس "اسم جدول" منفصل. يقوم pg-boss بتشغيل Timekeeper الذي يقيم تعبيرات cron كل 30 ثانية وينشئ وظائف في الطابور المطابق.12
أضف هذا داخل main()، بعد registerWorkers():
// 09:00 Europe/Dublin every day — uses IANA timezone via the tz option
await boss.schedule(
QUEUES.dailyDigest,
'0 9 * * *',
{ date: new Date().toISOString().slice(0, 10) },
{ tz: 'Europe/Dublin' },
);
console.log('[schedule] daily-digest registered for 09:00 Europe/Dublin');
بعض الحقائق المتعلقة بـ cron:
- يستخدم pg-boss نظام cron المكون من 5 حقول (
minute hour day month dayOfWeek).12 نظام cron المكون من ستة حقول (مع الثواني) غير مدعوم. - عبر عمليات Node المتعددة، تكتسب عملية واحدة فقط قفل cron وتطلق الجدول الزمني في أي لحظة معينة - يستخدم pg-boss كائناً منفرداً (singleton) لمدة 60 ثانية بالإضافة إلى قفل موزع لضمان ذلك.12
- يعيش إدخال الجدول الزمني في جدول
pgboss.schedule، وليس في الكود الخاص بك، لذا فإن إعادة تشغيل العملية لا تؤدي إلى إطلاق cron مرتين. إذا قمت بتغيير اسم طابور، فاحذف الصف القديم منpgboss.scheduleصراحةً - وإلا سيستمر pg-boss في إرسال الوظائف إلى اسم الطابور المهجور دون وجود معالج.
الخطوة 6: تثبيت لوحة التحكم
تأتي واجهة مراقبة المراقبة الرسمية كحزمة منفصلة، @pg-boss/dashboard@1.1.3.4 يتم توزيعها كواجهة سطر أوامر مستقلة (ملف pg-boss-dashboard الثنائي)، وليس كمكتبة - لا يوجد createServer() لاستيراده. يتم التكوين بالكامل عبر متغيرات البيئة.
npm install @pg-boss/dashboard@1.1.3
قم بتشغيلها في نافذة أوامر ثانية، مع اختيار منفذ مختلف عن تطبيقك:
DATABASE_URL=postgresql://pgboss:pgboss@localhost:5432/pgboss \
PGBOSS_SCHEMA=pgboss \
PORT=3001 \
npx pg-boss-dashboard
متغيرات البيئة المدعومة هي:
DATABASE_URL— سلسلة اتصال Postgres. القيمة الافتراضية هيpostgres://localhost/pgboss.PGBOSS_SCHEMA— المخطط (schema) الذي تم ترحيل pg-boss إليه. القيمة الافتراضية هيpgboss.PORT— منفذ HTTP للاستماع عليه. القيمة الافتراضية هي3000.PGBOSS_DASHBOARD_AUTH_USERNAME/PGBOSS_DASHBOARD_AUTH_PASSWORD— تفعيل مصادقة HTTP الأساسية (يوصى بها بشدة خارج localhost).
افتح http://localhost:3001. سترى طوابيرك، وحالات الوظائف الأخيرة، وعارض حمولة كل وظيفة. تتيح لك لوحة التحكم أيضاً إلغاء الوظائف أو إعادة محاولتها أو استئنافها أو حذفها مباشرة من الواجهة - وهو أمر مفيد لفرز الإدخالات في طابور الرسائل المهملة دون كتابة سكربت لمرة واحدة.4
الخطوة 7: الإغلاق التدريجي عند استقبال SIGTERM
في بيئة الإنتاج، يرسل المنسق (Kubernetes، ECS، fly.io، systemd) إشارة SIGTERM وينتظر فترة سماح قبل فرض SIGKILL. إذا كان pg-boss في منتصف وظيفة عندما تصل SIGKILL، فستبقى تلك الوظيفة في حالة active حتى تنقضي expireInSeconds - وهذا بالضبط نوع الفشل "العالق لمدة عشر دقائق" الذي من المفترض أن يمنعه طابور الوظائف.
أضف هذا إلى نهاية src/index.ts:
async function shutdown(signal: string): Promise<void> {
console.log(`[pg-boss] received ${signal, draining…`);
try {
await boss.stop({
timeout: 25_000, // Match this to your platform's grace period
graceful: true, // Wait for in-flight handlers to resolve
destroy: false, // Let pg release connections cleanly
});
console.log('[pg-boss] stopped cleanly');
process.exit(0);
} catch (err) {
console.error('[pg-boss] forced shutdown', err);
process.exit(1);
}
}
process.on('SIGTERM', () => void shutdown('SIGTERM'));
process.on('SIGINT', () => void shutdown('SIGINT'));
يجب أن تكون قيمة timeout أقل قليلاً من terminationGracePeriodSeconds الخاصة بمنصتك (القيمة الافتراضية في Kubernetes هي 30). ضبطها بقيمة أقل من فترة السماح يضمن أن لدى pg-boss وقتاً للانتهاء قبل أن يقوم المنسق بالقتل القسري.
التحقق
افتح ثلاث نوافذ أوامر وقم بإجراء اختبار الدخان. في النافذة الأولى:
docker compose up -d
npx tsx src/index.ts
في النافذة الثانية:
DATABASE_URL=postgresql://pgboss:pgboss@localhost:5432/pgboss \
PORT=3001 \
npx pg-boss-dashboard
في النافذة الثالثة، ألقِ نظرة مباشرة على جدول الوظائف المقسم:
docker exec -it pgboss-db \
psql -U pgboss -d pgboss -c \
"SELECT name, state, retry_count, created_on
FROM pgboss.job
ORDER BY created_on DESC LIMIT 10;"
المتوقع: وظيفة send-email واحدة على الأقل في حالة completed، والوظيفة المرتدة تنتقل عبر retry ← failed ← وأخيراً وظيفة send-email-dlq في حالة created أو completed. يجب أن تظهر واجهة الطوابير في لوحة التحكم نفس الشيء.
الآن اضغط على Ctrl-C في النافذة الأولى. يجب أن ترى received SIGINT, draining… متبوعة بـ stopped cleanly في غضون ثوانٍ قليلة - والوظيفة النشطة (إن وجدت) يجب أن تكون completed في قاعدة البيانات، وليست عالقة في active.
الأخطاء الشائعة وإصلاحها
Error: queue "send-email" does not exist— قمت باستدعاءboss.send()قبلboss.createQueue(). تمت إزالة الإنشاء التلقائي في pg-boss 11.8 احرص دائمًا على استدعاءensureQueues()بعدboss.start().- المعالج يعمل مرة واحدة ولكن المتغير
undefined— لقد نسيت تفكيك مصفوفة الوظائف (destructure). منذ الإصدار v10، تتلقى معالجاتboss.work()دائمًاJob<T>[]، وليسJob<T>.11 استخدمasync ([job]) => { ... }للمعالجات التي تعمل على وظيفة واحدة. - وظائف Cron لا تعمل أبدًا — تأكد من ضبط
schedule: true(القيمة الافتراضية) في منشئPgBossوأنsupervise: true(القيمة الافتراضية أيضًا) لم يتم تعطيلها. يقوم مكون Timekeeper بتشغيل وظائف داخلية على طابور__pgboss__send-it؛ يمكنك فحص هذا الطابور في لوحة التحكم للتأكد من أن الجدول الزمني يعمل.12 - لوحة التحكم تقول "schema not found" — يجب أن تكون قاعدة البيانات في connectionString هي نفسها التي قام pg-boss بترحيلها (migrated). إذا وجهت لوحة التحكم إلى قاعدة بيانات مختلفة، فلن تجد أي جداول. تأكد باستخدام
\dnفيpsqlمن وجود مخطط (schema)pgboss. - الوظائف عالقة في حالة
active— يعني هذا دائمًا تقريبًا أن العامل (worker) تعطل في منتصف المعالجة. يخبرexpireInSeconds(المحدد في الطابور) pg-boss بتمييز هذه الوظائف كفاشلة بعد انتهاء المهلة؛ ثم يبدأ منطق إعادة المحاولة. إذا رأيت هذا كثيرًا، فقم بتقليلexpireInSecondsوراجع كود المعالج بحثًا عن وعود (promises) مرفوضة لم يتم التعامل معها.
متى تختار شيئًا آخر
يعتمد pg-boss على Postgres SELECT ... FOR UPDATE SKIP LOCKED لعملية سحب المهام الذرية (atomic dequeue).2 هذه الآلية تتوسع أفقيًا حتى النقطة التي يهيمن فيها التنافس وقت الإرسال (commit-time contention) على تقسيمات الوظائف — وهي نقطة عالية بما يكفي لمعظم التطبيقات، ولكنها أقل من طابور مدعوم بـ Redis. إذا كنت تحتاج حقًا إلى عشرات الآلاف من الوظائف في الثانية، مع تأخيرات بدقة الملي ثانية وتبعيات معقدة بين الأب والابن، فإن BullMQ على Redis يظل أداة أكثر حدة — تظهر اتجاهات npm أنه يحقق تقريبًا عشرة أضعاف التحميلات الأسبوعية مقارنة بـ pg-boss أو Graphile Worker لهذا السبب.13
بالنسبة للآخرين — الفريق الذي يدير Postgres لكل شيء آخر، والشركات الناشئة الصغيرة (SaaS) التي لا تريد إضافة Redis كنقطة فشل واحدة (SPOF)، والمشاريع الجانبية التي تريد جدولة cron موثوقة ودلالات DLQ جاهزة للاستخدام — فإن pg-boss هو خيار افتراضي ممتاز.
الخطوات التالية ومزيد من القراءة
- يشرح المنشور المصاحب تجميع اتصالات Postgres في الإنتاج باستخدام PgBouncer و Supavisor كيفية وضع pg-boss خلف مجمع اتصالات (pooler) بأمان (تلميح: وضع الجلسة session mode، وليس وضع المعاملة transaction mode).
- للحصول على نمط قناة وقت حقيقي مكمل ليس طابور وظائف، راجع Postgres LISTEN/NOTIFY للتواجد في الوقت الحقيقي.
- إذا كنت لا تزال تستخدم Postgres 17 وقلقًا بشأن الترقية، فإن ترقية Postgres 18 بدون وقت توقف باستخدام pg_createsubscriber تغطي عملية الهجرة.
- توفر وثائق pg-boss الرسمية مرجع API كاملاً14 وصفحة إعدادات (Configuration) تسرد كل خيار من خيارات المنشئ التي لم تُستخدم هنا (أسماء المخططات المخصصة، محولات قواعد البيانات الخاصة بك، حجم تجمع
max، ووظائف كلمة المرور).
Footnotes
-
timgit/pg-boss README — "يعتمد pg-boss على SKIP LOCKED في Postgres، وهي ميزة تم بناؤها خصيصًا لطوابير الرسائل... تسليم لمرة واحدة بالضبط وأمان الالتزامات الذرية المضمونة لمعالجة الوظائف غير المتزامنة." https://GitHub.com/timgit/pg-boss ↩ ↩2 ↩3 ↩4
-
وثائق PostgreSQL —
FOR UPDATE SKIP LOCKED(تم تقديمها في PostgreSQL 9.5). https://www.postgresql.org/docs/current/sql-select.html#SQL-FOR-UPDATE-SHARE ↩ ↩2 -
سجل pg-boss npm — الإصدار 12.18.2،
"type": "module"،"engines": {"node": ">=22.12.0"}، التبعيات pg ^8.20.0 و cron-parser ^5.5.0. https://www.npmjs.com/package/pg-boss ↩ ↩2 ↩3 ↩4 ↩5 -
سجل @pg-boss/dashboard npm — الإصدار 1.1.3، يتطلب Node 22.12+ و pg-boss 12.11+. https://www.npmjs.com/package/@pg-boss/dashboard ↩ ↩2 ↩3
-
إعلان إصدار PostgreSQL 18 (25 سبتمبر 2025) بالإضافة إلى وسم
postgres:18-alpineالحالي على Docker Hub (Postgres 18.3). https://www.postgresql.org/about/news/postgresql-18-released-3142/ و https://hub.Docker.com/_/postgres ↩ ↩2 -
جدول إصدارات Node.js — دخل Node 24 مرحلة Active LTS في أكتوبر 2025؛ نهاية الدعم (EOL) في 30 أبريل 2028. https://endoflife.date/nodejs ↩ ↩2
-
مرجع تكوين pg-boss — الافتراضي
monitorIntervalSeconds=60،maintenanceIntervalSeconds=86400،superviseIntervalSeconds=60،warningQueueSize،warningSlowQuerySeconds. https://deepwiki.com/timgit/pg-boss/15.5-configuration-reference ↩ ↩2 -
ملاحظات إصدار pg-boss v11 — يجب إنشاء الطوابير (queues) قبل الإرسال/الإدراج؛ تم تقديم تقسيم جداول المهام (job table partitioning). https://GitHub.com/timgit/pg-boss/releases/tag/11.0.0 ↩ ↩2
-
ملاحظات إصدار pg-boss v10 — تمت إضافة خيار
deadLetterإلىsend()وinsert()وcreateQueue(). https://GitHub.com/timgit/pg-boss/releases/tag/10.0.0 ↩ -
تكوين إعادة المحاولة في pg-boss — خيار
retryBackoff: trueيفعل التراجع الأسي مع التذبذب (jittered exponential backoff) ويجعلretryDelayافتراضياً 1 إذا لم يتم تحديده. https://deepwiki.com/timgit/pg-boss/11.1-retry-configuration ↩ -
ملاحظات إصدار pg-boss v10 — تم توحيد معالج
boss.work()ليتلقى دائماًJob[]؛ وتمت إزالةteamSizeوteamConcurrencyوteamRefill. https://GitHub.com/timgit/pg-boss/releases/tag/10.0.0 ↩ ↩2 ↩3 -
وثائق الجدولة المعتمدة على Cron في pg-boss — يقوم
Timekeeperبتقييم تعبيرات cron المكونة من 5 حقول؛ خيارtzيقبل المناطق الزمنية IANA؛ نظام الـ singleton لمدة 60 ثانية بالإضافة إلى القفل الموزع (distributed lock) يمنع تكرار تشغيل cron. https://deepwiki.com/timgit/pg-boss/10.1-cron-based-scheduling ↩ ↩2 ↩3 ↩4 -
مقارنة npm-trends بين BullMQ و pg-boss و graphile-worker (يتصدر BullMQ التحميلات الأسبوعية بنحو عشرة أضعاف تقريباً). https://npmtrends.com/bullmq-vs-graphile-worker-vs-pg-boss ↩
-
بوابة وثائق pg-boss API. https://timgit.GitHub.io/pg-boss/ ↩