Opened 4 months ago
Last modified 5 weeks ago
#63938 new enhancement
A feature to bulk schedule events
| Reported by: |
|
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
#2
@
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
@
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".
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)