feat(platform): Fix pg-boss queue conflict and add safety standards
Summary: - Fix pg-boss queue conflict (duplicate key violation on queue_pkey) - Add global error listener to prevent process crash - Reduce connection pool from 10 to 4 - Add graceful shutdown handling (SIGTERM/SIGINT) - Fix researchWorker recursive call bug in catch block - Make screeningWorker idempotent using upsert Security Standards (v1.1): - Prohibit recursive retry in Worker catch blocks - Prohibit payload bloat (only store fileKey/ID in job.data) - Require Worker idempotency (upsert + unique constraint) - Recommend task-specific expireInSeconds settings - Document graceful shutdown pattern New Features: - PKB signed URL endpoint for document preview/download - pg_bigm installation guide for Docker - Dockerfile.postgres-with-extensions for pgvector + pg_bigm Documentation: - Update Postgres-Only async task processing guide (v1.1) - Add troubleshooting SQL queries - Update safety checklist Tested: Local verification passed
This commit is contained in:
@@ -202,3 +202,4 @@ export const jwtService = new JWTService();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ export class PgBossQueue implements JobQueue {
|
||||
this.boss = new PgBoss({
|
||||
connectionString,
|
||||
schema, // 使用platform_schema
|
||||
max: 10, // 最大连接数
|
||||
max: 4, // 🛡️ 限制连接数,避免挤占 Prisma 连接配额(RDS 限制 100)
|
||||
application_name: 'aiclinical-queue',
|
||||
|
||||
// 调度配置
|
||||
@@ -61,6 +61,17 @@ export class PgBossQueue implements JobQueue {
|
||||
maintenanceIntervalSeconds: 300, // 每5分钟运行维护任务
|
||||
})
|
||||
|
||||
// 🛡️ 全局错误监听:防止未捕获错误导致进程崩溃
|
||||
this.boss.on('error', (err: any) => {
|
||||
// 静默处理 duplicate key 错误(队列并发初始化时的正常现象)
|
||||
if (err.code === '23505' && err.constraint === 'queue_pkey') {
|
||||
console.log(`[PgBossQueue] ℹ️ Queue concurrency conflict auto-resolved: ${err.detail}`);
|
||||
} else {
|
||||
console.error('[PgBossQueue] ❌ Critical error:', err);
|
||||
// 记录到日志但不崩溃进程
|
||||
}
|
||||
});
|
||||
|
||||
console.log('[PgBossQueue] Initialized with schema:', schema)
|
||||
}
|
||||
|
||||
@@ -192,13 +203,22 @@ export class PgBossQueue implements JobQueue {
|
||||
console.log(`[PgBossQueue] 🔧 开始注册 Handler: ${type}`);
|
||||
|
||||
try {
|
||||
// pg-boss 9.x 需要显式创建队列
|
||||
await this.boss.createQueue(type, {
|
||||
retryLimit: 3,
|
||||
retryDelay: 60,
|
||||
expireInSeconds: 6 * 60 * 60 // 6小时
|
||||
});
|
||||
console.log(`[PgBossQueue] ✅ Queue created: ${type}`);
|
||||
// pg-boss 9.x 需要显式创建队列(幂等操作)
|
||||
try {
|
||||
await this.boss.createQueue(type, {
|
||||
retryLimit: 3,
|
||||
retryDelay: 60,
|
||||
expireInSeconds: 6 * 60 * 60 // 6小时
|
||||
});
|
||||
console.log(`[PgBossQueue] ✅ Queue created: ${type}`);
|
||||
} catch (createError: any) {
|
||||
// 队列已存在时会报 duplicate key 错误,忽略
|
||||
if (createError.code === '23505' || createError.message?.includes('already exists')) {
|
||||
console.log(`[PgBossQueue] ℹ️ Queue already exists: ${type}`);
|
||||
} else {
|
||||
throw createError;
|
||||
}
|
||||
}
|
||||
|
||||
await this.boss.work<Record<string, any>>(type, {
|
||||
batchSize: 1, // 每次处理1个任务
|
||||
|
||||
@@ -332,5 +332,6 @@ export function getBatchItems<T>(
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -85,3 +85,4 @@ export interface VariableValidation {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -355,3 +355,4 @@ export default ChunkService;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -51,3 +51,4 @@ export const DifyClient = DeprecatedDifyClient;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -206,3 +206,4 @@ export function createOpenAIStreamAdapter(
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -212,3 +212,4 @@ export async function streamChat(
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -30,3 +30,4 @@ export { THINKING_TAGS } from './types';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -105,3 +105,4 @@ export type SSEEventType =
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user