Skip to main content
With the scale of sites we work on, performance is a key concern. This document outlines some of the key performance considerations when writing code for WordPress. This is one of those things that you can spend countless hours on and still not be perfect. It is important to be in a position where we feel comfortable shipping code and then incrementally improve it.

Cache Queries and Requests

When working with APIs or any type of external data, it is recommended to cache the results of the request. This not only helps with providing the results faster, and reduces the load on the external service, but also makes the site more resilient to downtime or slow responses from the external service. We can then update the cache via a cron-job or by setting an expiration time on it. It’s often useful to cache every post query, particularly those used in utility functions. WordPress doesn’t cache these out of the box as the cache hit rate would be very low, but when we can predict that a query will happen repeatedly, we can cache it at a higher level. (Plugins are available that will handle this for you automatically, including the Advanced Post Cache plugin which is included on VIP sites.) Where possible, use cached functions in WordPress, as these have already handled the caching for you. This includes most high-level functions around data, including get_post(), get_post_meta(), get_option(), and many more. If in doubt, check the WordPress source for wp_cache_get() calls, or ask a colleague.

Unbounded Queries

One of the easiest things to avoid and one of the most common causes of performance issues is unbounded queries. Make sure you always set the posts_per_page parameter in WP_Query and get_posts() calls and work with pagination where possible. You should never use -1 as the value for posts_per_page unless you’re absolutely sure that the number of posts will be very low. If you do need to get every item, reconsider the need to do so first, and whether you can change the process altogether. Maybe you can use pagination or an infinite scroll to present data. The typical unbounded query in WordPress is using WP_Query with 'posts_per_page' => -1. Do not do this. Likewise, raw database queries without a LIMIT clause should not be used. If you really do need to touch every post in the database, consider whether you can move your code into an offline solution; that is, into a wp-cli command or a cron task. This ensures that end users are shielded from potential scaling issues, and makes the operation fail-safe.

Avoid Writes on Page Views

You should avoid writing to the database on page views. Writing anything simply when a page is loaded is a surefire way to kill the database. This applies on any page view, including regular frontend pages and dashboard pages. You should only write to the database after a form submission (or Ajax/API PUT or POST request). Writes to the database can cause row or table locking, in addition to invalidating the various caches throughout WordPress and the database. Additionally, it means that pages cannot be cached properly. If you need to track page views or similar, you must use a dedicated service like Google Analytics. You’re often better off using these and pulling data back to the site via their API. WordPress is built for a read-heavy workload, whereas analytics are a write-heavy workload, so a dedicated service built for analytics is usually a better option.

Unperformant Functions

The following functions are uncached and should mostly be avoided in favour of alternatives: get_posts() (VIP only) - This disables all filters which are used for query caching, use WP_Query instead or set 'suppress_filters' => false manually. get_children() - This is uncached like get_posts(), but with an unbounded query, so should always be avoided.