Troubleshooting · Developer docs

Troubleshooting

Grouped by symptom, never by module, because the person with the problem does not know which module has it. One hundred questions across ten pages, each answered with the causes in the order they usually turn out to be true.

Symptom 4 questions

Performance

Four performance symptoms, each with its causes in the order they usually turn out to be true and the measurement that tells them apart.

Almost every performance question starts with the same two measurements: the response headers, which say whether the page came from the cache, and the time to first byte, which says whether the time is spent on the server or in the browser. Take both signed out before changing anything (Measure first).

One page is dramatically slower than the rest

Request it twice signed out and compare with a fast page on the same site. In order:

  1. It is never served from the cache. The second request still says miss: a preventCaching() in its template or in an include only it uses, a redirect, a status other than 200, or a pattern in the exclusions setting (the diagnostic). Every visitor then waits for a render.
  2. It is cached, but under many keys. The template reads a query parameter that varies with every visit, such as a campaign or click id, so almost every request is a new copy and a render (query parameters).
  3. Its render is expensive. A long list whose rows read related records or call a lookup helper, or a deep component tree (query-per-row, nesting). Find the section responsible by removing parts on a development environment and timing each.
  4. It calls something outside the database while rendering, such as a search view or a lookup on every row. Each is a request to another service.
  5. The time is in the browser, not the server. Time to first byte is normal and the page is still slow: images at their uploaded size, a widget or a tag only this page loads (Images).
Open this answer on its own page →
The site is fast for anonymous visitors and slow for logged-in ones

A signed-in member is served the same stored copy of a public page as anyone else, so slowness for members comes from what renders for them. In order:

  1. The pages they use are the ones that always render. Member pages under /my-account, members-only records and the cart are never stored, for anyone. Their cost is the full render on every view, so they are where query-per-row hurts most (member areas and carts).
  2. Fragments render on every page view. A cached public page that fetches a personal fragment pays for that fragment on every view of every member. A fragment that loads a menu, a list or a cart line by line is a slow page in disguise. Keep fragments small (in a fragment template).
  3. Per-member lists without a limit. Order history, enrolments or saved items printed in full grow with the member's history. Paginate them.
  4. The session cookie. A request carrying PHPSESSID works without the shared Memcached layer, so each render does a little more set-up work. On its own this is a small cost; it matters only on top of one of the above.
  5. It is not a member problem. If staff are signed in to the admin and see a slow site, check whether they are on the same hostname and environment as visitors; staging is often slower and is easy to mistake for production.
Open this answer on its own page →
The first load is huge but subsequent pages are fine

The browser keeps scripts, stylesheets, fonts and images after the first visit, so the first load carries the whole theme. Look at the first load's waterfall in a private window. In order:

  1. One bundle for the whole site. The checkout's or a form library's code loaded on the home page (Bundle size).
  2. Images at their uploaded size on the first page people land on, usually the home page and its hero (request the size you display).
  3. Every font weight preloaded, or fonts from several families (loading fonts).
  4. Third-party scripts from a tag manager, which are downloaded on the first visit and often run again on every page (the tag manager problem).

Two first-load effects are expected and pass. After every deploy, asset URLs carry the new version and are downloaded again, once. And the first request for a new image size, and the first visit to a page after its cache was cleared, wait for the image to be generated or the page to be rendered; the next visitor does not.

Open this answer on its own page →
Memory or timeout errors on a listing page

A listing that fails with an out-of-memory error, or takes so long that the request is cut off, is loading more than it shows. In order:

  1. No limit on the query. A $_query->findBy() without a limit, or a getter that returns every child of a large parent, loads every record into memory, however few the page prints. Pass a limit, or paginate.
  2. Related records read for every row. Each record a template touches stays in memory until the request ends, so a long list that reads relations on every row holds the list and all of its relations (a relation in a loop).
  3. Loading the records behind search results. getRecord() on every result of a search loads the whole record per row. Render from the indexed fields instead (what the template receives).
  4. A list that grew. It worked at launch with fifty records and fails with five thousand. Any list that can grow needs pagination, or a search view, which pages by default (filtering a listing).

On a development environment the error page shows the template and line; on production, look under Template Errors in the admin. Fix the query, not the limit: a page that needs more memory today will need more again next year.

Open this answer on its own page →
Go deeper

Was this page helpful?

Your answer goes to the docs team.