Using Laravel Telescope, how do you find the slow query behind a sluggish admin page, and how does the query watcher decide a query is slow?
answer
- request entry groups its queries
- slow option, 100 ms by default
- tag slow on the entry
- file and line of the caller
- ignore_packages skips vendor frames
basics
~20 sOpen the admin page's request in Telescope and read its queries: each shows SQL with bindings, duration and the calling file and line. The query watcher tags any query at or above its slow option (100 ms by default) as slow.
solid answer
~40 sLoad the sluggish admin page, then open its **request** entry in Telescope: it shows the duration and, in the same batch, every query the request ran. Each **query** entry has the SQL with bindings substituted, the connection, the time in milliseconds, and the application file and line that issued it. The query watcher's `slow` option in `config/telescope.php` defaults to `100`; a query taking that many milliseconds or more gets `slow: true` and the tag `slow`, so filtering the queries screen by that tag lists the culprits across all requests. Lower it (say `'slow' => 50`) to catch borderline queries. With `ignore_packages` on (the default), the reported caller skips vendor frames, so the line points at your controller or model code, and queries issued only from packages are not recorded.
code
php · 15 lines<?php
// config/telescope.php (excerpt)
use Laravel\Telescope\Watchers;
return [
'watchers' => [
Watchers\QueryWatcher::class => [
'enabled' => env('TELESCOPE_QUERY_WATCHER', true),
'ignore_packages' => true,
'ignore_paths' => [],
'slow' => 50, // ms; tag queries at or above 50 ms as "slow"
],
],
];go deeper
Know that Telescope lists each request's queries with SQL and duration and marks slow ones.
Explain the slow option and its 100 ms default, the slow tag, the caller file and line, and how batches tie queries to a request.
Use tags and filters to find slow queries across requests, understand ignore_packages, and know the default filter drops slow queries outside local.
Decide where query timing should come from in each environment, weighing Telescope's detail against production-grade monitoring.
## The scenario An admin page listing orders has become slow. You suspect the database but not which query. Telescope lets you answer that after the fact, without adding any code. ## Step by step 1. **Load the page** once with Telescope recording (locally, or on an environment where your filter records it). 2. **Open Requests** in the dashboard and pick the admin route. The entry shows the method, URI, status, **duration**, memory, headers, payload, session and the response. 3. **Look at the batch.** Everything the request did shares its batch id, so the request page lists its queries, model events, cache calls and log lines together. Scan the query list for the longest durations. 4. **Open the slow query.** A query entry contains: - the SQL with bindings substituted, ready to copy into a database client; - `time`, the duration in milliseconds as reported by the connection; - `connection` and `driver`; - `file` and `line`, the first stack frame outside the ignored paths; - a family hash of the SQL, so identical statements group together. 5. **Fix and re-check**: add the missing index, reduce the columns or rows, or remove repeated queries, then reload and compare durations. ## How "slow" is decided The query watcher is configured in `config/telescope.php`: ```php Watchers\QueryWatcher::class => [ 'enabled' => env('TELESCOPE_QUERY_WATCHER', true), 'ignore_packages' => true, 'ignore_paths' => [], 'slow' => 100, ], ``` For every `QueryExecuted` event it compares the query's time with `slow`: - time **greater than or equal to** `slow` sets `content.slow = true` and adds the tag **`slow`**; - otherwise the query is recorded without the flag. The threshold is in milliseconds. `100` suits a typical web app; an admin panel over large tables might use `50` locally to surface queries before they become a problem. Because `slow` is a tag, the Queries screen's tag search (`slow`) lists every slow query across all recorded requests, not just one page. In a filter callback, `$entry->isSlowQuery()` identifies them, which is how you would choose to keep slow queries on a non-local environment. ## What the caller line means `ignore_packages` controls which stack frames count as the caller: | `ignore_packages` | Frames skipped | Effect | |---|---|---| | `true` (default) | everything under `vendor/` | the caller is your own code; queries run only by packages have no such frame and are not recorded | | `false` | only `vendor/laravel` | package queries are recorded, attributed to the package file | `ignore_paths` adds your own directories to skip, for example a repository base class, so the reported line is the more useful one above it. ## A worked example Suppose the admin orders page takes 2.3 seconds. Its request entry lists 52 queries: 1. One query takes 1,800 ms: `select * from "orders" where "status" = 'pending' order by "created_at" desc`, reported from `app/Http/Controllers/Admin/OrderController.php` line 34 and tagged `slow`. Copying it into a database client and checking the plan shows it scans the whole table. 2. Fifty identical `select * from "customers" where "id" = ? limit 1` queries, each a few milliseconds, come from the orders table Blade partial: a relation loaded once per row. The first is a single slow query, fixed at the database level; the second is many fast queries whose total matters. Telescope shows both, but only the first carries the `slow` tag, which is why reading the whole batch matters as much as the tag search. ## Other watchers that help on the same page - The **model watcher** with `hydrations` enabled shows how many models of each class were loaded, which exposes a page that hydrates thousands of rows. - The **request watcher**'s `size_limit` (64 KB by default) caps how much of the response is stored. - Repeated identical SQL with different bindings, visible in the batch, points to a query inside a loop. ## Limits - Telescope reports the time the database connection measured; it is not a profiler for PHP code. - On a non-local environment the published filter only keeps exceptions, failed requests, failed jobs, scheduled tasks and monitored tags, so slow queries are **not** recorded there unless you add `isSlowQuery()` to the filter.
- Telescope is recording on staging, but no slow queries appear there, though they do locally. Why?The published `TelescopeServiceProvider` filter records everything only in `local`; elsewhere it keeps reportable exceptions, failed requests, failed jobs, scheduled tasks and monitored tags. Slow queries are not in that list. Add `$entry->isSlowQuery()` to the filter, or monitor the `slow` tag, to keep them on staging.
- Why might a query run by a third-party package never appear in Telescope?With `ignore_packages` true, the watcher looks for the first stack frame outside `vendor/`. A query triggered entirely from package code has no such frame, so the watcher finds no caller and records nothing. Set `ignore_packages` to false to record package queries, attributed to the package file.
Telescope's slow tag works like a speed camera on a road: every car is logged, but only the ones at or above the limit get a ticket you can search for later. Moving the limit from 100 to 50 does not make cars slower; it just tickets more of them.
saying these in an interview costs you the question
- Telescope's slow flag is based on the whole request's duration.
- The slow threshold is set in seconds.
- Only queries strictly above 100 ms are tagged slow.
- Telescope shows query SQL with ? placeholders and no binding values.
- The default filter keeps slow queries in every environment.