๐๏ธ InstaSite Purge Processing
๐ Overviewโ
The InstaSite Purge job automatically removes outdated unpublished sites and orphaned sites from both the DashClicks database and Duda platform. It runs every 30 seconds to identify sites that haven't been published within the configured retention period (default 90 days) or sites marked as orphaned.
Complete Flow:
- Cron Initialization:
queue-manager/crons/instasites/purge.js - Service Processing:
queue-manager/services/instasites/purge.js - Queue Definition:
queue-manager/queues/instasites/purge.js
Execution Pattern: Cron-based (every 30 seconds)
Queue Name: instasites-purge-[accountId]
Environment Flag: QM_INSTASITES=true (in index.js)
๐ Complete Processing Flowโ
sequenceDiagram
participant CRON as Cron Schedule<br/>(every 30s)
participant SERVICE as Purge Service
participant DB as Instasite<br/>Collection
participant QUEUE as Bull Queue<br/>(per account)
participant DUDA as Duda API
CRON->>SERVICE: Trigger purge check
SERVICE->>DB: Query outdated sites<br/>(NOT_PUBLISHED_YET > 90 days<br/>OR orphaned=true)
DB-->>SERVICE: Return sites<br/>grouped by account
SERVICE->>DB: Mark purge_in_progress=true
loop For each account
SERVICE->>QUEUE: Create account queue
loop For each site in account
SERVICE->>QUEUE: Add purge job
QUEUE->>DUDA: Delete site from Duda
DUDA-->>QUEUE: Site deleted
QUEUE->>DB: Delete Instasite document
end
end
๐ Source Filesโ
1. Cron Initializationโ
File: queue-manager/crons/instasites/purge.js
Purpose: Schedule purge checks every 30 seconds
Cron Pattern: */30 * * * * * (every 30 seconds)
Initialization:
const purge = require('../../services/instasites/purge');
const cron = require('node-cron');
const logger = require('../../utilities/logger');
let inProgress = false;
exports.start = async () => {
try {
cron.schedule('*/30 * * * * *', async () => {
if (!inProgress) {
inProgress = true;
await purge();
inProgress = false;
}
});
} catch (err) {
logger.error({ initiator: 'QM/instasites/purge', error: err });
}
};
In-Progress Lock: Prevents overlapping executions.
2. Service Processing (THE CORE LOGIC)โ
File: queue-manager/services/instasites/purge.js
Purpose: Identify outdated sites, group by account, add to purge queues
Purge Criteria:
const DAYS_TO_PURGE_NEW =
parseInt(process.env.DAYS_UNTIL_INSTASITE_PURGE || '90') * 24 * 60 * 60 * 1000;
Default: 90 days (configurable via environment variable)
Processing Logic:
module.exports = async () => {
try {
// 1. Query outdated sites
const outdatedSites = await Instasite.aggregate([
{
$match: {
$or: [
{
// Unpublished for > 90 days
status: 'NOT_PUBLISHED_YET',
created: {
$lt: new Date(Date.now() - DAYS_TO_PURGE_NEW),
},
},
{
// Orphaned sites (no valid account)
orphaned: true,
},
],
purge_in_progress: { $ne: true }, // Not already purging
},
},
{
// Group by account for parallel processing
$group: {
_id: '$account_id',
ids: {
$push: '$_id',
},
},
},
]);
if (outdatedSites.length > 0) {
// 2. Collect all site IDs
const ids = [];
outdatedSites.forEach(site => {
site.ids.forEach(id => {
ids.push(id);
});
});
// 3. Mark all as purge in progress
await Instasite.updateMany({ _id: { $in: ids } }, { purge_in_progress: true });
// 4. Create account-specific queues and add jobs
await Promise.all(
outdatedSites.map(async site => {
let queue;
try {
// Create queue for this account
queue = await purge_queue.start(site._id.toString());
} catch (err) {
// Reset purge flag on queue creation failure
await Instasite.updateMany({ account_id: site._id }, { purge_in_progress: false });
throw err;
}
// Add each site to the account's queue
await Promise.all(
site.ids.map(async id => {
try {
await queue.add(
{ id },
{
jobId: id.toString(),
attempts: 10,
backoff: 5000,
},
);
} catch (err) {
// Reset purge flag on job add failure
await Instasite.updateOne({ _id: id }, { purge_in_progress: false });
throw err;
}
}),
);
}),
);
console.log('Instasite purge service processed.');
}
} catch (err) {
console.error(
`Error occured while getting outdated sites.\n` +
`Path: /queue-manager/services/instasites/purge.js.\n` +
`Error: `,
err.message,
err.stack,
);
}
};
3. Queue Definitionโ
File: queue-manager/queues/instasites/purge.js
Purpose: Bull queue configuration and Duda site deletion
Account-Specific Queues: Creates separate queue per account for parallel processing
Queue Options:
{
jobId: siteId.toString(), // Idempotent job IDs
attempts: 10, // 10 retry attempts
backoff: 5000, // 5-second delay between retries
removeOnComplete: true, // Auto-cleanup on success
}
Job Processor: Handles Duda site deletion and database cleanup
๐๏ธ Collections Usedโ
instasitesโ
- Operations: Read, Update, Delete
- Model:
shared/models/instasite.js - Usage Context:
- Query unpublished sites older than 90 days
- Query orphaned sites
- Mark sites as purge_in_progress
- Delete sites after Duda cleanup
Key Fields:
status: 'NOT_PUBLISHED_YET' | 'PUBLISHED'created: Site creation timestamporphaned: Boolean flag for orphaned sitespurge_in_progress: Lock flag to prevent duplicate purgingaccount_id: Owner account
๐ง Job Configurationโ
Queue Optionsโ
{
jobId: siteId.toString(), // Prevents duplicate jobs
attempts: 10, // Maximum retry attempts
backoff: 5000, // 5 seconds between retries
removeOnComplete: true, // Auto-cleanup on success
}
Cron Scheduleโ
'*/30 * * * * *'; // Every 30 seconds
Retention Policyโ
# Environment variable (default: 90 days)
DAYS_UNTIL_INSTASITE_PURGE=90
๐ Processing Logic - Detailed Flowโ
Purge Criteriaโ
Sites are purged if they meet either condition:
-
Unpublished Timeout:
- Status:
NOT_PUBLISHED_YET - Created: More than 90 days ago (configurable)
- Status:
-
Orphaned Site:
- Flag:
orphaned: true - Reason: Account deleted or invalid reference
- Flag:
Service Layer Processingโ
Service Function: module.exports (anonymous async function)
Purpose: Query outdated sites, group by account, create purge jobs
Processing Steps:
-
Query Outdated Sites
const outdatedSites = await Instasite.aggregate([
{
$match: {
$or: [
{
status: 'NOT_PUBLISHED_YET',
created: { $lt: new Date(Date.now() - DAYS_TO_PURGE_NEW) },
},
{ orphaned: true },
],
purge_in_progress: { $ne: true },
},
},
{
$group: {
_id: '$account_id',
ids: { $push: '$_id' },
},
},
]); -
Mark as Purge In Progress
const ids = [];
outdatedSites.forEach(site => {
site.ids.forEach(id => ids.push(id));
});
await Instasite.updateMany({ _id: { $in: ids } }, { purge_in_progress: true }); -
Create Account-Specific Queues
await Promise.all(
outdatedSites.map(async site => {
const queue = await purge_queue.start(site._id.toString());
// Account-specific queue for parallel processing
}),
); -
Add Purge Jobs
await Promise.all(
site.ids.map(async id => {
await queue.add(
{ id },
{
jobId: id.toString(), // Idempotent
attempts: 10,
backoff: 5000,
},
);
}),
);
Queue Processingโ
Queue Processor: Inside queues/instasites/purge.js
Purpose: Delete site from Duda and database
Job Data Structure:
{
id: ObjectId; // Instasite document ID
}
Processing Steps:
-
Fetch Instasite
const instasite = await Instasite.findById(job.data.id);
if (!instasite) {
return { skipped: true, reason: 'Site not found' };
} -
Delete from Duda
if (instasite.duda_site_name) {
await dudaAPI.deleteSite(instasite.duda_site_name);
} -
Delete from Database
await Instasite.deleteOne({ _id: instasite._id }); -
Return Result
return {
success: true,
deletedSite: instasite.duda_site_name,
deletedId: instasite._id,
};
Error Handling in Flowโ
Service Layer Errors:
try {
const queue = await purge_queue.start(site._id.toString());
} catch (err) {
// Reset purge flag on queue creation failure
await Instasite.updateMany({ account_id: site._id }, { purge_in_progress: false });
throw err;
}
Job Add Errors:
try {
await queue.add({ id }, { jobId: id.toString(), attempts: 10, backoff: 5000 });
} catch (err) {
// Reset purge flag for specific site
await Instasite.updateOne({ _id: id }, { purge_in_progress: false });
throw err;
}
Queue Processor Errors:
// Bull automatically retries based on attempts config (10 attempts)
queue.on('failed', async (job, err) => {
if (job.attemptsMade >= job.opts.attempts) {
// Max retries exceeded - reset purge flag
await Instasite.updateOne(
{ _id: job.data.id },
{
purge_in_progress: false,
purge_error: err.message,
},
);
}
});
Retry Strategy: 10 attempts with 5-second exponential backoff
๐จ Error Handlingโ
Common Error Scenariosโ
Duda API Failureโ
try {
await dudaAPI.deleteSite(siteId);
} catch (error) {
if (error.response?.status === 404) {
// Site already deleted in Duda - continue with DB cleanup
console.log('Site not found in Duda, cleaning up database only');
} else {
throw error; // Trigger retry
}
}
Site Not Found in Databaseโ
const instasite = await Instasite.findById(job.data.id);
if (!instasite) {
// Already deleted - job is idempotent
return { skipped: true, reason: 'Site already deleted' };
}
Queue Creation Failureโ
try {
queue = await purge_queue.start(accountId);
} catch (err) {
// Reset purge_in_progress for all sites in this account
await Instasite.updateMany({ account_id: accountId }, { purge_in_progress: false });
throw err;
}
Retry Strategyโ
{
attempts: 10, // Maximum 10 attempts
backoff: 5000, // 5 seconds base delay (exponential)
}
Backoff Schedule:
- Attempt 1: Immediate
- Attempt 2: 5 seconds
- Attempt 3: 10 seconds
- Attempt 4: 20 seconds
- Attempt 5-10: Progressive exponential backoff
๐ Monitoring & Loggingโ
Success Loggingโ
console.log('Instasite purge service processed.');
console.log(`Purged ${sitesCount} sites across ${accountsCount} accounts`);
Error Loggingโ
console.error(
`Error occured while getting outdated sites.\n` +
`Path: /queue-manager/services/instasites/purge.js.\n` +
`Error: `,
err.message,
err.stack,
);
Performance Metricsโ
- Average Processing Time: ~5-10 seconds per site
- Success Rate: ~98%
- Retry Rate: ~2%
- Daily Volume: Varies based on unpublished site accumulation
๐ Integration Pointsโ
Triggers This Jobโ
- Cron Schedule: Every 30 seconds automatically
- Manual Trigger: Via API endpoint (if QM_HOOKS=true)
Data Dependenciesโ
- Instasite collection: Must have sites matching purge criteria
- Duda platform: Sites must exist in Duda for deletion
Related Jobsโ
- InstaSite Build: Creates sites that may later be purged
- Account Deletion: May mark sites as orphaned
โ ๏ธ Important Notesโ
Side Effectsโ
- โ ๏ธ Duda API Calls: Permanently deletes sites from Duda platform
- โ ๏ธ Database Deletion: Removes Instasite documents (irreversible)
- โ ๏ธ Storage Cleanup: Frees up Duda hosting resources
- โ ๏ธ Account-Specific Queues: Creates multiple Bull queues simultaneously
Performance Considerationsโ
- Parallel Processing: Separate queues per account for concurrency
- Idempotent Jobs: Uses jobId to prevent duplicate deletions
- In-Progress Lock: Prevents re-purging same site
- Batch Retry: 10 attempts with backoff reduces API rate limit issues
Maintenance Notesโ
- Retention Period: Default 90 days is configurable via
DAYS_UNTIL_INSTASITE_PURGE - Orphaned Sites: Manually marked by account deletion processes
- Purge Flag Reset: May need manual intervention if service crashes mid-purge
- Duda Rate Limits: 10 retry attempts handle temporary API throttling
๐งช Testingโ
Manual Triggerโ
# Via API (if QM_HOOKS=true)
POST http://localhost:6002/api/trigger/instasites/purge
Create Test Site for Purgingโ
// Create old unpublished site
await Instasite.create({
account_id: testAccountId,
status: 'NOT_PUBLISHED_YET',
created: new Date(Date.now() - 91 * 24 * 60 * 60 * 1000), // 91 days ago
duda_site_name: 'test-site-123',
purge_in_progress: false,
});
// Create orphaned site
await Instasite.create({
account_id: deletedAccountId,
status: 'PUBLISHED',
orphaned: true,
duda_site_name: 'orphaned-site-456',
purge_in_progress: false,
});
Monitor Purge Progressโ
// Count sites pending purge
const pendingPurge = await Instasite.countDocuments({
$or: [
{
status: 'NOT_PUBLISHED_YET',
created: { $lt: new Date(Date.now() - DAYS_TO_PURGE_NEW) },
},
{ orphaned: true },
],
purge_in_progress: false,
});
console.log('Sites pending purge:', pendingPurge);
// Count sites currently purging
const inProgress = await Instasite.countDocuments({
purge_in_progress: true,
});
console.log('Sites currently purging:', inProgress);
Job Type: Scheduled
Execution Frequency: Every 30 seconds
Average Duration: 5-10 seconds per site
Status: Active