---
title: "The sync engine"
description: "How catalogue repricing is scheduled, batched, resumed and finished — and what each job state actually means."
url: "https://maksut.net/price-anchor/docs/sync-engine/"
language: "en-US"
datePublished: "2026-08-02T00:35:16+00:00"
dateModified: "2026-10-10T08:40:00+00:00"
---

# The sync engine

Repricing a catalogue is the cost Price Anchor pays for having a fast storefront. The engine is built so that cost is bounded, resumable and visible.

## Scheduling

Price Anchor uses Action Scheduler when WooCommerce provides it and falls back to WP-Cron when it does not. Both are hidden behind one interface, so nothing in the pricing code cares which is running. Action Scheduler is detected as a capability, never required as a dependency.

## Batching and resumability

- Work is processed in batches using **keyset pagination** — the job stores a cursor rather than an offset, so inserting or deleting products mid-pass cannot cause it to skip or repeat rows.
- Job state — id, cursor, totals, status — is persisted. If a request dies halfway, the next batch continues from the stored cursor instead of starting over.
- A lock prevents two passes running at once. Two workers repricing the same catalogue simultaneously is exactly how you get inconsistent prices.
- Batch size is filterable for stores with unusual hosting constraints.

## Job states

| State | Meaning |
| --- | --- |
| Idle | No pass in progress. This is the normal resting state. |
| Queued | A pass has been requested but background processing has not picked it up yet. |
| Running | Batches are being processed. Progress is shown. |
| Done | Every product in scope has been repriced. |
| Failed | The pass stopped on an error. The reason is in the WooCommerce log. |

The state set is closed, so a corrupted row cannot invent a state the UI does not know how to render.

## Completion

A batch that reaches the job’s total, or returns fewer rows than its limit, has proved there is no required next page. The worker that processes that terminal batch commits *Done* immediately. Earlier behaviour persisted “running” and scheduled an empty follow-up batch, which made completion depend on another cron tick and left a one-product store sitting at “100% Running” with a Cancel button visible after every price was already current.

## When a pass starts

- A validated new rate table is stored.
- A pricing-relevant setting changes: markup, rounding, source currency or provider.
- The WooCommerce store currency changes.
- You press the manual resync control on the Sync tab.

Saving an individual product reprices that product immediately rather than queueing a whole catalogue pass.

## Large catalogues

The engine is designed to be boring at scale, but a first run over tens of thousands of variations is still real work. If your host is aggressive about long-running requests:

- make sure Action Scheduler is available — WooCommerce ships it, so this usually means confirming it is not disabled;
- run the first pass outside peak hours;
- confirm real cron is firing. A low-traffic site relying on WP-Cron’s page-view trigger will reprice slowly simply because nobody is visiting.
