mirror of
https://github.com/xtr-dev/payload-mailing.git
synced 2025-12-10 00:03:23 +00:00
Optimize polling performance and reduce memory usage
- Reduce polling attempts from 10 to 5 with 3-second timeout protection - Optimize exponential backoff delays (25ms-400ms vs 50ms-2000ms) - Remove memory-intensive unique keys from job creation - Reduce ensureEmailJob retry attempts from 5 to 3 - Use gentler exponential backoff (1.5x vs 2x) capped at 200ms - Rely on database constraints for duplicate prevention instead of memory keys Performance improvements: - Faster response times for immediate email sending - Reduced memory bloat in job queue systems - Better resource efficiency for high-volume scenarios 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -9,10 +9,6 @@ export interface ProcessEmailJobInput {
|
|||||||
* The ID of the email to process
|
* The ID of the email to process
|
||||||
*/
|
*/
|
||||||
emailId: string | number
|
emailId: string | number
|
||||||
/**
|
|
||||||
* Optional unique constraint helper to prevent duplicate jobs
|
|
||||||
*/
|
|
||||||
uniqueKey?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -149,16 +149,26 @@ export const sendEmail = async <TEmail extends BaseEmailDocument = BaseEmailDocu
|
|||||||
throw new Error('PayloadCMS jobs not configured - cannot process email immediately')
|
throw new Error('PayloadCMS jobs not configured - cannot process email immediately')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Poll for the job with exponential backoff
|
// Poll for the job with optimized backoff and timeout protection
|
||||||
// This handles the async nature of hooks and ensures we wait for job creation
|
// This handles the async nature of hooks and ensures we wait for job creation
|
||||||
const maxAttempts = 10
|
const maxAttempts = 5 // Reduced from 10 to minimize delay
|
||||||
const initialDelay = 50 // Start with 50ms
|
const initialDelay = 25 // Reduced from 50ms for faster response
|
||||||
|
const maxTotalTime = 3000 // 3 second total timeout
|
||||||
|
const startTime = Date.now()
|
||||||
let jobId: string | undefined
|
let jobId: string | undefined
|
||||||
|
|
||||||
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
||||||
// Calculate delay with exponential backoff (50ms, 100ms, 200ms, 400ms...)
|
// Check total timeout before continuing
|
||||||
// Cap at 2 seconds per attempt
|
if (Date.now() - startTime > maxTotalTime) {
|
||||||
const delay = Math.min(initialDelay * Math.pow(2, attempt), 2000)
|
throw new Error(
|
||||||
|
`Job polling timed out after ${maxTotalTime}ms for email ${email.id}. ` +
|
||||||
|
`The auto-scheduling may have failed or is taking longer than expected.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate delay with exponential backoff (25ms, 50ms, 100ms, 200ms, 400ms)
|
||||||
|
// Cap at 400ms per attempt for better responsiveness
|
||||||
|
const delay = Math.min(initialDelay * Math.pow(2, attempt), 400)
|
||||||
|
|
||||||
if (attempt > 0) {
|
if (attempt > 0) {
|
||||||
await new Promise(resolve => setTimeout(resolve, delay))
|
await new Promise(resolve => setTimeout(resolve, delay))
|
||||||
@@ -178,15 +188,15 @@ export const sendEmail = async <TEmail extends BaseEmailDocument = BaseEmailDocu
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log on later attempts to help with debugging
|
// Log on later attempts to help with debugging (reduced threshold)
|
||||||
if (attempt >= 3) {
|
if (attempt >= 2) {
|
||||||
console.log(`Waiting for job creation for email ${email.id}, attempt ${attempt + 1}/${maxAttempts}`)
|
console.log(`Waiting for job creation for email ${email.id}, attempt ${attempt + 1}/${maxAttempts}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!jobId) {
|
if (!jobId) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`No processing job found for email ${email.id} after ${maxAttempts} attempts. ` +
|
`No processing job found for email ${email.id} after ${maxAttempts} attempts (${Date.now() - startTime}ms). ` +
|
||||||
`The auto-scheduling may have failed or is taking longer than expected.`
|
`The auto-scheduling may have failed or is taking longer than expected.`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,9 +47,9 @@ export async function ensureEmailJob(
|
|||||||
const mailingContext = (payload as any).mailing
|
const mailingContext = (payload as any).mailing
|
||||||
const queueName = options?.queueName || mailingContext?.config?.queue || 'default'
|
const queueName = options?.queueName || mailingContext?.config?.queue || 'default'
|
||||||
|
|
||||||
// Implement atomic check-and-create with retry logic to prevent race conditions
|
// Implement atomic check-and-create with minimal retry for efficiency
|
||||||
const maxAttempts = 5
|
const maxAttempts = 3 // Reduced from 5 for better performance
|
||||||
const baseDelay = 100 // Start with 100ms
|
const baseDelay = 50 // Reduced from 100ms for faster response
|
||||||
|
|
||||||
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
||||||
// Check for existing jobs with precise matching
|
// Check for existing jobs with precise matching
|
||||||
@@ -64,14 +64,12 @@ export async function ensureEmailJob(
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Attempt to create job with specific input that ensures uniqueness
|
// Attempt to create job - rely on database constraints for duplicate prevention
|
||||||
const job = await payload.jobs.queue({
|
const job = await payload.jobs.queue({
|
||||||
queue: queueName,
|
queue: queueName,
|
||||||
task: 'process-email',
|
task: 'process-email',
|
||||||
input: {
|
input: {
|
||||||
emailId: normalizedEmailId,
|
emailId: normalizedEmailId
|
||||||
// Add a unique constraint helper to prevent duplicates at queue level
|
|
||||||
uniqueKey: `email-${normalizedEmailId}-${Date.now()}-${Math.random()}`
|
|
||||||
},
|
},
|
||||||
waitUntil: options?.scheduledAt ? new Date(options.scheduledAt) : undefined
|
waitUntil: options?.scheduledAt ? new Date(options.scheduledAt) : undefined
|
||||||
})
|
})
|
||||||
@@ -85,7 +83,7 @@ export async function ensureEmailJob(
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
// On any creation error, wait briefly and check again for concurrent creation
|
// On any creation error, wait briefly and check again for concurrent creation
|
||||||
if (attempt < maxAttempts - 1) {
|
if (attempt < maxAttempts - 1) {
|
||||||
const delay = baseDelay * Math.pow(2, attempt) // Exponential backoff
|
const delay = Math.min(baseDelay * Math.pow(1.5, attempt), 200) // Gentler exponential backoff, capped at 200ms
|
||||||
await new Promise(resolve => setTimeout(resolve, delay))
|
await new Promise(resolve => setTimeout(resolve, delay))
|
||||||
|
|
||||||
// Check if another process succeeded while we were failing
|
// Check if another process succeeded while we were failing
|
||||||
|
|||||||
Reference in New Issue
Block a user