Signals · 3 min read · Updated 2026-08-14
Transactions, traces, and spans
A trace is one request’s path. A span is a timed unit of work. A transaction is the root span Talaria meters.
APM without a data model is a stopwatch. The unit of work has to be named, nested, and queryable — otherwise you cannot ask “which route got slower after the deploy?”
The problem
A single HTTP request is not one timer. It is a tree: the server handler, each SQL statement, each outbound HTTP call, each Redis get, each queued job enqueue. Flatten that tree into one duration and you know the request was slow. Keep the tree and you know why.
What it is
OpenTelemetry defines a trace as a directed acyclic graph of spans. Each span has a name, a start and end time, a kind, optional parent, and attributes. Semantic conventions fix those attribute names so backends can query portably: http.request.method, http.route, http.response.status_code, db.system.name, db.query.text.
Span kinds describe the role, not the technology:
server— inbound work (PHP middleware, a pageload transaction).client— outbound calls (Guzzle, fetch,TalariaHttpClient).internal— in-process work that is neither.producer/consumer— queues and jobs.
In Talaria a transaction is a sampled root span — a span with no parent. That is the billable unit. Child spans travel with it and are not metered separately. See sampling and metering.
What it gives a team
- Route-level timing:
POST /checkoutas a name you can sort, not a raw URL with IDs. - A parent/child tree you can open as a waterfall when p95 moves.
- Correlation: errors carry
traceIdandspanIdso an issue is not an orphan stack.
How Talaria does it
SDKs send OTel-shaped batches to POST /spans/ingestBatch with API key scope spansWrite. Identifiers follow W3C: 32-hex traceId, 16-hex spanId. Resources map service.name from your project, service.version from release, and deployment.environment from the API key. The server stamps that environment. SDKs do not send it.
What official SDKs start as transactions:
- PHP / Silverstripe —
HTTPMiddlewarewraps the request (serverspan named likeGET /product). - Browser — a pageload transaction, plus fetch/XHR as
clientspans. - Flutter —
TalariaNavigatorObserverstarts a transaction per route;TalariaHttpClientrecords outbound HTTP. - Dart — manual
Talaria.startTransaction/startSpanwhen you are not on Flutter navigation.
Child instrumentation on PHP includes PDO / Silverstripe MySQL, Guzzle, Redis, and queued jobs when those modules are present. Dart does not auto-instrument databases. Browser Web Vitals are separate spans — see Web Vitals.
Ingest stores up to 200 child spans per transaction, and the waterfall loads up to 500 spans per trace. Span attributes follow OpenTelemetry semantic conventions on purpose, so an OTLP path stays coherent with what the SDKs already send.
How to read it
On Performance, the transaction table groups sampled roots by name and shows count, error rate, and p95. Tap a row to open a sample waterfall. Prefer route names over raw URLs; high-cardinality paths (IDs in the path) make the table noisy and the RED series less useful.
Turn it on
Tracing is off until you turn it on under Project settings. The dashboard lists transactions only after sampled roots arrive.
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.
Signals · 3 min
Sampling you can explainKeep every failing transaction. Sample a tenth of the rest. Bill the root, not every query. That is the whole policy.
In the request · 2 min
Distributed tracing with W3C Trace ContextOne trace ID from the browser, through the API, into the query that failed — carried on a standard header named traceparent.