What To Do with a Slow Site?

Aleksandr Guidrevitch
darwinapps
Published in
5 min readSep 18, 2017

The right fix can result in drastic changes. Here is how CPU load changed after the right fix was applied at 1:45 PM:

Everything started at my favorite time — Saturday night, while I was brushing my teeth before finally going to bed. A client site became unresponsive at 12:30 am my time. Looking at the graph, I’d say it was unresponsive most of day, but it seems that no one really noticed.

To be absolutely honest, at 12:30 am I was neither able nor willing to do much about it, so I just increased caching interval at NGINX side and set a goal for myself to investigate the problem in the morning as long as the site was already up and somehow running.

Always start with logs

On Saturday morning, I started with checking the web server access logs, and they showed a pretty small number of visits. MySQL’s slow log was also clean, so the problem seemed to be within WordPress itself.

My first step was to clone the site from production and install it locally. In order to collect some insights on what happened inside this poor little WordPress site, I fetched its main page with XDebug profiler enabled, and was able to collect 1 GB of profiling data after the five minutes it initially took to actually render the main page (I have my development environment running in VirtualBox, and it’s certainly not the fastest thing on earth — especially with XDdebug profiler enabled).

Here is what QCacheGrind was able to present to me:

What?
Wait, WHAT ?

22797 calls to get_post() to build the main page! That means that the main page iterates roughly over 22797 posts and 77989 terms, sanitizing their fields 1,526,661 times! (for context, these numbers are insane!)

As long as it was clear that get_news_select_values() and _cpt_metaboxes() functions above the get_post() in the listing are not a part of the WordPress core, I decided to start with these two functions. What I found in get_news_select_values() and _cpt_metaboxes() was astounding:

<?php

require_once
("cmb2/init.php");
add_filter('cmb2_meta_boxes', '_cpt_metaboxes');

function _cpt_metaboxes(array $meta_boxes)
{

// get news
function get_news_select_values()
{
$values = array(0 => 'None');
$news = get_posts(array(
'posts_per_page' => -1,
'offset' => 0,
'post_type' => 'post',
'post_status' => 'publish',
'orderby' => 'post_title',
'order' => 'ASC',
'suppress_filters' => true
));

if (!empty($news))
foreach ($news as $new) {
$values[$new->ID] = $new->post_title;
}

return $values;
}
// ....
}

The function selects all the posts of type ‘post’ from WordPress — including all the posts and their relations, including terms (this is where those 77989 terms come from) — just to build up a simple map of post id to post title! What’s more, there were seven functions like that one, replicating the same functionality for different post types. All seven were called on for each page render, causing 23k posts fetched, sanitized, pre- and post- processed in various filters, and that means a huge, HUGE amount of work performed behind the scenes of loading the main page — or any other page!

The rewrite was trivial:

<?php

require_once
("cmb2/init.php");
global $_cpt_metaboxes_posts;
$_cpt_metaboxes_posts = array();
add_filter('cmb2_meta_boxes', '_cpt_metaboxes');

function _cpt_metaboxes(array $meta_boxes) {
global $wpdb, $_cpt_metaboxes_posts;
foreach ($wpdb->get_results("SELECT ID, post_type, post_title FROM {$wpdb->posts} WHERE post_status = 'publish' ORDER BY post_title ASC") as $row) {
$_cpt_metaboxes_posts[] = [$row->post_type, $row->ID, $row->post_title];
}

// get news
function get_news_select_values() {
global $_cpt_metaboxes_posts;
static $cached;

if ($cached)
return $cached;

$values = array( 0 => 'None');
foreach ($_cpt_metaboxes_posts as $post) {
if ($row[0] == 'post')
$values[$row[1]] = $row[2];
}
return $cached = $values;
}
}

What this code does — it:

  • Fetches all the published content using one single SQL query into an array
  • Re-implements those seven functions to filter the array, fetched at step 1 using pure PHP. The result was immediate and astounding:

In fact, speeding up WordPress — or anything else — in such a drastic way in 2 hours of time rarely happens, as there were so many items broken throughout the third party plugins and themes. Given the rarity of a “fix it all” solution, I personally was pretty surprised with the result, and as I’m always suspicious about silver bullets, I asked the customer to closely monitor the site for any artifacts (even though I was 100% sure the fix was right).

This kind of performance problem is hard to predict. The one above was introduced by a junior developer at an early project stage, and the site worked well while the number of posts was low. I frequently see site owners experiencing performance problems in 1–2–3 years from the initial development. The good thing about software is that everything can be fixed, with the right budget.

So, what’s the solution ?

Well, there are actually 2 possible solutions — either buy a better server or fix the problem permanently. To make the proper decision:

  1. Check CPU Credits graph if you’re on a t2 AWS instance. If your credits are close to zero, switch the instance type from t2 to quite literally anything else. And avoid t2 instances in production in the first place.
  2. If a server upgrade costs less over the following 12 month-period than 8 hours of development time to find and fix the bottleneck, I suggest upgrading to the new server. If you decide you need your code fixed for good — hire the right engineers, or at least reach out to some for advice. We manage a WordPress site that has 20–30 3rd party plugins that slow it down to 1/10 of where it’s speed should be — and we really can’t do much without rewriting some of those plugins or building their functionality from the scratch.

Call To Action

We are ready to check if the performance of your company’s site could be improved for free, a service otherwise worth around $375, but here is the catch — you must meet the following criteria:

  1. You work in a marketing department or are otherwise in charge of your company’s site.
  2. The site is used for marketing/sales.
  3. The site is running WordPress.
  4. Your site actually experiences performance issues.
  5. You have permission and willing to share both code and database, as databases can have huge impact on the performance.

The offer expires Saturday, September 30th, and is capacity limited (meaning turnaround time will depend on our engineer capacity and load). Feel free to send your details to me, aguidrevitch@darwinapps.com, directly.

--

--