I’ve been a big fan of Elliot Condon‘s super handy Wordpress plugin Advanced Custom Fields for a number of years now. For those who don’t know, ACF allows developers to seamlessly add custom data fields just about anywhere within the WordPress admin console. It can be used to add something as simple as an extra tickbox option to a particular page template, right through to adding multiple instances of complex data types that are used on every page of the site. What’s more, all this can be achieved in a matter of minutes. This combination of simplicity and power is rare in my experience and sets ACF head and shoulders above the competition.
Anyway, enough eulogising … what is this post about? Basically, while testing one of our soon-to-be-launched web development projects, I became aware of some performance issues with the server-side page generation time.
Firstly, a quick summary of how we typically use ACF: In general, it’s a combination of functionality; components that are present on all pages (e.g. social links in the footer) make use of an Options page, whereas template specific data (e.g. section home carousel content) is handled using template specific Custom Field Groups. Both of these approaches regularly require the use of ACF’s premium Repeater component, which allows for multiple instances to be added (e.g. several slide objects to be displayed in a carousel). This is undoubtedly a very useful add-on, however when combined with the Options plugin it appears that site performance can potentially be affected quite badly.
To demonstrate this, we’ll use Query Monitor to see what’s happening when we load custom field data in each scenario. For the purposes of this demo I’m using a stripped back page which includes only the HTML head section and the elements whose performance we want to measure.
First off, lets measure how many queries are run when we only include our header:
Next, we’ll add in a call to retrieve the contents of a carousel made up of 6 items each with 5 properties, which has been stored as a Custom Field Group against this page:
The impact of this is fairly low – we’ve incurred a few extra queries but nothing significant. This is because Custom Field Group data is stored as a json object in a blob field so retrieving it requires very little extra work. Now we’ll swap out the previous ACF call with a call to retrieve a set of sidebar panels from the Options page. Again this consists of 6 items, each with 5 properties:
Hang on, that’s a lot of extra queries considering we’re loading the same amount of custom data as the previous example. So whats going on here? Looking at the query log we can see we now have 30+ calls to get_option() that weren’t previously present.
This is because every piece of data stored by the Options add-on is recorded on its own row in the wp_options table. As you can see in the screenshot below, a separate database entry is created for each property of each Repeater item; the number added alongside the property name tracks which item the data relates to. The impact of this behaviour becomes starkly clear when we look at the query count for our homepage:
Oh dear; we now require nearly 300 database queries to load just one page, and as a knock on effect the page generation time is approaching 1 second. If that wasn’t bad enough, these stats are being measured on a dev box with a local web server and database – that load time is only going to increase on remote hardware, especially when it’s under load.
So how do we handle this? Unfortunately, its too close to deadline day to re-engineer the site, so the best bet is to try to reduce the number of queries. We know the format used for the option_name of the data so we can easily retrieve all rows relating to a particular Option with a single query. We can then use PHP to parse the results and format them into a useful array ready for output. Let’s see how that affects things:
OK, that has definitely helped matters but there is still room for improvement. We know that some of our custom data is used on every page of the site, so by making use of the PHP sessions we can store this data, meaning it only needs to be retrieved once, on the user’s first page load.
Its not perfect by any means, however we have succeeded in reducing the number of database queries by 43% for the first homepage load and 62% for all subsequent loads.