Make WordPress Core

Opened 4 months ago

Last modified 5 weeks ago

#63938 new enhancement

A feature to bulk schedule events

Reported by: urlund's profile urlund Owned by:
Milestone: Awaiting Review Priority: normal
Severity: normal Version:
Component: Cron API Keywords: has-patch reporter-feedback
Focuses: Cc:

Description

I was playing around today with wp_schedule_single_event, and when scheduling a bunch of events (1000) the traditional way if scheduling becomes rather slow. So instead I made a solution for a wp_schedule_bulk_events function.

I'd like to submit this to WP core if you'd like, but not sure how :)

I hope the rest speaks for itself. Let me know if you have any suggestions or adjustments.

<?php
/**
 * Schedule a bulk of events.
 *
 * @param int    $timestamp  Unix timestamp (UTC) for when to next run the event.
 * @param string $hook       Action hook to execute when the event is run.
 * @param array  $args       An array of arrays containing arguments to pass to the
 *                           hook's callback function. The array keys are ignored.
 * @param bool   $wp_error   Optional. Whether to return a WP_Error on failure. Default false.
 * @return bool|WP_Error True if event successfully scheduled. False or WP_Error on failure.
 */
function wp_schedule_bulk_events(int $timestamp, string $hook, array $args, bool $wp_error = false)
{
    // check if all arguments are arrays
    $is_array_args = array_reduce( $args, fn($carry, $item) => $carry && is_array($item), true );

    // if not, schedule a single event
    if ( ! $is_array_args) {
        return wp_schedule_single_event( $timestamp, $hook, $args, $wp_error );
    }

    // get current cron events
    $bulk_value = $orig_value = get_option( 'cron', [] );

    // when updating the cron option, keep a copy of the original value (to skip update for now)
    $tmp_update_option = function ($new_value, $old_value) use (&$bulk_value) {
        $bulk_value = $new_value;
        return $old_value;
    };

    // when getting the cron option, return the modified value
    $tmp_get_option = function () use (&$bulk_value) {
        return $bulk_value;
    };

    // add filters from above
    add_filter( 'pre_update_option_cron', $tmp_update_option, 10, 2 );
    add_filter( 'pre_option_cron', $tmp_get_option, 10 );

    // add cron events
    foreach ( $args as $arg ) {
        wp_schedule_single_event( $timestamp, $hook, $arg, $wp_error );
    }

    // remove filters
    remove_filter( 'pre_update_option_cron', $tmp_update_option, 10, 2 );
    remove_filter( 'pre_option_cron', $tmp_get_option, 10 );

    // if the cron option was changed in the meantime, abort
    if ($orig_value !== get_option( 'cron', [] )) {
        return $wp_error ? new \WP_Error('wp_schedule_bulk_events_failed', __('Failed to verify bulk schedule cron events, value has changed', 'wordpress')) : false;
    }

    // update cron option
    if ( ! update_option( 'cron', $bulk_value )) {
        return $wp_error ? new \WP_Error('wp_schedule_bulk_events_failed', __('Failed to update bulk schedule cron events', 'wordpress')) : false;
    }

    return true;
}

/**
 * The test
 */

// args for test events
$test_args = array_map( fn($i) => [$i], range(1, 1000) );
$timestamp = time() + 3600;

// test 1 - traditional scheduling
$start_time = microtime(true);
foreach ( $test_args as $test_arg ) {
    wp_schedule_single_event( $timestamp, 'test1', $test_arg );
}

echo "Traditional scheduling time: " . round( microtime(true) - $start_time, 5 ) . " seconds" . PHP_EOL;

// test 2 - bulk scheduling
$start_time = microtime(true);
wp_schedule_bulk_events( $timestamp, 'test2', $test_args );

echo "Bulk scheduling time: " . round( microtime(true) - $start_time, 5 ) . " seconds" . PHP_EOL;

Result:
Traditional scheduling time: 7.82431 seconds
Bulk scheduling time: 0.02945 seconds

Change History (4)

This ticket was mentioned in PR #9768 on WordPress/wordpress-develop by urlund.


4 months ago
#1

  • Keywords has-patch added

Introduces wp_schedule_bulk_events to allow scheduling multiple cron events at once. This function optimizes bulk scheduling by batching updates to the cron option and includes error handling for concurrent modifications.

Trac ticket: [](https://core.trac.wordpress.org/ticket/63938)

#2 @johnbillion
4 months ago

  • Keywords reporter-feedback added

Thanks for the PR @urlund but what's the real life use case for such a function? Did you have a real need to schedule 1,000 cron events that all have an identical timestamp and hook name, or was this more of an academic exercise?

#3 @urlund
4 months ago

@johnbillion thanks for asking, the real life use case is a customer who generates reports on custom post types, and another case where a customer is exporting order data to an old C5 integration, both using bulk actions, and the handle_bulk_actions-{$screen} filter.

Typically they submit between 100-200 items in each bulk action.

This is how we currently handle it in the hook callback:

<?php
$event_args = array_map(function ($i) {
    return [$i];
}, $items);

// split $event_args into chunks
$event_arg_chunks = array_chunk($event_args, apply_filters('generate_ticket_report_chunk_size', 10));

// event scheduling
foreach ($event_arg_chunks as $i => $event_args) {
    $timestamp  = time() + ($i * 60);
    foreach ($event_args as $event_arg) {
        wp_schedule_single_event($timestamp, 'generate_ticket_report', $event_arg);
    }
}

Depending in the number of items, server load, etc. this takes around 1-2 sec. to complete.

If I instead change this to "bulk scheduling", like this:

<?php
foreach ($event_arg_chunks as $i => $event_arg_chunk) {
    $timestamp  = time() + ($i * 60);
    wp_schedule_bulk_events($timestamp, 'generate_ticket_report', $event_arg_chunk);
}

My execution time is now around 0.1-0.3 sec. instead.

So the test case in my code from the first example was just to overstate my point that savings in execution time can be made using a "bulk transaction".

Last edited 4 months ago by urlund (previous) (diff)

This ticket was mentioned in Slack in #accessibility by joedolson. View the logs.


5 weeks ago

Note: See TracTickets for help on using tickets.