Caching didn’t fix our high-volume WordPress sites. Database indexing and cursor pagination did
We manage some content-heavy WordPress sites (news portals, big blogs, 10k+ posts) and hit a wall where no amount of page or object caching helped. Turned out the bottlenecks were baked into how WordPress stores data, not something a cache layer could paper over. Sharing what actually moved the needle in case it saves someone a bad week.
Three things were doing most of the damage:
Taxonomy queries. On a site with 50k posts and ~10 tags each, wp_term_relationships balloons to half a million rows. Filtering by multiple taxonomies means expensive JOINs, and without the right indexes MySQL just falls back to full table scans. A composite index on term_taxonomy_id and object_id took some of these from seconds to milliseconds.
Post meta lookups. wp_postmeta gets brutal at scale since every custom field is its own row. Anything that filters or sorts by meta (featured status, view counts, custom dates) JOINs that table repeatedly. Indexing meta_key with a prefixed meta_value (191 chars for utf8mb4) helped a lot. For the really hot fields we ended up denormalizing into a small custom table kept in sync via save_post.
Deep pagination. WordPress uses OFFSET, so page 500 makes MySQL fetch and throw away 10,000 rows before it returns anything. Crawlers hitting deep archives were quietly hammering the DB. Switching to cursor-based pagination with date_query comparisons kept query time flat no matter how deep the page.
Query Monitor on staging plus EXPLAIN to confirm the indexes were actually being used was the workflow that tied it all together.
Happy to share the SQL and WP_Query snippets if anyone wants them, I wrote the whole thing up with code somewhere. Curious what’s worked for others too, especially anyone who’s gone the custom-table route.
[link] [comments]