Technote

Performance optimization Practical

Series Profiling WordPress performance Part 3 of 8

The N+1 problem: get_post_meta in a loop, and priming

WP_Query primes caches for its own results only. A hand-built list of IDs gets none of that for free — and that difference decides your query count.

Most of a duplicate-query list converges on one shape: a loop over posts that reads a meta value for each one. Nothing about it looks wrong.

foreach ( $ids as $id ) {
    $price = get_post_meta( $id, 'price', true );
}

How many queries that produces depends entirely on whether the cache was primed. Primed, it is zero. Cold, it is one query per post. The same code behaving differently on different screens is what makes this hard to spot.

WP_Query primes its own results

After fetching results, WP_Query loads the meta and terms for those posts in one go and puts them in the object cache. That is why get_post_meta() inside an ordinary loop issues no queries at all — the values are already there. Core was built with this problem in mind.

The trouble is lists that leave that path. Four cases cover most of it.

Lists that arrive primed, and lists that do not

The third is the real trap. 'update_post_meta_cache' => false is a genuine optimisation for a list that touches no meta at all, but it circulates as a general performance tip and gets pasted into loops that do read meta. You save one query and buy N.

The priming functions core already ships

You do not have to touch the cache API yourself.

$ids = $wpdb->get_col(
    $wpdb->prepare( "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = %s", 'featured' )
);

// posts, terms and meta — one query each
_prime_post_caches( $ids, true, true );

foreach ( $ids as $id ) {
    $price = get_post_meta( $id, 'price', true ); // cache hit
}

If you only need meta, update_postmeta_cache( $ids ) does that alone; for terms it is update_object_term_cache( $ids, 'product' ), and for a list of users, cache_users( $user_ids ). In every case, N lookups collapse into one.

One detail people get wrong: get_post_meta( $id, 'price', true ) reads all of that post’s meta, not just the one key, and caches the lot. Reading five keys from the same post is still one query. The N in N+1 is the number of posts, not the number of keys.

Confirming that you fixed it

Once the priming call is in, go back to the profile from the previous part and re-read it on the same URL under the same conditions. Check two things: that the repeat count for that SQL has dropped to one, and that total response time actually moved.

One pass at removing an N+1 — skip the last box and you do not know whether you fixed it

The second check matters. Sometimes the repetition vanishes and the response time does not move, which means those queries were always cheap and the bottleneck is elsewhere — write that down too. “No measurable change” is information for whoever picks this up next.

More on query design for listing screens is in the performance archive, and a full review of a site’s query structure is part of the diagnostic in our optimization program.

Next part

Priming removes repetition within one request. Keeping it between requests needs a persistent object cache — next we separate what Redis genuinely removes from what it does not.

More on this topic

All technotes

Performance optimization Practical

Who pays for the milliseconds a tag adds?

A tracking script does not stop at downloading. While it is parsed and executed the main thread is busy, and a busy page ignores fingers. That cost is…

Marketers 5 min read

Performance optimization Practical

The object cache: what Redis actually removes

Repeated identical lookups disappear. One slow query does not. Miss that distinction and you will wire up Redis and then ask why nothing got faster.

Developers 7 min read

Performance optimization Practical

This is how a landing page gets heavy

Nobody decides to make a landing page heavy. It accumulates: one addition per campaign, and nothing ever removed when the campaign ends.

Marketers 6 min read

₩270,000 · Join the program