Application traces that stop at "database call, 340ms" are not enough to fix a slow query. Here is how to see the actual SQL and its plan inside the trace.
Most application frameworks instrument the database driver enough to show that a call happened and how long it took, but not what the query actually was or why it was slow. Getting that detail into Helix takes two small configuration changes, one on Postgres and one on the SDK.
1. Enable query normalization on Postgres
Turn on pg_stat_statements if it is not already active, and set the collector to poll it. This gives Helix the aggregate view, average duration, call count, and rows returned, per normalized query shape, independent of any single traced request.
-- postgresql.conf
shared_preload_libraries = 'pg_stat_statements'
pg_stat_statements.max = 10000
pg_stat_statements.track = all2. Turn on statement capture in the SDK
By default the database instrumentation records duration only, to avoid leaking parameter values into span attributes. Turning on statement capture records the normalized query text (with literal values redacted) as a span attribute, which is what makes a trace actionable instead of just a timer.
require('@helix-obs/sdk').start({
service: 'billing-monolith',
instrumentations: {
pg: { captureStatement: true, redactParams: true },
},
});3. Query slow statements across all traces
Once statement text is on the span, slow queries are one aggregate query away, no more scrolling through individual traces hoping to spot a pattern.
from traces
| where span.name == "pg.query"
| where duration > 200ms
| summarize count(), avg(duration) by db.statement
| sort by avg(duration) desc4. Connect the plan when duration alone is not enough
Duration explains that a query is slow, not why. For the top offenders from the query above, pull the current plan directly from Postgres and check for a sequential scan on a table where an index should apply, this is almost always the actual fix, not query rewriting.
EXPLAIN ANALYZE
SELECT * FROM orders WHERE customer_id = 4821 AND status = 'pending';- Enable
pg_stat_statementsfor aggregate query statistics independent of tracing. - Turn on statement capture with parameter redaction so spans carry the actual query shape.
- Aggregate slow statements across all traces instead of reading them one at a time.
- Pull
EXPLAIN ANALYZEfor the worst offenders, duration tells you where, the plan tells you why.