OllieJones / index-wp-mysql-for-speed

A plugin to add useful indexes to your WordPress installation's MySQL database.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Improve performance of Woocommerce Memberships?

infra-biboo opened this issue · comments

Hi, Maybe this has nothing to do with the creation of indexes, but I prefer to leave the doubt.

Woocommerce memberships validates if a user has access to a plan, consulting one by one.

The average of each query is 0.005 according to Query Monitor.

This on sites with 100 plans might be acceptable.

But in sites with 1000 or 2000 plans, WCM queries could easily reach 2 seconds of waiting, without considering the rest of the information such as posts, products, orders and users. This could easily reach 4 seconds for each page visited.

In my case apply some tricks: Disable the plugin for the front end ( Mu Plugins), and only do the permission query when I load the content via custom ajax (all plugins are disabled with this ajax query and I enable only the necessary: ​​woocommerce + woocommerce memberships).

It works quite well, but it still does the query in each of the membership plans that exist, so if there are many, the response is slow.

The query performed for each plan created is as follows:

SELECT wp_posts.ID
FROM wp_posts
WHERE 1=1
AND wp_posts.post_parent = 54248
AND wp_posts.post_author IN (1)
AND wp_posts.post_type = 'wc_user_membership'
AND ((wp_posts.post_status <> 'trash'
AND wp_posts.post_status <> 'auto-draft'))
ORDER BY wp_posts.post_date DESC
LIMIT 0, 5

Is it possible to improve this through indexes? Or is it simply something that has to do with the construction of the plugin itself?

Thank you very much for your reply.
Best regards.

Pablo

But in sites with 1000 or 2000 plans, WCM queries could easily reach 2 seconds of waiting, without considering the rest of the information such as posts, products, orders and users. This could easily reach 4 seconds for each page visited.

But WCM indexes plans into an autoloaded (big) option.

Can you screenshot QM output and point out what you're seeing there?

Hello @lkraav , of course, here I attach them:

PD: I have about 580 plans created and 580 products, 11k orders (all status), 11k users, 5k posts, 1k comments and 90 active subscriptions.

Captura de Pantalla 2022-07-12 a la(s) 08 34 16
Captura de Pantalla 2022-07-12 a la(s) 08 37 28
Captura de Pantalla 2022-07-12 a la(s) 08 42 34

It's unclear if it's a front-end or backend query. Always post URL for clarity.

I don't see a "plan" in any of the SELECTs. Maybe it is "post_parent"? Or maybe the "ID" is used to look in some other table?

Does Woo run a separate query for each possible "plan"? If so, it is a failing of Woo, itself, and not indexing, that is the primary problem.

I don't see a "plan" in any of the SELECTs. Maybe it is "post_parent"? Or maybe the "ID" is used to look in some other table?

Does Woo run a separate query for each possible "plan"? If so, it is a failing of Woo, itself, and not indexing, that is the primary problem.

Exactly, each membership created uses this type of taxonomy: post_type=wc_user_membership, which is also a child of post_type=wc_membership_plan.

The author of post_type=wc_user_membership is the user, not an admin.

Every time a protected content is accessed. WCM looks for the user to have an active "wc_user_membership" post and to which ( wc_membership_plan ) or parent post it corresponds.

These queries are done one by one for each wc_membership_plan created.

Thanks for your reply.

It's unclear if it's a front-end or backend query. Always post URL for clarity.

Yes, it is a front end query.

It looks like the various membership plans are posts with type wc_membership_plan. The second query in your QM screen dump looks like it fetches those.

It looks to me like each different membership is a post (a row in wp_posts) with type wc_user_membership and a particular value of post_parent. And, it looks like the code retrieves those lists of members one by one, with a query for each plan as you say.

I have to guess all this because WooCommerce Memberships is closed-source so I can't look at it.

This is a scalability issue within the code of that closed-source plugin. I guess the author of that plugin didn't contemplate a user with many hundreds of membership plans. The parent index on wp_posts is certainly useful to run those queries that retrieve lists of memberships. Each query is quick, but there are too many of them, as you say.

What can you do about this besides what you've already tried?

  • It's possible a persistent object cache can help you. If it will, you'll probably see one or more UPDATE wp_options queries right after each of those thousands of queries. It's worth installing a persistent object cache on a busy site in any case. But it requires help from your hosting provider, and the "budget" hosts won't do that.
  • File a defect report with WooCommerce; contact their very competent support staff to do that.

Better indexes can't make a difference to your issue, sad to say.

Regardless of Ollie's conclusions, my team will also look into this, since we're approaching 100+ plans ourselves. Can't promise a timeline, but I'll post here when I have something.

@lkraav - Query 42 would benefit from INDEX(post_type, post_status, post_date)

Query 51 (etc): INDEX(post_parent, post_type, post_author, post_date, post_status)

Contact me if you would like further discussion about indexing.

Regardless of Ollie's conclusions, my team will also look into this, since we're approaching 100+ plans ourselves. Can't promise a timeline, but I'll post here when I have something.

Hello @lkraav, I was looking for an alternative for memberships. And I found a plugin that grants memberships per post, pages and custom types.

It's called Groups https://wordpress.org/plugins/groups/

It is compatible with woocommerce and woocommerce subscriptions: https://woocommerce.com/products/groups-woocommerce/

The woocommerce groups plugin checks via get_meta_user if you have access to a group or not (only groups that were assigned by purchase or manually are saved). The best thing is that it does not check the total of the plans as in WCM.

You can also buy the plugin for categories (https://www.itthinx.com/shop/groups-restrict-categories/) and then check in the loop with something like this:

$id_group_by_user = get_user_meta($user_id, '_groups_buckets' );
$id_group_by_term = get_term_meta($term_id, 'groups-read');

if (array_intersect($ids_group_by_user, $ids_group_by_term) == true ) {
            $has_access = true;
            get_template_part( 'template-parts/content');
} else {
            $has_access = false;
}

This can be taken to pages, posts, products, terms, etc.

As you can see, it works on a meta user and meta term basis, so it is not limited to WCM's one-to-one verification. Since only the exact data is saved in the meta.

Possibly this can also be optimized at the index level (17 monitor queries / loaded data: 20k users - 120 groups - 4k categories).

As I am not an expert and I only tried under what I think can be fast. If you feel that Metadata validation may be slow or not optimal, I would appreciate your feedback.

PS: I did the check with array_intersect to check for multiple group ids from the categories and from the user.
Example: $id_group_by_user = array(1,2,3,4), $id_group_by_term = array(2, 5), this will return true since 2 is the ID of the group to which the user has permission. If you know of a more elegant or better solution, please let me know. It's hard for me because my forte is Product Design haha

I hope my research can help you.
Best regards.