Automattic / Automattic/agents-api
Routines AS bridge: register() unschedule scan degrades with canceled rows in the group; per-action generation stamps leak into wp_options (257k rows observed)
- Dominant language
- PHP
- Stars
- 33
- Forks
- 8
- Avg merge
- 1h 36m
- Merged PRs (30d)
- 31
Description
## Summary
Two production-observed durability problems in `WP_Agent_Routine_Action_Scheduler_Bridge` (`src/Routines/class-wp-agent-routine-action-scheduler-bridge.php`, consumed via Data Machine on a WordPress multisite with 717 persisted routines). Both surfaced during a consumer-side reconcile storm (Extra-Chill/data-machine#3493), but the substrate behaviours are independent of that bug.
### 1. `register()` unschedule scan degrades with canceled rows in the group
`register()` calls `as_unschedule_all_actions( SCHEDULED_HOOK, $logical_args, GROUP )` for idempotency. Action Scheduler resolves that to `SELECT a.action_id FROM …actionscheduler_actions a LEFT JOIN …actionscheduler_groups g ON g.group_id=a.group_id WHERE g.slug='agents-api' AND a.hook='wp_agent_routine_run_scheduled' AND a.args=… AND a.status='pending'`. MariaDB planned it on the `group_id` index, so every call scans every row in the `agents-api` group — including the canceled rows that each previous `register()` produced. Because the bridge cancels (not deletes) superseded chains, the group accumulates canceled rows monotonically and each subsequent `register()` gets slower: O(n²) across a re-registration pass.
Observed: 0 → 44k canceled rows in one hour; 60 MariaDB threads pinned in `Sending data` on that query; purging the canceled rows returned each call to milliseconds instantly.
The comment on line 146 ("exact-match store query, not a scan") is true only while the group is small.
Options: constrain the lookup to `status='pending'` on an index AS can use (hook+status), delete superseded pending actions instead of cancelling them, or register a retention rule for the routine hook so canceled rows don't accumulate.
### 2. Per-action generation stamps leak into `wp_options`
`stamp_stored_action()` writes one `agents_routine_action_generation_` option per stored action (initial schedule **and every recurrence successor**). The option is only deleted in `cancel()` — never when Action Scheduler completes, fails, or purges the action itself.
Observed on one site: **257,130 `agents_routine_action_generation_*` rows** in `wp_options` (258,831 total rows; 717 are legitimate `agents_routine_generation_*` stamps). Every recurring routine tick leaks one row forever.
Options: store the generation on the action (AS `extended_args`/meta) rather than in `wp_options`; or delete the stamp on `action_scheduler_completed_action` / `action_scheduler_failed_action` / `action_scheduler_deleted_action`; or move to a single keyed option with pruning. A migration/cleanup helper for existing installs would also be needed.
## Environment
agents-api `dev-main` @ `96806e2e07f14677ed666f33ae8f11ed7cd72025` (vendored in data-machine 0.176.8), WordPress 7.0 multisite, MariaDB, PHP 8.4, Action Scheduler via WooCommerce.
Contributor guide
No contributing guide indexed for this repository
Research direction
Read src/Routines/class-wp-agent-routine-action-scheduler-bridge.php, focusing on register(), stamp_stored_action(), and cancel(). Trace how pending and canceled actions are queried and how generation options are created and removed, then review the Action Scheduler completion, failure, and deletion hooks named in the issue. Done means the chosen design prevents canceled-row scan growth, stops per-action option leakage, and addresses existing leaked options.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- php, wordpress
- Domain
- backend, databases, performance
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100