Automatic cleanup of old repeater "ready" items
Short description
ProcessWire creates a hidden + unpublished "ready" item for a repeater each time an edit form renders, and never reaps them. The cleanup routine already exists and is complete, but the only way to trigger it is a checkbox on each field's config screen. On a long lived site the items accumulate indefinitely.
The core source already asks this question, in FieldtypeRepeater/config.php next to the checkbox:
// @todo: should we just do this automatically?
$numOldReady = $fieldtype->countOldReadyPages($this->field);
Why it matters
On a site running since 2022 with 37 repeater fields:
- 19,197 ready items, of which 18,842 are older than the 3 day threshold
countOldReadyPages() already uses
- That is 33% of all pages on the site
- One field,
icon_grid, had 15,242 ready items against 20 real ones
They are not inert. Repeater deletes cascade through deleteRepeaterPage() with children('include=all'), so the orphans are walked and deleted too. One RepeaterMatrix item whose nested parent held 363 orphans took ~12 seconds to remove, which showed up as gateway timeouts when an editor deleted a few blocks and saved.
What already exists
FieldtypeRepeater::countOldReadyPages($field, $delete = false, $secondsOld = 259200) does the whole job. Nothing calls it with $delete = true except the config screen POST handler.
Requests
-
Run it automatically, or provide a documented hook that a site can schedule. Given deletion can be slow (see processwire-issues for FieldtypePage::hookPagesDelete), doing it inline during a normal request is probably the wrong place - a LazyCron or a maintenance entry point would fit better.
-
Bound the existing button. The _deleteOldReady handler calls countOldReadyPages($field, true), which finds and deletes the whole backlog in one request with no set_time_limit() and no limit - unlike the neighbouring "unnecessary pages" handler, which sets set_time_limit(600) and caps at 500 with a "run this again" notice. On a large backlog the button reliably times out, which is exactly when a site most needs it. Applying the same cap and notice would make it usable.
-
Consider reusing ready items more aggressively. On the site above, created and modified for recent ready items match exactly, meaning no existing ready item is ever reused - a fresh one is minted per render (~50/day across two fields). getNextReadyPage() only reuses an item present in the loaded value, so when a nested repeater's field row is absent the existing pool is invisible to it and a new item is created every time.
Workaround
A CLI script that calls countOldReadyPages($field, true) per field, bounded by count and wall clock so it can run from cron and resume across runs.
Environment
- ProcessWire 3.0.270, PHP 8.5, MySQL 8 (AWS RDS)
- 37 repeater / RepeaterMatrix fields, ~56,000 pages
Automatic cleanup of old repeater "ready" items
Short description
ProcessWire creates a hidden + unpublished "ready" item for a repeater each time an edit form renders, and never reaps them. The cleanup routine already exists and is complete, but the only way to trigger it is a checkbox on each field's config screen. On a long lived site the items accumulate indefinitely.
The core source already asks this question, in
FieldtypeRepeater/config.phpnext to the checkbox:Why it matters
On a site running since 2022 with 37 repeater fields:
countOldReadyPages()already usesicon_grid, had 15,242 ready items against 20 real onesThey are not inert. Repeater deletes cascade through
deleteRepeaterPage()withchildren('include=all'), so the orphans are walked and deleted too. One RepeaterMatrix item whose nested parent held 363 orphans took ~12 seconds to remove, which showed up as gateway timeouts when an editor deleted a few blocks and saved.What already exists
FieldtypeRepeater::countOldReadyPages($field, $delete = false, $secondsOld = 259200)does the whole job. Nothing calls it with$delete = trueexcept the config screen POST handler.Requests
Run it automatically, or provide a documented hook that a site can schedule. Given deletion can be slow (see processwire-issues for
FieldtypePage::hookPagesDelete), doing it inline during a normal request is probably the wrong place - a LazyCron or a maintenance entry point would fit better.Bound the existing button. The
_deleteOldReadyhandler callscountOldReadyPages($field, true), which finds and deletes the whole backlog in one request with noset_time_limit()and no limit - unlike the neighbouring "unnecessary pages" handler, which setsset_time_limit(600)and caps at 500 with a "run this again" notice. On a large backlog the button reliably times out, which is exactly when a site most needs it. Applying the same cap and notice would make it usable.Consider reusing ready items more aggressively. On the site above,
createdandmodifiedfor recent ready items match exactly, meaning no existing ready item is ever reused - a fresh one is minted per render (~50/day across two fields).getNextReadyPage()only reuses an item present in the loaded value, so when a nested repeater's field row is absent the existing pool is invisible to it and a new item is created every time.Workaround
A CLI script that calls
countOldReadyPages($field, true)per field, bounded by count and wall clock so it can run from cron and resume across runs.Environment