# Fix patterns by bottleneck category Load this file once `references/diagnostic-tools.md` (or the Step 3 baseline metrics) has pointed at a dominant bottleneck. Pick *one* primary category to fix at a time — trying to fix everything in one pass makes the before/after Verification step in `SKILL.md` meaningless, since you won't know which change caused which delta. Every fix below is still gated by `SKILL.md`'s Step 0 (read-only-first) and Verification (re-measure the same way afterward) sections. ## Database / query performance Use when profiling points at DB time or a high query count. - Avoid N+1 query patterns — batch queries, prime caches, avoid per-row lookups inside a loop. - Prefer `fields => 'ids'` in `WP_Query`/`get_posts()` when only IDs are needed. - Avoid expensive meta queries where possible; consider indexing or a schema change for genuinely hot lookups. - Use object caching for repeated reads (see below) rather than re-querying the same data every request. Backend-only tools: Query Monitor via REST (query lists, stack traces, slow/duplicate flags) — see `diagnostic-tools.md`. `wp db query` for targeted SQL/`EXPLAIN` — be careful running this in production, it's a direct DB access path. Query Monitor plugin: https://wordpress.org/plugins/query-monitor/ ## Autoloaded options Autoloaded options load on *every* request, so a large autoload payload hurts every page, not just the page that set the option. Quick checks (see `SKILL.md` Step 3 for the always-collect total): ```bash wp option list --autoload=on --fields=option_name,size_bytes | sort -n -k 2 | tail ``` Fix patterns: - Stop autoloading large blobs — store large/rarely-read data with `autoload=off` instead. - Move large computed data to transients or the persistent object cache (if the site has one — check first, see `SKILL.md` Step 3 metric 3) rather than an autoloaded option. - Remove stale options left behind by removed plugins/themes — confirm nothing still reads the option before deleting; a stale-looking option name isn't proof nothing depends on it. Docs: `wp option list` — https://wpcli.dev/docs/option/list · `wp doctor`'s `autoload-options-size` check — https://make.wordpress.org/cli/handbook/doctor-default-checks/ ## Object caching Use when profiling shows repeated identical queries or a low cache hit rate. - WordPress's default object cache is per-request memory only — it does not persist across requests unless a persistent drop-in (`wp-content/object-cache.php`) is present. On this fleet, that's currently only true for `menorca.pt`/`menorca.ro` (Redis Object Cache plugin against cerebro's Valkey) — see `SKILL.md` Step 3 metric 3 before assuming a site has one. - `wp cache flush` (or the Redis-plugin-specific `wp redis flush`) can affect more than the one site you're working on — cerebro's Valkey DB 0 is genuinely shared across sites by key-prefix, not fully isolated per site (confirmed in `docs/server-cerebro.md`: `wp redis enable`'s `FLUSHDB` clears the whole shared DB 0, not just the calling site's prefix). Never flush without explicit approval. Fix patterns: - Cache expensive computed results (transients, or the persistent object cache where one exists) with explicit invalidation — don't rely on natural expiry alone for data that changes on a known event. - Avoid unbounded caches — set expirations, or implement real invalidation hooks tied to the data changing. - If a site doesn't have a persistent object cache and profiling genuinely points there, adding one is an infra change (provisioning Valkey ACL credentials on cerebro, installing the PHP Redis extension in the jail — package name `php85-pecl-redis`, not `php85-redis`, per `bastille-jail-provisioning`) — coordinate rather than doing it as a drive-by inside a performance investigation. WP-CLI cache commands: https://wpcli.dev/docs/cache · flush guardrails: https://wpcli.dev/docs/cache/flush ## Cron Use when cron causes request-time spikes or slowness. ```bash wp cron event list wp cron test # spawning health wp cron event run --due-now ``` Fix patterns: - De-duplicate scheduled events and reduce frequency where the task genuinely doesn't need to run that often. - Ensure cron callbacks are idempotent and short — a task that's slow enough to matter for site performance is a candidate for moving off the request path entirely, not just running it less often. - Move heavy work (imports, RSS/content-agent jobs — several granja sites already run these hourly, per `docs/server-granja.md`) fully off the page-load request path; a badly-timed spawn on a real WP-Cron pseudo-cron setup can add real latency to whichever request happens to trigger it. WP-CLI cron command package: https://github.com/wp-cli/cron-command ## Remote HTTP API calls Use when profiling shows slow external requests (`wp_remote_get()` etc.). Fix patterns: - Add explicit timeouts and fail-fast behavior — a slow or hanging third-party API shouldn't be able to make every page load slow. - Cache responses where the data doesn't need to be live on every request (transients / persistent object cache where available). - Batch requests, and avoid calling a remote API on every page load when a periodic cron-driven refresh would do. - Move genuinely heavy remote work off the request path entirely (cron or a queue), same principle as the Cron section above. Query Monitor can report HTTP API call timing via the REST envelope (`qm` property) — see `diagnostic-tools.md`.