Data Structures
Schemas, database design, and data formats for the tracing system.
Span Structure
Canonical Span Schema
Spans sent from SDK to backend follow the shared OTELSpan schema (packages/rhesis/src/rhesis/telemetry/schemas.py), serialized as JSON:
span_name must match ai.<domain>.<action> (domains chain, workflow, pipeline rejected) or function.<name>.
Test Execution Context
Context attributes added to spans during test execution:
Database Schema
trace Table
Column Details
| Column | Type | Description |
|---|---|---|
id | UUID | Primary key |
trace_id | VARCHAR(32) | OpenTelemetry trace ID (groups spans) |
span_id | VARCHAR(16) | OpenTelemetry span ID (unique per span) |
parent_span_id | VARCHAR(16) | Parent span for hierarchy |
project_id | UUID | Project isolation, FK with ON DELETE CASCADE |
organization_id | UUID | Multi-tenancy isolation (no FK) |
environment | VARCHAR(50) | e.g. development, production |
conversation_id | VARCHAR(255) | Groups turns of a multi-turn conversation |
test_run_id / test_result_id / test_id | UUID | Linked test execution entities |
span_name | VARCHAR(255) | Operation name (ai.llm.invoke) |
span_kind | VARCHAR(20) | OTEL span kind (CLIENT, INTERNAL, …) |
duration_ms | FLOAT | Calculated duration |
status_code | VARCHAR(20) | OK, ERROR, UNSET |
attributes / events / links / resource | JSONB | Span data |
processed_at | TIMESTAMP | Set when enrichment last ran; NULL triggers re-enrichment |
enriched_data | JSONB | Cached enrichment results |
trace_metrics / trace_metrics_status_id / trace_metrics_processed_at | JSONB / UUID / TIMESTAMP | LLM-based metric evaluation results and status |
trace_reviews | JSONB | Human review annotations |
Indexes
Enrichment Data
The enriched_data JSONB column caches computed values (EnrichedTraceData, schemas/enrichment.py):
Enrichment Fields
| Field | Description |
|---|---|
costs.total_cost_usd / total_cost_eur | Total cost across all LLM spans |
costs.breakdown | Per-span cost breakdown |
anomalies | Detected anomalies: slow_span (>10s), high_token_usage (>10,000 tokens), error |
metrics | Trace-level duration, span count, error count |
models_used / tools_used / operation_types | Unique values seen across the trace’s spans |
root_operation | The root span’s span_name |
Common Query Patterns
Get Trace by ID
Get Traces for Test Run
Get LLM Calls with Specific Model
Get Error Traces
Get High-Cost Traces
HTTP Request Format
Ingestion Endpoint
Endpoint: POST /telemetry/traces
Headers:
Payload:
Response Codes
| Status | Meaning | Action |
|---|---|---|
| 200 | Success | Spans ingested (post-processing dispatched separately) |
| 401 | Unauthorized | Check API key |
| 422 | Validation error, or no project_id could be resolved | Fix span format, or pass a project-scoped token / X-Project-Id header |
| 500 | Server error | Retry with backoff |
Validation Errors
Common 422 errors:
Why PostgreSQL + JSONB?
| Aspect | Benefit |
|---|---|
| Single Database | Simplifies operations, existing expertise |
| JSONB Flexibility | Schema can evolve without migrations |
| GIN Indexes | Fast attribute queries |
| ACID Compliance | Reliable linking operations |
| Familiar SQL | Easy debugging and ad-hoc queries |
Future Scaling
If trace volume exceeds PostgreSQL capacity:
- Partition by time - Monthly partitions for retention
- TimescaleDB - Hypertable for time-series optimization
- ClickHouse - Columnar store for analytics
- Archive strategy - Move old traces to cold storage