Skip to content

Two as_has_scheduled_action() queries on every pageload (front-end included) #1884

@dd32

Description

@dd32

Note: This issue was authored with AI assistance (Claude Code) and has not yet been reviewed by a human. Please review the details before acting on it.

On every request — including front-end pageviews where no Stream UI is rendered — Stream issues two duplicate queries against the Action Scheduler table to check whether the async deletion is running.

Sample (table prefix anonymised):

SELECT a.action_id FROM wp_actionscheduler_actions a
  WHERE 1=1 AND a.hook='stream_erase_large_records_action'
    AND a.status IN ('in-progress', 'pending')
  LIMIT 0, 1

Backtrace (abridged):

do_action('init')
  → WP_Stream\Plugin::init               (classes/class-plugin.php:252)
  → new WP_Stream\Settings               (classes/class-settings.php:59)
  → Settings::get_options                (classes/class-settings.php:554)
  → Settings::get_defaults               (classes/class-settings.php:580)
  → Settings::get_fields                 (classes/class-settings.php:299)
    ├── Admin::is_running_async_deletion (classes/class-settings.php:361)   ← query #1
    └── Settings::get_deletion_warning   (classes/class-settings.php:370)
        └── Admin::is_running_async_deletion (classes/class-settings.php:602) ← query #2

Root cause

Settings::__construct eagerly populates $this->options = $this->get_options() on the init hook (priority 9). get_options() needs defaults, get_defaults() derives them by walking get_fields(), and get_fields() happens to embed two pieces of dynamic UI state — the type and the desc of the delete_all_records field — both of which call Admin::is_running_async_deletion() (classes/class-admin.php:716), which in turn calls as_has_scheduled_action() with no caching.

The check is purely admin UI state: its only two callers decide whether to render a "deletion in progress" warning on the Stream settings screen. Computing it on front-end pageloads is unnecessary, and computing it twice in the same admin pageload is redundant.

Impact

Two DB hits on every pageload of every site that has Stream active, regardless of where the request is heading. Individually cheap (~0.5 ms each, indexed query), but the queries serve no purpose outside the Stream settings screen.

Suggested fix

Short-circuit Admin::is_running_async_deletion() outside admin context, and memoise per-request:

public static function is_running_async_deletion() {
    if ( ! is_admin() ) {
        return false;
    }
    static $cached = null;
    return $cached ??= as_has_scheduled_action( self::ASYNC_DELETION_ACTION );
}

Both existing callers are admin UI render paths, so is_admin() is a safe gate. Happy to send a PR.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions