0

I have a site based on WooCommerce Memberships with the Teams for WooCommerce Memberships extension. 95% of the time it copes very well despite a large user base (>70,000 users). We get about 10k daily visits with response times under one second.

There are seasonal peaks where users have to complete training and the site can crash under load of concurrent uncached requests.

Using Query Monitor there's one query which sticks out like a sore thumb: 0.0375s (average is < 0.0050s). This is a query that checks:

  1. Is the current user a member of a TEAM with an ACTIVE subscription?

  2. Do they have permission to view this post?

Here is the query itself:

    SELECT wp_posts.*
    FROM wp_posts
    LEFT JOIN wp_postmeta _teams_pm
    ON wp_posts.ID = _teams_pm.post_id
    AND _teams_pm.meta_key = '_member_id'
    AND _teams_pm.meta_value = 2
    LEFT JOIN wp_usermeta _teams_um
    ON _teams_um.user_id = _teams_pm.meta_value
    AND _teams_um.meta_key = CONCAT( '_wc_memberships_for_teams_team_', wp_posts.ID, '_role' )
    WHERE 1=1
    AND ((wp_posts.post_type = 'wc_memberships_team'
    AND (wp_posts.post_status = 'publish'
    OR wp_posts.post_status = 'acf-disabled'
    OR wp_posts.post_status = 'private')))
    AND ( _teams_um.meta_value    IN('manager', 'member')
    OR wp_posts.post_author = 2 )
    ORDER BY wp_posts.post_date DESC

It is called by:

"WP_Query->get_posts()
wp-includes/class-wp-query.php:3111
WP_Query->query()
wp-includes/class-wp-query.php:3542
WP_Query->__construct()
wp-includes/class-wp-query.php:3653
SkyVerge\W\M\T\Teams_Handler->get_teams()
wp-content/plugins/woocommerce-memberships-for-teams/src/Teams_Handler.php:446
wc_memberships_for_teams_get_teams()
wp-content/plugins/woocommerce-memberships-for-teams/src/Functions/Teams.php:100
ctz_membership_get_user_team_id()
wp-content/plugins/core-functionality/temp/wc_teams.php:603"

Does anyone have any ideas as to what indexes with MySQL could help speed this up? I'm not familiar enough with SQL to know the best place to index.

We have a big spike anticipated on Monday so I'm hoping to get something in place whilst the site is quiet this weekend.

Thanks in advance!

Edit: output of explain, with prefix and db name anonymised:

+----+-------------+---------------+------------+-------+------------------------------+------------------+---------+----------------------------------------------+------+----------+---------------------------------------+
| id | select_type | table         | partitions | type  | possible_keys                | key              | key_len | ref                                          | rows | filtered | Extra                                 |
+----+-------------+---------------+------------+-------+------------------------------+------------------+---------+----------------------------------------------+------+----------+---------------------------------------+
|  1 | SIMPLE      | wp_posts | NULL       | range | type_status_date,post_author | type_status_date | 164     | NULL                                         | 5556 |   100.00 | Using index condition; Using filesort |
|  1 | SIMPLE      | _teams_pm     | NULL       | ref   | PRIMARY,meta_key,meta_value  | PRIMARY          | 1030    | dbname.wp_posts.ID,const    |    1 |   100.00 | Using where                           |
|  1 | SIMPLE      | _teams_um     | NULL       | ref   | PRIMARY,meta_key             | PRIMARY          | 1030    | dbname._teams_pm.meta_value,func |    1 |   100.00 | Using where                           |
+----+-------------+---------------+------------+-------+------------------------------+------------------+---------+----------------------------------------------+------+----------+---------------------------------------+
3 rows in set, 4 warnings (0.00 sec)

