A client came to me recently to get help with identifying why their site was so slow when users were logged out.
Normally, the opposite is true – normally, logged in users aren’t benefiting from page-caching or other forms of caching, and normally logged in users – e.g. admin – have more content to load, like the wp-admin bar and other related admin functions.
So, it’s not normal for performance to be slower for logged out users. What gives?
Finding the source of the problem
There are many ways to skin this cat – the most reliable is probably something like xDebug or XHProf which will tell you precisely which function is consuming the most amount of time.
They take a while to install however, so there are other tried and true methods of figuring this out.
To start with, logged out, his million product store was taking anything between 100 and 160 seconds to load. Logged in, as admin – it was taking 2 seconds.
I’m using a neat little Chrome Extension called Performance Analyser, which gives you pingdom/page-speed style results, but mostly I just need the timer.
Here’s the order of events:
- I logged into the database and used SHOW FULL PROCESSLIST while refreshing the shop page in incognito mode. I found a query fetching ALL product IDs from the database
- I changed the theme first to one I know is scalable (I used Flatsome because it was already on the server but Rehub, Themify, Elegant Themes, StudioPress are all scalable). This gave a speed boost, but still about 60 seconds.
- I removed all of the slow WooCommerce filters – price filters, category filters etc – still slow, although faster
- I deactivated all plugins except for WooCommerce – now I got 700ms
- I reactivated all of the plugins then de-activated the suspicious ones. Anything related to ‘feeds’ are generally suspicious if you’ve seen SQL grabbing everything. Still, we were back to over 100 seconds.
- I logged into the server and performed a ‘grep’ command in the plugins folder for ‘product’ so I could find which plugins were candidates for creating this SQL query that grabs every product from the database
- Going through the 5 or 6 remaining I hadn’t yet ruled out, I identified Yith Wishlist as the culprit
Narrowing down the source of the performance issue
So, I moved into the yith wishlist folder and grepped again for ‘product’ so I can find which files to investigate:
From the above, I could tell the likely file was includes/class.yith-wcwl.php. Opening that file and then searching again for ‘product’ (remember I’m looking for a query that grabs all products with status ‘publish’) I find the following code:
Ok, so we already know that when users are logged in that Yith is working fast, even with 1 million products in the database. So, why is it slow when users are logged out?
If you have a read through the code, I’ve added some annotations to point out the functionality being delivered here.
First, it’s grabbing every product id in the database and then for each item in the visitors wishlist (currently empty!), it’s checking if these products still exist. Presumably the purpose of this is to ensure items that get deleted from the store also get deleted from the wishlist cookie – but to read EVERY PRODUCT IN THE DATABASE is to forget entirely about the concept of scalability.
Double checking my results
There’s an old maxim that says when you’re testing, to only change one thing at a time. Sometimes for speed, I prefer to use something similar to a Quicksort algorithm – e.g. disable 50% of the plugins, if speed is far better, reactivate half of those disabled ones until I find the culprit.
Whilst that’s all going on, I sometimes alter other things I see, so even though I keep track of all this, it’s still good practice to double check. So, i reactivated the Yith Wishlist plugin and got this:
Then I deactivated the Yith Wishlist plugin again.
And then ran the /shop/ page again in incognito mode.
Note: There is no page caching in use yet, so this is raw page-speed generation which is not too bad for a million product store. There are further improvements can be made of course, but for now, my client is happy.
Recommendation for developers
This goes to all developers, not just the Yith developers. In a scenario like this, the size of your datasets matter. There are 2 datasets being compared here – the product ids in the wishlist cookie and the product ids in the database.
Now, using your business domain knowledge, you know that the wishlist dataset will always be far, far smaller than the shop product ids dataset. So, that should be your starting point. For example, this coding path would be lightning fast in comparison:
- Get product IDs from wishlist
- Call WP_Query using IN operator and product ID list from wishlist – you can even store it in an array like you’re currently doing
- Use existing code to run through items in wishlist and check if they are in the array
For million product stores, this would transform this code from taking 120 – 160 seconds down to 0.001 second. Just to clarify for you, it’s not only TIME TAKEN, you’re also messing up other stuff:
- You are consuming CPU on the server affecting other pages being generated
- You are using up more RAM than needed, restricting how many simultaneous users can be served
- You are wiping out a lot of caches on a lot of server configs by fetching every product ID from the database
- You are using up far more disk activity than needed and there’s zero chance of using an index when you just grab everything
- You are using up more POWER than needed and the planet is warming up
And specifically to Yith – please educate your developers on how to code properly against datasets and if you need a million product store to test against, let me know, I’ll happily provide you a clone.
This is just one more example of how plugin developers can kill websites with awful code. If you ever find yourself coding a plugin that doesn’t filter at all, and you’re just grabbing every product ID or every post ID then processing that with PHP, you are doing something seriously wrong.
The only time that might be acceptable is in a cron job, e.g for imports and exports.
So – until they get this fixed, avoid the Yith Wishlist plugin. Ok – if you only have 1000 products or so, you’ll probably be fine, but otherwise your site visitors will be seeing extremely slow page speed.
I’ve reached out to Yith to let them know about this performance review and I’ll update this article if and when I hear back. Hopefully they can remedy this soon as I know this plugin is fairly popular.
I’ve also gone ahead and implemented a code fix myself, using the improved pattern I described. So – the functionality is identical, but now it scales properly and will not slow down on large websites. You can find the file changes I made on a gist here:
Update with positive response from Yith
I submitted this article to Yith support and to two Facebook groups – WordPress Speed Up and Advanced WordPress. These are excellent groups, both with very supportive and knowledgeable communities – they are both closed by default but you can apply to join:
Although the initial response from Yith was disheartening (generic response about this issue is probably caused by another plugin), someone from the Advanced WordPress Facebook group contacted Yith directly and pointed them in the direction of the group chat.
As a result, I then got a personal response from Yith Support and 3 hours ago they informed me that my patch is now live in their free version and the premium version will have the patch soon too! That’s a fast turnaround.
So – rock on! You can use the Yith Wishlist on your huge stores again. If anyone else has a slow performing plugin or theme and you have identified that it’s definitely that specific theme or plugin, you can submit it for a full performance review here:
Chat to me directly through our on-site chat bubble.
Latest posts by Dave Hilditch (see all)
- Performance review of the Yith Wishlist (it’s bad, but they fixed it in 24 hours!) - July 17, 2019
- My journey fixing WooCommerce slow speed with many variations – part 1 - July 6, 2019
- Speed up your WordPress site - June 4, 2019