Skip to content
Back to skills

Magento2 Performance Audit

BSecurity

This skill should be used when the user asks to "audit performance", "check Core Web Vitals", "run Lighthouse", "check server configuration", "verify Redis/Varnish setup", "analyze database queries", "find N+1 query issues", "review indexer configuration", "check cron health", "debug cache flush", asks "why does full_page cache keep flushing", wants to "trace FPC invalidation", or reports "too many ajax requests", a "customer data section reload storm", or a "crawler overloading server". Perf...

  • 3 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 19, 2026
ai-agentsgophpsqlreactcode-reviewgitdatabasebackendsecurityperformance

Works with

  • cli
  • mcp

Security analysis

B77/100
  • mediumUses curl or wget to download content
  • highPerforms destructive filesystem operations

Pro scans all 12 files and shows the line behind each finding

Scanned October 7, 2026

npx -y skills add ddtcorex/maestro-skills --skill magento2-performance-audit --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Magento2 Performance Audit?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Magento2 Performance Audit
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/ddtcorex-magento2-performance-audit/badge)](https://www.skillsdirectory.com/skills/ddtcorex-magento2-performance-audit)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: magento2-performance-audit
description: |
  This skill should be used when the user asks to "audit performance", "check Core Web
  Vitals", "run Lighthouse", "check server configuration", "verify Redis/Varnish setup",
  "analyze database queries", "find N+1 query issues", "review indexer configuration", "check
  cron health", "debug cache flush", asks "why does full_page cache keep flushing", wants to
  "trace FPC invalidation", or reports "too many ajax requests", a "customer data section
  reload storm", or a "crawler overloading server". Performs a comprehensive performance and
  health audit for Magento 2 projects against Adobe Commerce Best Practices. DEPENDENT on
  magento2-dev-core for code-level performance patterns.
compatibility: claude, codex, opencode, copilot, dsh
depends: [magento2-dev-core]
metadata:
  audience: developers
  workflow: magento
---

# Magento 2 Performance Audit

This skill performs a comprehensive audit of Magento 2 performance, infrastructure, and code-level patterns.

## Govard-Native Audit Coverage

`govard audit run` executes PHPCS and PHPStan through Govard's pinned lint
toolchain image — it covers coding-standard and static-analysis findings only.
As of Govard v1.64.0 no `performance` audit check exists:
`govard audit run --checks performance` fails with "audit check ... is not
implemented". Govard v1.64.0 does add a native `profiler` check
(`--checks lint,profiler --url <url>`) that machine-captures the stock profiler CSV for one
URL — a quick complement to this skill's manual per-page audit, not a replacement: it ships
no query log, no cross-page matrix, and no threshold analysis. The profiler requires a project target (not standalone), an absolute http(s) URL (the request carries `Accept: text/html` so stock Magento enables the CSV), and is guarded by a per-project `diagnostics` lease; the CSV lands as `artifacts/profiler/profile.csv` with its SHA in `audit-result.json` — open it as spreadsheet to read per-timer costs. Manual per-page captures (7 pages) cost ~2.5-3 min **on their own** (50k queries/page with call-stack, 16-26s each) — the full deep audit around them takes far longer, ~22m observed, see the observed floor above. Keep all 7, run with 300s timeout or background polling and trap restore, not by sampling fewer categories (that hides per-item N+1s). Keep running this checklist
yourself and treat `govard audit run --checks lint` as the shared lint gate. Never present a
lint-only pass as a performance verdict.

> **This is a checklist, not a menu.** All 9 steps under **Workflow** (bottom of this file) run on every invocation — infra, indexer/cron, per-page-type capture, Slow Query Analysis, Cache Invalidation Efficiency, Client-Side AJAX Load, Core Web Vitals, code-level grep, report. Picking the steps that feel highest-signal for the effort and quietly dropping the rest (no admin creds, no Chrome DevTools MCP, "I already found a good bug") is the single most common failure mode of this skill — it produces a confident, well-formatted report that silently covers less than half the checklist. If a step genuinely can't run, say so *in the report, under that step's own heading* — `Skipped: <reason>` — never by omission. See the self-verification gate at the end of Workflow: the report is not done until it's been checked against the Audit Report Template line by line.

> **Distinguish a scoped ask from an unscoped one — this rule governs dropping steps quietly, not answering a narrower question.** A general ask — "audit performance", "review this project before launch" — is unscoped: all 9 steps apply, none optional, exactly as above. When the user's own words name one specific category instead ("just check the MySQL query count", "audit N+1s only", "how many queries does the homepage run"), scope the work to that category and its reference file(s) — running the other 8 steps anyway would be answering a different question than the one asked. The obligation that carries over unchanged: state the scope explicitly (a "Scope" line/heading in the report) so the result is never mistaken for a full audit, and don't let scope creep run in either direction — no silently expanding a scoped ask back to all 9 steps, and no silently narrowing an unscoped one down to whichever step already found something.

> **Read the step's reference file before the first attempt at that step — not after improvising fails.** Each "Full detail:" link owns its supported commands and environment traps: BusyBox grep inside `govard sh`, host/container DNS, Laminas renames, and DB privilege walls. If two attempts at a step fail, read that reference end to end before attempt 3. Never hand-edit `app/etc/env.php` to enable a diagnostic: it is deployment configuration, not an audit control surface; a syntax error takes every Magento CLI command down and cannot be reverted with `git checkout` when the file is gitignored.

> **Shared query-log captures require ownership.** Before step 3, acquire the `.performance-audit.lock` session token described in `references/database-query-profiling.md`. A busy or foreign lock must fail-fast; do not remove a lock you do not own or treat its log as evidence.

### Common ways this gets shortcut (don't)

| Rationalization | Reality |
|---|---|
| "Infra/cache/indexer checks already give strong signal, that's enough" | Slow Query Analysis, Cache Invalidation Efficiency, and Client-Side AJAX Load each catch bug classes the others structurally cannot see — one being clean says nothing about the others |
| "I already found a solid N+1, that's enough for a report" | A real finding proves the audit found *something*; it doesn't prove the mandatory steps ran. Finding a bug early is not a reason to stop the checklist |
| "One category and one product page is representative enough" | Only 3 differently-sized samples per type can surface the size-scaling N+1 signal (`references/per-page-type-audit.md`) — a single sample is provably unable to show it, however clean the one page looks |
| "This step needs admin credentials / a Chrome DevTools MCP I don't have" | Mark that section **unverified** with the reason, in the report — don't drop it from the conversation as if it were never in scope |
| "The draft report already has good findings, ship it" | Diff the draft against every checkbox in the Audit Report Template *before* presenting it as done — an unchecked box with no skip reason means the audit isn't finished, not that it's fine to omit |
| "This query shape (or profiler timer) repeats/is slow but I don't think it's a real bug" | Not your call to make silently — list it in the Repeated Query Shapes or Slowest Blocks/Templates table (`references/report-template.md`) with your assessment anyway. A borderline case left out of the report is indistinguishable from one that was never checked |
| "The user only asked about query counts, so I only ran that" | Correct *if* their own words named that one category — say so under a Scope heading. If their ask was general ("audit performance", "review this project"), this is the same shortcut as the rows above, just dressed up as scoping |
| "There's no `dev:profiler:enable` / `dev:query-log:enable` flag, so I'll add one to `env.php` / patch a bootstrap script" | These are CLI diagnostics, not config flags. Use the exact commands in `references/database-query-profiling.md` and `references/per-page-type-audit.md`; never edit `env.php` or add a one-off bootstrap script |
| "The reference's grep recipes don't work in `govard sh`, so I'll write my own" | The container's grep is BusyBox — no `--include`. `find app/code -name '*.php' -exec grep -Hn ... {} +` is the supported equivalent (recipes in `references/code-level-patterns.md` are already BusyBox-safe) |

## Related Skills

**REQUIRED BACKGROUND:** Load `magento2-dev-core` first — code-level fixes for N+1 queries and heavy constructors follow the patterns it defines.

Part of the QA trio with `magento2-linter` and `magento2-security-scan` — together with `magento2-dev-core`, these form the "QA quartet" that `magento2-code-review` orchestrates at PR/module/theme/project scope. Findings use the shared `M2-PERF-xxx` codes cataloged in `magento2-dev-core/references/severity-and-codes.md`. Async/queue findings often point back to `magento2-backend-dev`.

## Audit Categories

Nine categories, each with full commands/thresholds/edge-cases in its own reference file — read the relevant file when executing that step of the Workflow below, not all up front:

| Category | Reference |
|---|---|
| Infrastructure, cache, indexer, async consumers, asset optimization, cron, security probes | `references/infrastructure-checks.md` |
| Core Web Vitals (LCP/INP/CLS, Chrome DevTools MCP trace, Lighthouse fallback) | `references/core-web-vitals.md` |
| Database query profiling: query-count tiers, query log setup, common issues, Slow Query Analysis | `references/database-query-profiling.md` |
| HTML profiler: block/template timing, tracing custom-code cost, cross-page-type signals | `references/html-profiler-audit.md` |
| Per-page-type audit (homepage + 3 category + 3 product samples, uncached) | `references/per-page-type-audit.md` |
| Cache invalidation efficiency (built-in FPC debug log + Varnish BAN tracing) | `references/cache-invalidation-audit.md` |
| Client-side AJAX/Customer Data load (sections.xml, reload storms) | `references/ajax-load-audit.md` |
| Code-level performance patterns (N+1, collection counting, heavy constructors, cache invalidation code) | `references/code-level-patterns.md` |
| Audit report template + self-verification checklist | `references/report-template.md` |

## Quick / Deep — scope param (quick vs deep, quick 5–10m vs deep 20–30m)

This skill accepts a `scope` param: `quick` (PR check, cap 5–10m) or `deep` (release audit, 20–30m). The value `quick.*deep` on one line is intentional for tooling checks — keep the param name `scope` with those two literal values. Default to `deep` when the caller does not specify; callers that need a fast PR signal pass `scope=quick`.

Observed floor (reference large-project audit, verified 2026-09-21, deep scope): ~22m / 144 tool calls / ~332k tokens for the **whole audit** — setup, 7 captures, both passes, analysis, and report writing. Captures alone are only ~2.5–3 min of that (see `references/per-page-type-audit.md`); the rest is reading, cross-page analysis and the report. Use the total for the foreground/background + budget decision; it is an observation, not a commitment.

> **Report header (mandatory):** every report starts with `Scope: quick` or `Scope: deep` on its first line (see `references/report-template.md`). Quick uses `Scope: quick — 3 pages (1 home + 1 category + 1 product)`, Deep uses `Scope: deep — 7 pages (1 home + 3 category small/medium/large + 3 product)`. Do not start a report without that line — it is how a reader tells PR vs release coverage at a glance.

| Mode | Pages | Query-log call-stack | Query-time threshold | Files scope | grid_per_page | Time cap | Transport & restore |
|------|-------|----------------------|----------------------|-------------|---------------|----------|---------------------|
| quick | 3 pages small/medium/large (1 home + 1 category + 1 product) — call-stack false — threshold 1 — quick files — grid_per_page 48 — cap 5–10m | `--include-call-stack=false` | `--query-time-threshold=1` | quick files (`app/code` + `app/design` + `app/etc` sampled, batch govard sh) | 48 | 5–10m | `grep -c '## QUERY'` recipes; batch govard sh single setup cmd; trap single |
| deep | 7 pages double-pass (1 home + 3 category small/medium/large + 3 product) — call-stack true — threshold 0 — deep files — 20–30m | `--include-call-stack=true` (two-pass: pass 1 false for counts, pass 2 true for 1–2 traces) | `--query-time-threshold=0` | deep files (full `app/code` + `vendor`/`dev`/`lib`/`m2-hotfixes` to the ignore boundary) | prod `grid_per_page`/`list_per_page` | 20–30m | Same native-or-grep split; batch govard sh where possible; trap single (one `trap ... EXIT` for the whole session) |

Key verbatim mapping for quick 3 pages small/medium/large call-stack false threshold 1 quick files grid_per_page 48 cap 5–10m vs deep 7 pages double-pass call-stack true threshold 0 deep files 20–30m — keep these values in sync with `references/per-page-type-audit.md` and `references/database-query-profiling.md`.

> **Use the recipes, not hand-reading:** do not open 50k lines of `var/debug/db.log` as a spreadsheet. Use the `grep -c '## QUERY'` / `mysqldumpslow` / `pt-query-digest` recipes in `references/database-query-profiling.md`. For lint use `govard audit run --checks lint --format json` on the host.

> **No stats tool exists for the query log.** Do not hand-grep 16–50k lines; use the `grep -c '## QUERY'` / `mysqldumpslow` recipes below.

> **Batch govard sh + trap single:** collapse multi-step container setup (`mkdir .performance-audit.lock`, `dev:profiler:enable`, `dev:query-log:enable`, `cache:disable`, `cache:flush`, warmup) into one `govard sh -c '...'` round-trip where sequencing allows — single-quoted outer, `;`-sequenced (never `&&` around `grep`), see `govard-toolbox` § *govard sh -c quoting*; captures themselves stay sequential under the same lock. Always install a single `trap 'govard sh -c '\''bin/magento cache:enable; bin/magento cache:flush; bin/magento dev:profiler:disable; bin/magento dev:query-log:disable; rm -rf var/debug/.performance-audit.lock'\''' EXIT` — one trap for the entire audit, not per page — so a timeout restores caches/log/lock.

## Workflow

> **Query-log analysis:** use the `grep -c '## QUERY'` / `mysqldumpslow` recipes in references/database-query-profiling.md. Do not hand-grep 16–50k lines or open the log as a spreadsheet.

> No `performance` check exists — `govard audit run --checks lint` covers lint only; use `govard audit run --checks profiler --url <absolute http(s) url>` for a single-URL profiler CSV where it helps, otherwise run the manual per-page captures and grep scans below.
> **Otherwise:** run the same manual Workflow steps as written (profiler + query-log captures per `references/per-page-type-audit.md` and grep scans per `references/code-level-patterns.md`).

> **Step 0 — Branch & Env Gate (tool > LLM, interactive when the runtime supports it):** before infra, ensure the requested git branch is checked out and the Govard environment is ready. Tool `magento2-performance-audit({branch?:string, scope?:'quick'|'deep'})` — `branch` optional. When `branch` is provided: `git fetch --all --prune` → `git rev-parse --verify <branch>` → `git checkout -f <branch>` if different → `git pull --ff-only origin/<branch>` (if diverged → `Skipped: local diverged — audit on current HEAD`). When `branch` is undefined and the runtime supports interactive questions: ask `(1) current checked-out (2) input branch (3) choose from git branch -a` then same fetch/checkout/pull. After branch: `govard status` → if `Down` or project mismatch → `govard env up` (wait `Ready php runtime` ~11s like example-project) + `curl -skI https://<domain>/ 200` verify before step 1.

> **Step 0b — Discovery host-first (5s max):** see `references/per-page-type-audit.md` §0 — DB candidates via `<prefix>url_rewrite`, host-first curl `--max-time 5 --connect-timeout 3` (host curl where reachable, else container curl), prefix from `env.php`/`SHOW TABLES`, `is_active` via `information_schema` (not hard-coded), fallback `Skipped: no 200 URL`. Quick ≤15s (1 cat+1 product), deep ≤30s (3+3); quick 3 pages vs deep 7 pages preserved, 7 details gate unchanged.

**When invoked, steps 1-9 are mandatory, run in order, none optional:**
1. Execute infrastructure checks (env.php, mode, cache status) — first confirm whether the target is local dev, staging, or production, since expectations differ. Full detail: `references/infrastructure-checks.md`
2. Run indexer status check, and verify cron is actually running/draining `cron_schedule`. Full detail: `references/infrastructure-checks.md`
3. Run the Per-Page-Type Audit — homepage plus 3 category (small/medium/large) and 3 product URLs — with `full_page`/`block_html`/`layout` caches disabled — verify each test page is representative first, then capture profiler + query log together, watch for a query count (and a slow block/template timer) that scales with grid size across the 3 category samples, then restore state. Use the setup, capture, restoration, and host-side request commands owned by the linked references.
   Full detail: `references/per-page-type-audit.md`, `references/database-query-profiling.md`, and `references/html-profiler-audit.md`
4. Run Slow Query Analysis (app-level `TIME:` sort, and/or MySQL's own slow_query_log for cron/import-triggered queries the app-level log can't see) — `EXPLAIN` any candidate before reporting it, and turn slow_query_log back off when done. Full detail: `references/database-query-profiling.md`
5. Trace cache invalidation efficiency — enable temporary logging (debug.log for built-in Redis/file FPC, varnishlog/ban.list for Varnish), reproduce one isolated save/action (or mark unverified if no admin credentials are available this session), and flag any custom code causing broad/frequent flushes beyond Magento's default targeted invalidation. Full detail: `references/cache-invalidation-audit.md`
6. Confirm which reactive/AJAX mechanism the project actually uses (sections.xml/Customer Data vs. Magewire/PWA/GraphQL or similar), then capture the AJAX footprint of a fresh/anonymous page load (Network tab) and audit accordingly — for sections.xml, check for overly broad Customer Data invalidation rules; these uncacheable requests are what crawler/bot JS execution multiplies regardless of FPC hit rate. Full detail: `references/ajax-load-audit.md`
7. Run Core Web Vitals audit (Chrome DevTools MCP trace preferred, Lighthouse as fallback) — when render delay dominates an LCP, read it per the JS-hydration guidance rather than assuming a network/image problem. Full detail: `references/core-web-vitals.md`
8. Scan `app/code` for code-level performance patterns using the grep recipes. Full detail: `references/code-level-patterns.md`
9. Draft the report with recommendations, prioritizing any finding that repeats across all 3 page types (site-wide impact) over page-specific ones — and populate the Repeated Query Shapes table (every shape repeated more than ~5 times per page load), the **Per-Page Query Detail** breakdown (the complete, unfiltered per-page query list — **mandatory for deep**: 7 tables one per page via the `grep -c '## QUERY'` recipes, see gate in `references/report-template.md`; quick: `Skipped: quick — 3 pages only, Per-Page Detail deferred to deep`), and the Slowest Blocks/Templates table (every profiler timer past the threshold in `references/html-profiler-audit.md`), not just the ones already confirmed as bugs. **Always produce both** `report.md` (via `references/report-template.md`) and `report.html` (via `references/report.html.template` + inline CSS from `references/report-theme.md` — copy verbatim, no CDN) with identical sections/KPIs/per-page `<details>` (deep: 7 tables mandatory, quick: Skipped matrix row) — then ask "HTML sẵn ở `report.html` — bạn có muốn publish không? (1) `file://` sidebar `sidebar_open`, (2) `govard` static/`pub/media`, (3) `gh-pages`/`Vercel`, (4) không — tuỳ môi trường" before pushing. Templates: `references/report-template.md` + `references/report.html.template` + `references/report-theme.md`

**10. Self-verification gate — mandatory, run before presenting the report to the user:**

> **Pacing: do not inflate setup.** Run the reference-owned setup commands one at a time, then move directly into captures. If setup for a step takes more than a couple of calls, stop and read that step's reference file instead of iterating through unsupported variants.

Walk the draft report against every checkbox in the Audit Report Template (`references/report-template.md`), one by one. For each checkbox, exactly one of these must be true:
- It's checked, with evidence for it visible somewhere above in the report (a command output, a query-log count, a traced file:line).
- It's unchecked, with an explicit `Skipped: <reason>` line next to it (missing credentials, no Chrome DevTools MCP, environment doesn't apply).

A checkbox that is simply absent from the report — not checked, not marked skipped, just not mentioned — means step 10 hasn't been done yet. Go back and either run the missing step or add the skip reason; don't publish or hand off the report in that state. A report whose URLs section shows a cutoff URL without the full target visible (dev: absolute URL, client: full URI) fails the gate the same way. Only once every checkbox resolves to one of the two states above is the audit actually finished — both `report.md` and `report.html` (identical content, see step 9) must exist and have been checked against the template; publish `report.html` only if the user chose to in step 9 (otherwise keep both locally).

Files in this skill

  • SKILL.md20.4 KB
  • references/ajax-load-audit.md7.2 KB
  • references/cache-invalidation-audit.md7.9 KB
  • references/code-level-patterns.md7.5 KB
  • references/core-web-vitals.md3.2 KB
  • references/database-query-profiling.md29.1 KB
  • references/html-profiler-audit.md5.5 KB
  • references/infrastructure-checks.md7.9 KB
  • references/per-page-type-audit.md23 KB
  • references/report-template.md15.2 KB
  • references/report-theme.md4.9 KB
  • references/report.html.template7.7 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.