In the request · 3 min read · Updated 2026-08-14
Slow queries and N+1
Most “the database is slow” incidents are the application asking the same question too many times — or once, too late.
Object-relational mappers made N+1 famous: load a list of orders, then lazily load each customer in a loop. Twenty orders become twenty-one queries. The database is fine. The chatty application is not. APM that cannot show that pattern will send you to tune indexes that were never the problem.
The problem
Two database failures look identical on a host dashboard and opposite on a waterfall. A 900ms SELECT with a missing index is one fat bar. An N+1 is a comb of identical short bars. Treating both as “MySQL is slow” wastes the week.
What it is
A slow query in Talaria is a database span whose duration meets a threshold (API default 200ms). The span carries OpenTelemetry attributes: db.system.name, sanitized db.query.text, db.operation.name.
N+1 detection is a heuristic applied at ingest: if the same db.query.text appears ten or more times in one trace, those spans are flagged. The waterfall shows an N+1 badge; Performance lists them under “N+1 and slow queries.” This is exact query-text matching after sanitization — not ORM-aware lazy-load analysis, and not a rewrite of your DataObject relations.
Trace
00-3a91… · 184 ms
GET /checkout
SELECT orders
SELECT customer
N+1SELECT customer
N+1SELECT customer
N+1POST stripe.com
What it gives a team
- A queue of queries worth reading, ordered by duration, without a DBA workstation.
- A badge that says “you asked this ten times” before you add a composite index.
- Evidence you can paste into a PR: this trace, this query text, this count.
How Talaria does it
PHP records DB spans through TracingPdo / TracingPdoStatement. Silverstripe wraps MySQL when tracing is on (TracingMySQLDatabase). Redis is a separate db.system.name=redis path — it shows as a dependency peer, not this slow-query list (the list filters db_system IS NOT NULL with a duration threshold aimed at query work).
The dashboard calls SpansEndpoint.slowQueries with a default minDurationMs of 200. Project Settings can store slowQueryMs; the list you see today uses the API default unless a caller passes a different minimum.
Dart and Flutter do not auto-instrument SQL. Browser JavaScript has no application database. N+1 here is a server-side PHP/Silverstripe signal.
How to read it
- Sort mentally: one slow statement vs many identical ones. The fix is different.
- Open the parent waterfall before rewriting the query. Confirm the span sits on the critical path.
- If the text is a SELECT in a loop, look for a join,
prefetch, or batched ID query in your ORM — not a new index on an already-indexed primary key.
Turn it on
Enable tracing on the PHP or Silverstripe SDK. PDO and Silverstripe MySQL wrappers attach automatically when tracing is on. There is no separate “database APM” flag.
What this is not
This is not pg_stat_statements, EXPLAIN, or a host agent on the database box. It does not see queries from other apps sharing the same MySQL. It will miss N+1 that uses slightly different SQL text, and it will not flag nine repeats (the threshold is ten).
Related guides
Signals · 3 min
How to read a request waterfallA waterfall is the trace drawn as time. The critical path is the longest chain of parent-to-child work — that is usually the fix.
In the request · 3 min
External dependenciesEvery outbound call is a peer: HTTP hosts, databases, Redis, queues. Watch their error rate and p95 the same way you watch your own routes.
Signals · 3 min
Transactions, traces, and spansA trace is one request’s path. A span is a timed unit of work. A transaction is the root span Talaria meters.