How OpenSEO's Scheduled Rank Check Cron Job Prevents Schedule Drift
OpenSEO prevents schedule drift by only advancing the next_check_at timestamp after successfully claiming and starting a workflow, while the computeNextCheckAt function calculates future run times from a fixed anchor point rather than the current execution time.
The scheduled rank check cron job in every-app/open-seo processes keyword ranking checks every five minutes while maintaining strict resource budgets and atomic state management. This architecture ensures that daily, weekly, and monthly rank-tracking configurations execute at consistent intervals even when individual runs fail or experience delays.
How the Scheduled Rank Check Cron Job Executes
The cron job operates on a 5-minute tick cycle orchestrated through the main server entry point and the runScheduledRankChecks service.
Entry Point and Invocation
In src/server.ts, the cron handler invokes runScheduledRankChecks to begin each scheduling cycle. This serves as the primary coordinator for all scheduled rank-check activities.
// Cron entry point (src/server.ts)
await withPgClient(() => runScheduledRankChecks(env));
Identifying Due Configurations
The scheduler queries the RankTrackingRepository to fetch configurations where next_check_at is less than or equal to the current UTC time. The getDueConfigsWithOrganization method retrieves these candidates along with their organization data for plan verification.
// Fetching due configurations (scheduledRankChecks.ts L40-L42)
const dueConfigs = await rankTrackingRepository.getDueConfigsWithOrganization();
Resource Budgeting and Time Constraints
To prevent system overload, the implementation enforces two hard limits within the processing loop:
- Task Unit Budget: A maximum of 1,000 task units (calculated as keywords × devices) per tick via
SCHEDULED_TASK_UNIT_BUDGET - Wall-Clock Deadline: A hard stop at 3 minutes (
TICK_DEADLINE_MS) ensures completion before the next 5-minute tick
// Budget and deadline enforcement
if (started > 0 && unitsStarted + taskUnits > SCHEDULED_TASK_UNIT_BUDGET) {
stoppedByBudget = true;
break;
}
if (Date.now() >= deadline) {
stoppedByDeadline = true;
break;
}
Plan Verification and Atomic Claiming
For SaaS deployments, the job verifies that the organization maintains a valid paid plan using customerHasPaidPlan. If this check fails, the error is logged but the schedule is not advanced, allowing the configuration to be retried on subsequent ticks without drift.
Before launching a workflow, the system atomically claims the configuration slot using claimDueConfig. This prevents race conditions in multi-worker deployments by ensuring only one instance processes a given rank-tracking configuration.
Handling Concurrent Executions
If the workflow is already running when the cron fires, the scheduler executes a schedule restoration operation. It swaps observedNextCheckAt and nextCheckAt values to revert the schedule, ensuring the configuration remains due for the next tick rather than being skipped or delayed prematurely.
// Schedule restoration when workflow already running (scheduledRankChecks.ts L101-L112)
// Reverts the schedule so the config is retried on next tick without advancing
Drift Prevention Architecture
Schedule drift—where recurring jobs gradually shift later due to processing delays—is eliminated through anchor-based calculation and conservative state advancement.
The computeNextCheckAt Algorithm
Located in src/shared/rank-tracking.ts, the computeNextCheckAt function calculates the next execution time using the previous next_check_at value as a fixed anchor rather than the current timestamp. This ensures that if a weekly job runs late (for example, on Wednesday instead of Monday), the subsequent check is still scheduled for the following Monday, not the next Wednesday.
// computeNextCheckAt guarantees no drift (src/shared/rank-tracking.ts L78-L115)
if (previousNextCheckAt) {
const anchor = new Date(previousNextCheckAt).getTime();
const intervalMs = daysAhead * 86_400_000;
const steps = Math.floor(Math.max(0, now - anchor) / intervalMs) + 1;
return new Date(anchor + steps * intervalMs).toISOString();
}
For initial runs without a previous anchor, the system selects a random hour between 04:00 and 09:00 UTC with a random minute to distribute load, then applies the appropriate day interval (1 day for daily, 7 days for weekly, or end-of-month for monthly).
Conservative Schedule Advancement
The schedule is only advanced after successful workflow initiation. Transient failures—such as database concurrency conflicts during claimDueConfig, plan verification errors, or workflow start failures—leave the original next_check_at timestamp unchanged. This guarantees that the next 5-minute tick will retry the same configuration without pushing the schedule forward, preventing permanent drift.
Summary
- The cron job runs every 5 minutes via
runScheduledRankChecksinsrc/server.ts, processing rank-tracking configurations through atomic database operations inRankTrackingRepository. - Hard limits of 1,000 task units and 3-minute execution deadlines protect system resources from runaway scheduling loops.
- Drift prevention relies on
computeNextCheckAtinsrc/shared/rank-tracking.ts, which calculates future dates from a fixed anchor point rather than the current execution time. - The schedule only advances after successful workflow claims and plan verification; failures preserve the original
next_check_atfor retry on the next tick. - Concurrent execution handling via schedule restoration ensures configurations are never skipped due to existing workflow runs.
Frequently Asked Questions
How often does the rank check cron job run in OpenSEO?
The cron job executes every 5 minutes, invoking the runScheduledRankChecks function from src/server.ts to evaluate and process due rank-tracking configurations.
What prevents the scheduled rank check cron job from overloading the system?
OpenSEO implements a dual-throttle mechanism: a task unit budget of 1,000 units (keywords × devices) per tick via SCHEDULED_TASK_UNIT_BUDGET, and a 3-minute wall-clock deadline (TICK_DEADLINE_MS). The processing loop terminates immediately when either threshold is reached, deferring remaining work to subsequent ticks without schedule advancement.
What happens if a rank check workflow is already running when the cron fires?
If the system detects an already-running workflow during the claiming phase, it restores the schedule by reverting the next_check_at value using the observedNextCheckAt field. This ensures the configuration remains eligible for processing on the next 5-minute tick without losing its chronological position in the schedule sequence.
How does OpenSEO handle missed or delayed rank checks without causing schedule drift?
The computeNextCheckAt function uses the previous scheduled time as an immutable anchor, adding interval increments (1 day, 7 days, or monthly) until it reaches a future timestamp. Because calculations start from the original anchor rather than the delayed execution time, even significantly late runs do not shift the subsequent schedule, eliminating cumulative drift.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →