OpenTelemetry Operator Guide
Status: Available feature with comprehensive operator guidance.
pg_trickle supports W3C Trace Context propagation through the refresh pipeline. When enabled, distributed traces initiated in application sessions are linked to CDC capture events and refresh spans, allowing full end-to-end latency attribution from user write to materialized result.
How It Works
- Application session sets
pg_trickle.trace_idto a W3C traceparent header. - CDC trigger captures
pg_trickle.trace_idfrom the session GUC and stores it in the__pgt_trace_contextcolumn of the change buffer table. - Scheduler reads the trace context when consuming the change buffer.
- Refresh pipeline propagates the trace context through the DIFF/FULL refresh cycle and exports a child span to the configured OTLP endpoint.
Application session pg_trickle background worker
│ │
│ SET pg_trickle.trace_id = '...' │
│ INSERT INTO source_table ... │
│ └─► CDC trigger captures trace ──────┤
│ │ Reads change buffer
│ │ Opens child span
│ │ Exports span to OTLP
│ └─► Refresh complete
Configuration
Minimal setup
-- Enable trace propagation
ALTER SYSTEM SET pg_trickle.enable_trace_propagation = true;
-- Set the OTLP/HTTP endpoint
ALTER SYSTEM SET pg_trickle.otel_endpoint = 'http://localhost:4318';
SELECT pg_reload_conf();
Per-session usage
-- Set traceparent before DML (propagates through CDC to refresh span)
SET pg_trickle.trace_id = '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01';
-- Your DML here
INSERT INTO orders (id, total) VALUES (42, 99.99);
-- pg_trickle CDC trigger stores the trace context in the change buffer
GUC Reference
| GUC | Type | Default | Description |
|---|---|---|---|
pg_trickle.enable_trace_propagation | bool | false | Enable W3C Trace Context capture and export |
pg_trickle.otel_endpoint | string | '' | OTLP/HTTP endpoint; pg_trickle appends /v1/traces and /v1/metrics |
pg_trickle.trace_id | string | '' | Session W3C traceparent header |
Collector Configuration Examples
Jaeger (all-in-one)
docker run -d \
-p 4318:4318 \
-p 16686:16686 \
jaegertracing/all-in-one:latest
ALTER SYSTEM SET pg_trickle.otel_endpoint = 'http://localhost:4318';
ALTER SYSTEM SET pg_trickle.enable_trace_propagation = true;
SELECT pg_reload_conf();
Access traces at http://localhost:16686. Look for service name pg_trickle.
When the endpoint is configured, the existing monitoring cadence also sends a
bounded OTLP/HTTP metrics batch to {endpoint}/v1/metrics. It contains the
target freshness, exact p95, and breach-duration gauges in seconds. Metrics
use bounded db_oid, db_name, schema, and name attributes plus the
controller status; missing percentile evidence is omitted.
Grafana Tempo
# docker-compose.yaml excerpt
tempo:
image: grafana/tempo:latest
ports:
- "4318:4318" # OTLP HTTP
- "3200:3200" # Tempo HTTP API
ALTER SYSTEM SET pg_trickle.otel_endpoint = 'http://tempo:4318';
OpenTelemetry Collector (recommended for production)
# otel-collector-config.yaml
receivers:
otlp:
protocols:
http:
endpoint: "0.0.0.0:4318"
exporters:
otlp:
endpoint: "your-backend:4317" # Collector export remains gRPC
tls:
insecure: false
service:
pipelines:
traces:
receivers: [otlp]
exporters: [otlp]
metrics:
receivers: [otlp]
exporters: [otlp]
ALTER SYSTEM SET pg_trickle.otel_endpoint = 'http://otel-collector:4318';
Failure Behavior
pg_trickle's trace export is best-effort:
| Scenario | Behavior |
|---|---|
| OTLP endpoint unreachable | Span export silently skipped; refresh continues |
| OTLP endpoint returns error | Warning logged; refresh continues |
| OTLP connection timeout | Export attempt abandoned after 2 s; refresh continues |
trace_id not set | Span has no parent; exported as a root span |
enable_trace_propagation = false | No spans exported; no overhead |
Important: Trace export failures never block or delay refresh cycles. Monitoring refresh latency with
pgtrickle.sla_summary()is unaffected by OTLP endpoint health.
Verifying Trace Export
Check that trace context is captured
-- After enabling trace propagation and making a write:
SET pg_trickle.trace_id = '00-aaaabbbbccccdddd0000111122223333-0102030405060708-01';
INSERT INTO my_source_table VALUES (...);
-- Check the change buffer (replace <oid> with the source table OID):
SELECT __pgt_trace_context
FROM pgtrickle_changes.changes_<oid>
ORDER BY ctid DESC
LIMIT 5;
-- Should return the traceparent you set
Check the OTLP/HTTP endpoint
# Quick check: verify the collector accepts the metrics route
curl -i -X POST http://localhost:4318/v1/metrics \
-H 'Content-Type: application/json' \
--data '{"resourceMetrics":[]}'
Span Attributes
Refresh spans exported by pg_trickle include:
| Attribute | Value |
|---|---|
service.name | pg_trickle |
db.system | postgresql |
db.name | current database name |
pgt.stream_table | schema.stream_table_name |
pgt.refresh_mode | DIFFERENTIAL or FULL |
pgt.duration_ms | Refresh duration in milliseconds |
pgt.cycle_id | Scheduler cycle identifier |
Stable Span Names
These names are part of the v0.97 compatibility surface. Dashboards and collector routing can match them without depending on implementation details:
| Span name | Boundary |
|---|---|
pgtrickle.cdc_drain | Drain captured source changes |
pgtrickle.dvm_plan | Build the differential plan |
pgtrickle.merge_apply | Apply the result delta |
pgtrickle.notify_emit | Emit refresh notifications |
pgtrickle.scheduler_tick | Run one scheduler tick |
pgtrickle.refresh_cycle | Execute one refresh cycle |
pgtrickle.delta_execute | Execute a planned delta |
pgtrickle.frontier_advance | Advance the refresh frontier |
pgtrickle.cleanup | Retire consumed change state |
The OTLP scope version is the packaged pg_trickle version. This lets a collector or trace backend identify the exact extension build that emitted a span.
Troubleshooting
Spans not appearing in the collector
-
Verify
enable_trace_propagation = true:SHOW pg_trickle.enable_trace_propagation; -
Verify
otel_endpointis set and reachable:SHOW pg_trickle.otel_endpoint; -
Check PostgreSQL logs for OTLP export warnings:
grep -i "otel\|otlp\|trace" /var/log/postgresql/postgresql.log -
Ensure the collector is listening for OTLP/HTTP on the configured port and accepts
/v1/tracesand/v1/metrics.
__pgt_trace_context column missing
The column is added automatically when upgrading. If it is missing from a change buffer table:
-- Re-run the migration (idempotent):
ALTER TABLE pgtrickle_changes.changes_<oid>
ADD COLUMN IF NOT EXISTS __pgt_trace_context TEXT;
Integration Test
A dockerized integration test against a local OTLP collector is available
under tests/e2e_otel_tests.rs. It verifies:
- Span export success to a live collector
- Timeout and endpoint-failure handling (export is skipped, refresh completes)
- Trace context round-trip from session GUC through change buffer to span
Run with:
just test-e2e -- --test e2e_otel_tests
See also: CONFIGURATION.md · TROUBLESHOOTING.md