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
|
||||
*/
|
||||
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')
|
||||
}
|
||||
|
||||
// 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
|
||||
const maxAttempts = 10
|
||||
const initialDelay = 50 // Start with 50ms
|
||||
const maxAttempts = 5 // Reduced from 10 to minimize delay
|
||||
const initialDelay = 25 // Reduced from 50ms for faster response
|
||||
const maxTotalTime = 3000 // 3 second total timeout
|
||||
const startTime = Date.now()
|
||||
let jobId: string | undefined
|
||||
|
||||
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
||||
// Calculate delay with exponential backoff (50ms, 100ms, 200ms, 400ms...)
|
||||
// Cap at 2 seconds per attempt
|
||||
const delay = Math.min(initialDelay * Math.pow(2, attempt), 2000)
|
||||
// Check total timeout before continuing
|
||||
if (Date.now() - startTime > maxTotalTime) {
|
||||
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) {
|
||||
await new Promise(resolve => setTimeout(resolve, delay))
|
||||
@@ -178,15 +188,15 @@ export const sendEmail = async <TEmail extends BaseEmailDocument = BaseEmailDocu
|
||||
break
|
||||
}
|
||||
|
||||
// Log on later attempts to help with debugging
|
||||
if (attempt >= 3) {
|
||||
// Log on later attempts to help with debugging (reduced threshold)
|
||||
if (attempt >= 2) {
|
||||
console.log(`Waiting for job creation for email ${email.id}, attempt ${attempt + 1}/${maxAttempts}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (!jobId) {
|
||||
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.`
|
||||
)
|
||||
}
|
||||
|
||||
@@ -47,9 +47,9 @@ export async function ensureEmailJob(
|
||||
const mailingContext = (payload as any).mailing
|
||||
const queueName = options?.queueName || mailingContext?.config?.queue || 'default'
|
||||
|
||||
// Implement atomic check-and-create with retry logic to prevent race conditions
|
||||
const maxAttempts = 5
|
||||
const baseDelay = 100 // Start with 100ms
|
||||
// Implement atomic check-and-create with minimal retry for efficiency
|
||||
const maxAttempts = 3 // Reduced from 5 for better performance
|
||||
const baseDelay = 50 // Reduced from 100ms for faster response
|
||||
|
||||
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
||||
// Check for existing jobs with precise matching
|
||||
@@ -64,14 +64,12 @@ export async function ensureEmailJob(
|
||||
}
|
||||
|
||||
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({
|
||||
queue: queueName,
|
||||
task: 'process-email',
|
||||
input: {
|
||||
emailId: normalizedEmailId,
|
||||
// Add a unique constraint helper to prevent duplicates at queue level
|
||||
uniqueKey: `email-${normalizedEmailId}-${Date.now()}-${Math.random()}`
|
||||
emailId: normalizedEmailId
|
||||
},
|
||||
waitUntil: options?.scheduledAt ? new Date(options.scheduledAt) : undefined
|
||||
})
|
||||
@@ -85,7 +83,7 @@ export async function ensureEmailJob(
|
||||
} catch (error) {
|
||||
// On any creation error, wait briefly and check again for concurrent creation
|
||||
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))
|
||||
|
||||
// Check if another process succeeded while we were failing
|
||||
|
||||
Reference in New Issue
Block a user