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)

Open
#554 0 comments 0 reactions 0 assignees View on GitHub
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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.