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:
2025-09-14 21:41:29 +02:00
parent e28ee6b358
commit 8135ff61c2
3 changed files with 25 additions and 21 deletions

View File

@@ -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
}
/**

View File

@@ -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.`
)
}

View File

@@ -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