3.9 KiB
3.9 KiB
WP Guard — Performance Reference
Contents
- WP_Query discipline
- Meta and term cache priming
- Transients vs object cache
- Options autoload hygiene
- Asset loading
- Cron and background work
- Scaling traps checklist
WP_Query discipline
posts_per_page => -1is forbidden. There is always a bound; if the caller "needs everything," page through with a loop or use a bounded cap the project agrees on.query_posts()is forbidden — it clobbers the main query. UseWP_Queryorpre_get_posts.- Only IDs needed →
'fields' => 'ids'(skips row hydration and caches). - Not paginating →
'no_found_rows' => true(skipsSQL_CALC_FOUND_ROWS). - Not using meta/terms in the loop →
'update_post_meta_cache' => false,'update_post_term_cache' => false. - Avoid
'meta_query'on unindexed scans for high-traffic paths — meta queries do not scale on largepostmetatables; consider a lookup table or taxonomy when the access pattern is hot. - Never run queries inside
foreachwhen one query (or cache priming) can fetch the set.
Meta and term cache priming
The N+1 killer. When iterating IDs and reading meta per item:
$ids = get_posts( array( 'fields' => 'ids', /* … */ ) );
update_meta_cache( 'post', $ids ); // one query primes the cache
foreach ( $ids as $id ) {
$sku = get_post_meta( $id, '_sku', true ); // served from cache
}
update_meta_cache() and update_object_term_cache() exist for exactly this; on WP ≥ 6.1, _prime_post_caches() batches posts, meta, and terms in one call (public API since 6.1 despite the underscore). AI-generated loops skip them every time.
Transients vs object cache
set_transient()/get_transient()— persistent cache with TTL; backed by the object cache when a drop-in exists, by the options table otherwise.wp_cache_get()/wp_cache_set()— request-scope by default, persistent only with an object-cache drop-in (Redis/Memcached). Use cache groups and deliberate TTLs.- Expensive remote calls (Rule 11) always go behind one of these. Choose TTL consciously; never cache user-specific data in a shared key — include the user/locale in the key when output varies by them.
- On failure paths, decide explicitly whether to cache the failure (short TTL) or retry every request — and say which in a comment.
Options autoload hygiene
Every autoload => true option loads on EVERY request into alloptions. Rules:
add_option( 'ncs_big_report_cache', $data, '', false )— large or rarely-read data never autoloads (passfalseas the third arg ofupdate_optiontoo; the parameter exists since WP 4.2).- Keep individual autoloaded options small; an
alloptionsblob over ~1 MB is a known site-killer on high-traffic sites. - One serialized settings array per plugin beats twenty separate options.
Asset loading
- Enqueue only where used: check
is_admin(), screen IDs (get_current_screen()), shortcode presence, or block usage before enqueueing. - Front-end assets registered on
wp_enqueue_scripts, admin onadmin_enqueue_scriptswith the$hook_suffixcheck. - Version assets with the plugin version (cache busting); load non-critical JS with
defer/asyncstrategies (wp_register_scriptargs on WP ≥ 6.3).
Cron and background work
- WP-Cron is traffic-driven and can stack: guard handlers against overlap (a lock transient), keep events idempotent.
- Long or high-volume jobs → Action Scheduler when WooCommerce (or the library) is present.
- Never
sleep()in a request; never do batch work oninitfor every visitor.
Scaling traps checklist
- Unbounded
get_users()/get_terms()on large sites — same rules as posts. switch_to_blog()in loops without batching (multisite).- Per-request remote HTTP without caching — one slow third party becomes your TTFB.
- Writing to options on front-end requests (cache invalidation storm under traffic).
- Counting rows with
found_postswhen an indexed count query or cached counter would do.