Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions server/migrations/0011_spans_attrs_gin.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
-- GIN index on span attributes so the traces list can filter by attribute
-- (attributes @> '{"key":"value"}') without seq-scanning the spans table.
-- Mirrors logs_attrs_gin; jsonb_path_ops is compact and serves the @> operator.
--
-- SET LOCAL statement_timeout = 0 for this migration only: the pool sets a 60s
-- statement_timeout, but building a GIN over the existing spans can take longer,
-- and we don't want the build aborted (which would crash-loop startup). The build
-- holds a write lock on spans, so ingest pauses briefly once while it runs.
SET LOCAL statement_timeout = 0;
CREATE INDEX IF NOT EXISTS spans_attrs_gin ON spans USING gin (attributes jsonb_path_ops);
9 changes: 6 additions & 3 deletions server/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,10 @@ pub async fn list_traces(
.and_then(|s| s.split_once('='))
.filter(|(k, _)| !k.is_empty())
.map(|(k, v)| serde_json::json!({ k: v }));
// service + time bound the spans scanned (index-friendly); the trace-level
// filters (name / attribute / errors / duration) are applied with HAVING so
// service + time bound the spans scanned (index-friendly). The attribute
// filter selects whole traces that contain a matching span via a subquery the
// spans_attrs_gin index can serve (a HAVING bool_or couldn't use the index).
// The remaining trace-level filters (name / errors / duration) are HAVING, so
// the per-trace aggregates stay computed over the whole trace.
let rows = sqlx::query_as::<_, TraceSummary>(
"SELECT trace_id,
Expand All @@ -77,10 +79,11 @@ pub async fn list_traces(
WHERE ($1::text IS NULL OR service = $1)
AND ($2::timestamptz IS NULL OR start_time >= $2)
AND ($3::timestamptz IS NULL OR start_time <= $3)
AND ($6::jsonb IS NULL
OR trace_id IN (SELECT trace_id FROM spans WHERE attributes @> $6))
GROUP BY trace_id
HAVING ($5::text IS NULL
OR (array_agg(name ORDER BY start_time))[1] ILIKE '%' || $5 || '%')
AND ($6::jsonb IS NULL OR bool_or(attributes @> $6))
AND (NOT $7::bool OR count(*) FILTER (WHERE status_code = 2) > 0)
AND ($8::float8 IS NULL
OR extract(epoch FROM (max(end_time) - min(start_time))) * 1000.0 >= $8)
Expand Down