# How Scheduled Rank Checks Work in OpenSEO: A Deep Dive into the Cron Worker Architecture

> Discover how OpenSEO performs scheduled rank checks. Learn about the cron worker architecture, tracker validation, and workflow execution.

- Repository: [Every App/open-seo](https://github.com/every-app/open-seo)
- Tags: deep-dive
- Published: 2026-09-06

---

**OpenSEO performs scheduled rank checks through a dedicated cron worker that identifies due trackers, validates eligibility, atomically claims scheduling slots, and launches Rank Check Workflows while enforcing API budget limits and execution deadlines.**

OpenSEO, the open-source SEO platform developed by Every App, automates keyword rank tracking through a sophisticated scheduled job system. Understanding how scheduled rank checks work in OpenSEO helps developers customize tracking intervals, debug scheduling issues, or extend the platform's automation capabilities. This article examines the complete execution flow—from cron trigger to workflow launch—based on the actual implementation in the `every-app/open-seo` repository.

## The Four-Stage Scheduling Pipeline

The scheduled rank check system operates through four distinct stages, each designed to prevent duplicate runs, respect billing plan limits, and maintain reliable execution.

### Stage 1: Identify Due Trackers

The cron worker begins by querying for trackers ready to execute. In [`src/server/features/rank-tracking/services/scheduledRankChecks.ts`](https://github.com/every-app/open-seo/blob/main/src/server/features/rank-tracking/services/scheduledRankChecks.ts), the `runScheduledRankChecks` function calls `RankTrackingRepository.getDueConfigsWithOrganization` with the current ISO timestamp:

```ts
const nowIso = new Date().toISOString();
const dueConfigs = await RankTrackingRepository.getDueConfigsWithOrganization(nowIso);

```

This query returns all `RankTrackingConfig` records where `nextCheckAt` has passed and includes joined organization data for plan validation.

### Stage 2: Validate Eligibility

For each due configuration, the scheduler applies three validation filters using `isScheduledRankTrackingInterval` from [`src/shared/rank-tracking.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/rank-tracking.ts):

- **Schedule interval verification** — confirms `daily`, `weekly`, or `monthly` (rejects `manual` triggers)
- **Paid plan check** — calls `customerHasPaidPlan` for hosted deployments
- **Keyword presence** — skips empty trackers and free-plan configurations

Malformed or ineligible configs are silently skipped without modifying their schedule.

### Stage 3: Atomic Slot Claiming

To prevent race conditions between concurrent cron invocations, OpenSEO uses an **optimistic locking pattern** via `RankTrackingRepository.claimDueConfig`:

```ts
const nextCheckAt = computeNextCheckAt(interval, config.nextCheckAt);
const claimed = await RankTrackingRepository.claimDueConfig({
  configId: config.id,
  projectId: config.projectId,
  observedNextCheckAt: config.nextCheckAt, // used for optimistic check
  nextCheckAt,
  lastSkipReason: null,
});

```

The `observedNextCheckAt` parameter ensures the update only succeeds if the row hasn't changed since reading. Upon successful claim:

- The `nextCheckAt` timestamp advances using `computeNextCheckAt`
- Any previous `lastSkipReason` is cleared
- The config becomes ineligible for other workers until the next interval

If claiming fails due to concurrent modification, the config is skipped for this tick.

### Stage 4: Workflow Initialization

With the slot secured, `beginRankCheckRun` from [`src/server/features/rank-tracking/services/rankCheckRunGuards.ts`](https://github.com/every-app/open-seo/blob/main/src/server/features/rank-tracking/services/rankCheckRunGuards.ts) launches the actual execution:

```ts
await beginRankCheckRun({
  workflow: env.RANK_CHECK_WORKFLOW,
  config,
  projectId: config.projectId,
  billingCustomer: {/* system user */},
  keywordsTotal: kwCount,
  trigger: "scheduled",
  workflowStartErrorMessage: "Failed to start scheduled workflow",
});

```

The `trigger: "scheduled"` metadata distinguishes automated runs from manual initiations in analytics and billing records.

## Runtime Guards: Budget and Deadline Enforcement

OpenSEO implements two protective mechanisms to ensure reliable operation within infrastructure constraints.

### Task Unit Budget Cap

The constant `SCHEDULED_TASK_UNIT_BUDGET = 1000` limits keyword/device pairs processed per cron tick. With typical configurations requiring approximately 2 API calls per pair, this keeps DataForSEO requests below the 2,000/minute rate limit:

```ts
// In runScheduledRankChecks
const remainingBudget = SCHEDULED_TASK_UNIT_BUDGET - usedBudget;
if (keywordDevicesCount > remainingBudget) {
  // Defer remaining configs to next tick
}

```

### Three-Minute Execution Deadline

The `TICK_DEADLINE_MS = 180000` timer stops processing if the cron exceeds three minutes, ensuring the Worker doesn't exceed platform limits:

| Guard | Constant | Purpose |
|-------|----------|---------|
| Budget | `SCHEDULED_TASK_UNIT_BUDGET = 1000` | Caps DataForSEO API usage per tick |
| Deadline | `TICK_DEADLINE_MS = 3 min` | Prevents Worker timeout violations |

## Failure Recovery and Observability

When `beginRankCheckRun` cannot start a workflow (overlapping run, rate limit, or transient error), the scheduler **restores the original `nextCheckAt`** rather than advancing the schedule. This guarantees the tracker remains due for immediate retry on the next cron invocation.

Success and failure metrics aggregate into a structured log entry tagged `rank_tracking_scheduler_summary`, surfacing in Cloudflare Workers logs with counts of:
- Configs processed
- Tasks budgeted and started
- Errors encountered
- Execution duration

## Computing Next Check Times

The `computeNextCheckAt` utility in [`src/shared/rank-tracking.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/rank-tracking.ts) calculates interval boundaries relative to either:

- The previous `nextCheckAt` (for consistent scheduling alignment)
- Current time (for new or restored trackers)

Supported intervals defined in [`src/types/schemas/rank-tracking.ts`](https://github.com/every-app/open-seo/blob/main/src/types/schemas/rank-tracking.ts):

```ts
export const ScheduledRankTrackingInterval = z.enum([
  "daily",
  "weekly",
  "monthly",
  "manual"
]);

```

## Key Implementation Files

| File | Responsibility |
|------|---------------|
| [`src/server/features/rank-tracking/services/scheduledRankChecks.ts`](https://github.com/every-app/open-seo/blob/main/src/server/features/rank-tracking/services/scheduledRankChecks.ts) | Cron entry point, budget/deadline enforcement |
| [`src/server/features/rank-tracking/services/rankCheckRunGuards.ts`](https://github.com/every-app/open-seo/blob/main/src/server/features/rank-tracking/services/rankCheckRunGuards.ts) | Workflow launch and overlap prevention |
| [`src/shared/rank-tracking.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/rank-tracking.ts) | Time calculation and interval validation |
| [`src/types/schemas/rank-tracking.ts`](https://github.com/every-app/open-seo/blob/main/src/types/schemas/rank-tracking.ts) | Zod schemas for type-safe configuration |
| [`src/server/workflows/RankCheckWorkflow.ts`](https://github.com/every-app/open-seo/blob/main/src/server/workflows/RankCheckWorkflow.ts) | Actual rank checking execution |
| [`src/server/features/rank-tracking/repositories/RankTrackingRepository.ts`](https://github.com/every-app/open-seo/blob/main/src/server/features/rank-tracking/repositories/RankTrackingRepository.ts) | Database operations for due configs and claims |

## Summary

- **Scheduled rank checks in OpenSEO** execute through a cron worker using four coordinated stages: discovery, validation, atomic claiming, and workflow launch.
- **Optimistic locking** via `claimDueConfig` prevents duplicate runs without database transactions.
- **Budget and deadline guards** (1000 task units, 3 minutes) protect against API rate limits and Worker timeouts.
- **Failure recovery** preserves original `nextCheckAt` timestamps when workflows fail to start, ensuring no missed checks.
- **Comprehensive logging** through `rank_tracking_scheduler_summary` enables operational visibility.

## Frequently Asked Questions

### How does OpenSEO prevent the same rank check from running twice?

OpenSEO uses optimistic concurrency control—each worker attempts to atomically claim a configuration by matching the `observedNextCheckAt` timestamp. Only one claim succeeds; others skip to the next due config. The `nextCheckAt` only advances after successful workflow initiation.

### What happens if the cron worker exceeds its three-minute deadline?

Processing stops immediately when `TICK_DEADLINE_MS` elapses. Unprocessed due configs remain in the database with their original `nextCheckAt` timestamps, making them eligible for the next cron invocation. This prevents Worker timeouts while ensuring no data loss.

### Can free-plan organizations use scheduled rank checks?

Scheduled automation (`daily`, `weekly`, `monthly`) requires a paid plan in hosted mode—the `customerHasPaidPlan` check filters free organizations before workflow launch. Free plans may still use `manual` trigger intervals initiated by users.

### Where is the next check time calculated in OpenSEO?

The `computeNextCheckAt` function in [`src/shared/rank-tracking.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/rank-tracking.ts) handles all schedule arithmetic, accepting an interval type and optional anchor timestamp to produce ISO-8601 timestamps. It supports interval rollover logic for consistent scheduling alignment.