Files
playbook/antigravity-awesome-skills/skills/wp-guard/references/performance.md
T
2026-07-18 00:02:59 +00:00

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 => -1 is 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. Use WP_Query or pre_get_posts.
  • Only IDs needed → 'fields' => 'ids' (skips row hydration and caches).
  • Not paginating → 'no_found_rows' => true (skips SQL_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 large postmeta tables; consider a lookup table or taxonomy when the access pattern is hot.
  • Never run queries inside foreach when 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 (pass false as the third arg of update_option too; the parameter exists since WP 4.2).
  • Keep individual autoloaded options small; an alloptions blob 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 on admin_enqueue_scripts with the $hook_suffix check.
  • Version assets with the plugin version (cache busting); load non-critical JS with defer/async strategies (wp_register_script args 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 on init for 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_posts when an indexed count query or cached counter would do.