Curious Toad
  • 78
  • 11
  • Did you check questions like these: [How do I identify slow queries missing an index?](https://stackoverflow.com/questions/56015661/how-do-i-identify-slow-queries-missing-an-index) ? Please add the output from `EXPLAIN ...` – Luuk Feb 19 '22 at 10:50
  • I have before and if I'm honest, SQL isn't my speciality, so I have a tough time understanding the nitty gritty of explain outputs. What I can see is that it's using a filesort index. – Curious Toad Feb 19 '22 at 11:54
  • What you can do, it copy/paste the output of the EXPLAIN statement, and add it to this question. (That's why there is an [edit] option, to improve your question ) – Luuk Feb 19 '22 at 12:22
  • D'oh! You're right, I should have done that. I looked at thr character limit for comments and didn't think about editing the question. It's been a long time week! I'll edit now. – Curious Toad Feb 19 '22 at 13:32
  • Here is a plugin that adds indexes to help your query, and similar queries. https://wordpress.org/plugins/index-wp-mysql-for-speed/ – O. Jones Feb 19 '22 at 14:28
  • 1
    Thanks Oliver! Thought I recognised your name. I've actually used that plugin for the last couple of months. Interestingly I see it adds three separate indexes on meta_value - can I ask why that is out of curiosity? – Curious Toad Feb 19 '22 at 15:21

2 Answers2

0

That plugin should help with some of the query.

This might help for wp_posts

INDEX(post_type, post_status, post_author, ID, post_date)

As for multiple indexes on a table... Different queries need different indexes. MySQL will use only one [with rare exceptions] index per table reference. There are essentially 3 queries going on in your query, one against posts and two against postmeta.

The EXPLAIN you provided does not seem to be consistent. Please provide SHOW CREATE TABLE for the two tables so I can figure out what is going on (and better answer your question for Oliver).

The order of columns in an INDEX matters; the order in a WHERE clause does not.

There are two problematic areas in the query; avoiding them may help with performance: CONCAT and OR. This is the OR:

AND ( _teams_um.meta_value    IN('manager', 'member')
      OR wp_posts.post_author = 2 )

No indexing can help. But, because of the needs of the app, it may not be possible to improve on it.

Rick James
  • 135,179
  • 13
  • 127
  • 222
  • Thank you very much for your help here, Rick, and sorry for the delay in replying. I've been flat out on another project and returning to this now. A quick question - as I understand it, multiple indexes could also affect performance on saving as it has to do more work. Is this a tradeoff between front end and back end performance? – Curious Toad Mar 07 '22 at 10:53
  • @CuriousToad - Extra indexes have a _small_ burden on write operations; don't worry about it. Adding an index may _significantly_ speed up some Selects; this makes them worth adding. A composite (multi-column) Index is often worth adding. – Rick James Mar 07 '22 at 20:39
  • @CuriousToad - Front end vs backend -- ?? Are you are asking about shoveling more data than you need from the database into the app, then filtering in the app? The shoveling is extra work, too. I vote for an `INDEX` whenever practical. – Rick James Mar 07 '22 at 20:41
  • Sorry, my question wasn't very clear. My concern was that adding multiple indexes would cause backend operations (primarily saving changes to orders or content) to run more slowly. The reason for adding these queries would be to improve data retrieval on front end. So that's all I meant about a potential trade off - speeding up the front end performance at the cost of slower write operations on backend. – Curious Toad Mar 17 '22 at 19:36
  • @CuriousToad - Simply put, if an index would help a Select, then add it. Don't worry about the Inserts/Updates/Deletes. They will be only slightly impacted. If you find (with some detailed timings) that there is a problem there, then we can discuss it. However, sluggishness on Inserting is usually fixed in ways that does not involve avoiding indexes. – Rick James Mar 17 '22 at 20:05
  • That's great, thanks Rick! I thought it would be prudent to check. – Curious Toad Mar 18 '22 at 08:51
0

Based on the query, you should create the following indexes, in case you don't already have them:

ALTER TABLE `wp_postmeta` ADD INDEX `wp_postmeta_idx_meta_key_meta_value_post_id` (`meta_key`,`meta_value`,`post_id`);
ALTER TABLE `wp_posts` ADD INDEX `wp_posts_idx_post_type_post_statu_post_date` (`post_type`,`post_status`,`post_date`);
ALTER TABLE `wp_usermeta` ADD INDEX `wp_usermeta_idx_user_id` (`user_id`);

I also rewrote your query (using EverSQL), mainly to prefer IN clause over OR Conditions and it would be best if you tried to avoid using CONTACT function, as it will not use the relevant index.

SELECT
        wp_posts.* 
    FROM
        wp_posts 
    LEFT JOIN
        wp_postmeta _teams_pm 
            ON wp_posts.ID = _teams_pm.post_id 
            AND _teams_pm.meta_key = '_member_id' 
            AND _teams_pm.meta_value = 2 
    LEFT JOIN
        wp_usermeta _teams_um 
            ON _teams_um.user_id = _teams_pm.meta_value 
            AND _teams_um.meta_key = CONCAT('_wc_memberships_for_teams_team_',
        wp_posts.ID,
        '_role') 
    WHERE
        1 = 1 
        AND (
            (
                wp_posts.post_type = 'wc_memberships_team' 
                AND (
                    wp_posts.post_status IN (
                        'publish', 'acf-disabled', 'private'
                    )
                )
            )
        ) 
        AND (
            _teams_um.meta_value IN (
                'manager', 'member'
            ) 
            OR wp_posts.post_author = 2
        ) 
    ORDER BY
        wp_posts.post_date DESC
Oded
  • 19
  • 3