Skip to main content
Version: 2026.3

Runs and Progress

Detection does not run while you edit. A run is started explicitly, processes a scope in the background, and writes its findings into the review screens.

Starting one​

FromHow
The configuration screenSave & Detect (incremental) or Save & Full Detection.
The review screenRerun duplicate analysis (incremental), or Recalculate group for a single reference.
The CLIbin/console bpt:duplicates:analyze
A cronThe same command on a schedule.

A run never executes inline: starting one queues a Generic Execution Engine job and returns immediately. The work happens on the pimcore_backend_power_tools_duplicates_queue transport, so a worker must be consuming it. Only one run per scope is allowed at a time; runs on disjoint scopes may overlap.

bin/console bpt:duplicates:analyze [--scope=objects|assets] [--full]
  • --scope restricts the run to one configured scope. Omitted, every enabled scope runs.
  • --full re-analyses everything instead of only what changed.

Incremental vs full​

These are not simply "less work vs more work" — the choice changes four mechanics.

1. Selection. Incremental sweeps only elements changed since the scope's last completed sweep, plus the references of groups something else invalidated. The very first run for a scope has no watermark to select from, so it falls back to a full sweep automatically.

2. Probe depth. Incremental probes up to max probe chunks (3) of an element's chunk vectors; full uses the lower max probe chunks (full run) (1), because a full pass pays that cost once per element across the whole scope. The cap follows the actual selection, not the requested mode — an incremental run that fell back to full also uses the full cap rather than tripling its probes.

3. Retraction. Both modes can remove findings, but only within what they actually looked at. An incremental sweep withdraws the groups of references it just re-evaluated that no longer produce a cluster — those genuinely have no duplicates left — and the reconcile step, which is queued in both modes, always performs its out-of-scope cleanup. What is reserved for a completed full run is the run-wide withdrawal of every group the run did not reproduce: after an incremental sweep "not visited" cannot be told apart from "no longer a duplicate", so applying that rule would delete live findings from the part of the scope the sweep never reached.

4. Floor safeguard. Scores below the candidate floor were never stored, so after lowering a floor, incremental runs are blocked until a full run rewrites the dataset. Raising a floor needs no full run. See Stored vs shown.

Rule of thumb: Detect is the everyday, inexpensive choice, and it only ever retracts findings about elements it re-examined. Full is for after a configuration change that alters what counts as a duplicate, or to clear out findings nothing reproduces any more — which is why its confirmation is styled as destructive.

Following a run​

The configuration screen shows a banner while a run is active and the last run's result when idle. For monitoring from outside Studio, the status endpoint behind that banner is:

GET /configurations/{configurationId}/analysis-status

PIMCORE_ADMIN-gated, and deliberately separate from the configuration GET so that polling it never refetches the configuration while someone is mid-edit in the form.

Why the progress number is trustworthy​

Both execution budgets stop a sweep at a page boundary, never mid-page — the runtime budget on either tier, the element budget on the fuzzy tier only — and the tier that is running records that boundary in the run's single, tier-tagged resume cursor — for the exact tier the aggregation's continuation token, for the fuzzy tier the last element id visited, overwriting the exact tier's. scanned therefore always reflects whole pages actually processed, never a partially applied one.

The cursor is stored per job run. It lets a retried step carry on where it left off inside that run; it is not carried into the next invocation, which starts its sweep from the beginning.

groupsFound is per run, not the scope's whole inventory. It counts only the groups this run wrote: on a full run it grows to match the review list's own count, on an incremental run only to the groups that run confirmed.

Cancelling​

DELETE /analyze

Use it to stop a run started by mistake, or to recover one flagged staleWorkerSuspected.

It cancels the running job run, and that is all — there is no queue purge. Every duplicate message handler re-checks the job run's live state at its own entry, so once the state is no longer RUNNING, anything still queued self-drains as a no-op the moment a worker picks it up.

Cancel stops the run within one page, not instantly. A worker mid-page finishes that page — whose progress is already checkpointed — before the next step message hits the cancelled state and drains.

It is idempotent. Calling it with nothing running is a no-op that still returns 200; there is no error case for "nothing to cancel".

Cron runs​

A run started by a scheduled bpt:duplicates:analyze has no logged-in user to notify, so it never pushes a Mercure event — the job-run subscriber skips any run owned by the system owner. This does not affect the status endpoint: polling it reports a cron run's progress and completion exactly like a Studio-triggered one, because it reads the job run and the run record directly rather than depending on the notification.