Summary: Helpful code for scaling WooCommerce

In this article, we present code snippets and strategies we found out during our investigations to fine-tune your WooCommerce store, helping it to run smoothly and efficiently at scale.

1. Disable the total posts limit in ElasticSearch response.

By default, ElasticSearch limits the number of search results it returns. In our case, it was set to 10,000. However, you can disable this limit by modifying your ElasticSearch query with the following code snippet:

/**
 * Disable total posts limit in ElasticSearch response.
 *
 * @param array $formatted_args ElasticPress request arguments.
 *
 * @return array
 */
function elbytes_elasticpress_disable_total_posts_limit( $formatted_args ) {
	$formatted_args['track_total_hits'] = true;
	return $formatted_args;
}
add_filter( 'ep_formatted_args', 'elbytes_elasticpress_disable_total_posts_limit' );

2. Remove post custom fields metabox from all post types.

WooCommerce stores product data within the wp_postmeta table, which, in our case, currently has 45 million rows. This amount of data significantly impacts the speed of the website, particularly on the edit post page. This is primarily due to WordPress rendering the custom fields metabox, leading to resource-intensive database queries that encompass all 45 million rows. You can use the following code snippet to remove custom fields metabox from all post types:

/**
 * Remove post custom fields metabox from all post types.
 *
 * @return void
 */
function elbytes_remove_post_custom_fields_metabox() {
	$types = get_post_types( [], 'names' );
	foreach ( $types as $type ) {
		remove_meta_box( 'postcustom', $type, 'normal' );
	}
}
add_action( 'add_meta_boxes', 'elbytes_remove_post_custom_fields_metabox', 999 );

3. Taxonomies optimization for WooCommerce.

We had an issue with WooCommerce, where it recalculates the product count for each taxonomy term every time the get_terms() function is called. This became problematic as we have an extensive list of terms, and pages with filtering options took 30 to 60 seconds to load, severely affecting user experience. To solve this issue, we took three steps to get a solution

Disabling terms count calculation on the frontend

if ( ! is_admin() || wp_doing_ajax() ) {
	remove_filter( 'get_terms', 'wc_change_term_counts', 10, 2 );
}

Disabling terms count calculation during product updates

Our idea was to disable terms count calculation during product post type update and use CRON task to do it from time to time.

if ( ! wp_doing_cron() ) {
	remove_action( 'transition_post_status', '_update_term_count_on_transition_post_status' );

	add_filter( 'woocommerce_product_recount_terms', '__return_false' );
	add_action( 'transition_post_status', 'elbytes_maybe_update_term_count_on_transition_post_status', 10, 3 );
}

/**
 * Update terms count on transition post status if post type is not product.
 *
 * @param string  $new_status New post status.
 * @param string  $old_status Old post status.
 * @param WP_Post $post       Post object.
 */
function elbytes_maybe_update_term_count_on_transition_post_status( string $new_status, string $old_status, WP_Post $post ) {
	if ( $post->post_type === 'product' ) {
		return false;
	}

	// Update counts for the post's terms if not woocommerce product.
	foreach ( (array) get_object_taxonomies( $post->post_type ) as $taxonomy ) {
		$tt_ids = wp_get_object_terms( $post->ID, $taxonomy, [ 'fields' => 'tt_ids' ] );
		wp_update_term_count( $tt_ids, $taxonomy );
	}
}

Adding a background CRON task for products count calculation

/**
 * Schedule CRON task to update terms count every hour.
 *
 * @return void
 */
if ( ! wp_next_scheduled( 'elbytes_update_terms_count' ) ) {
	wp_schedule_event( time(), 'hourly', 'elbytes_update_terms_count' );
}
add_action( 'elbytes_update_terms_count', 'elbytes_update_terms_count_callback' );

/**
 * Update term count meta.
 *
 * @return void
 */
function elbytes_update_terms_count_callback() {
	$taxonomies        = [ 'product_cat', 'product_tag' ];
	$wc_product_terms  = [];
	$product_terms_ids = [];

	foreach ( $taxonomies as $taxonomy ) {
		$terms = get_terms(
			[
				'taxonomy'   => $taxonomy,
				'hide_empty' => false,
			]
		);

		if ( $terms && ! is_wp_error( $terms ) ) {
			foreach ( $terms as $term ) {
				$wc_product_terms[ $term->term_id ] = $term->parent;

				$product_terms_ids[] = $term->term_id;
			}

			// Updated terms count and save to database.
			_wc_term_recount( $wc_product_terms, get_taxonomy( $taxonomy ), false, false );

			wp_update_term_count( $product_terms_ids, $taxonomy );
		}
	}
}

For a more in-depth look into our optimization journey, please refer to the dedicated post “1.2M products: Optimizing the product update journey in WP Admin”.

4. Woocommerce orders search by customer

In our WooCommerce setup, managing over 500,000 orders became a challenge, particularly when searching for orders by customers within the WordPress Admin. After some research, we found a crucial performance bottleneck – the search function relied on the WP_User query to get customers and retrieve associated orders. We tackled that by replacing the existing code with a database query that leverages WooCommerce’s lookup tables.

function search_woo_customer_hpos( $result, $term, $limit ) {
    if ( class_exists( 'Automattic\WooCommerce\Utilities\OrderUtil' ) && OrderUtil::custom_orders_table_usage_is_enabled() ) {
	global $wpdb;

	$term    = '%' . $term . '%';
	$results = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
		$wpdb->prepare(
			"SELECT user_id FROM {$wpdb->prefix}wc_customer_lookup
			WHERE CONCAT(first_name, ' ', last_name) LIKE %s
			OR username  LIKE %s
			OR email LIKE %s",
			$term,
			$term,
			$term
		),
		ARRAY_A
	);

	$results = array_slice( $results, 0, $limit );

	$result = [];
	foreach ( $results as $item ) {
		$result[] = $item['user_id'];
	}
    }

    return $result;
}
add_filter( 'woocommerce_customer_pre_search_customers', 'search_woo_customer_hpos', 10, 3 );

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top