Tracing#

Strata emits OpenTelemetry spans across every meaningful tier of the request and worker paths. Each span carries the strata.component attribute so an operator can filter the entire gateway path or the entire worker path in one Jaeger query, and each worker iteration appears as a discrete trace so a slow / failing tick is easy to correlate with the meta + data ops it triggered.

The wire-up env vars + tail-sampler + ring buffer behaviour live in Monitoring. This page is the operator-facing reference for what spans exist, how they are named, and how to filter them.

Coverage matrix#

TierSpan name shapeEmitterstrata.componentExtra attributes
HTTP server<METHOD> <path>otel.NewMiddlewaregatewayhttp.method, http.target, http.status_code, request_id
Cassandra metameta.cassandra.<table>.<op>the Cassandra query observergatewaydb.system=cassandra, db.operation, db.cassandra.table, request_id, retroactive (q.Start, q.End) timestamps
TiKV metameta.tikv.<table>.<op>the TiKV Store-method decoratorgatewaydb.system=tikv, db.operation, db.tikv.table
RADOS datadata.rados.<op>the RADOS ObserveOp helpergatewaypool, oid, retroactive (start, end) timestamps
S3-over-S3 dataS3.<Operation>go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws v0.68 (semconv v1.40) installed by the S3 backend’s OTel middleware hookgatewayrpc.system.name=aws-api, rpc.method=S3/<op>, aws.region, http.response.status_code, strata.s3_cluster=<id>
Worker iteration (parent)worker.<name>.tickotel.StartIteration / EndIteration (re-exported as workers.StartIteration)workerstrata.worker=<name>, strata.iteration_id=<atomic.uint64>
Worker sub-opssee belowper-worker tracer.Start(ctx, …) under the iteration parentworkerstrata.worker=<name> + worker-specific keys

Worker sub-op spans#

WorkerSub-op span(s)
gcgc.scan_partition, gc.delete_chunk
lifecyclelifecycle.scan_bucket, lifecycle.expire_object, lifecycle.transition_object
replicatorreplicator.copy_object
notifynotify.deliver_event
access-logaccess_log.flush_bucket
inventoryinventory.scan_bucket
audit-exportaudit_export.export_partition
manifest-rewritermanifest_rewriter.rewrite_bucket
quota-reconcilequota_reconcile.scan_bucket
usage-rollupusage_rollup.sample_bucket
rebalancerebalance.scan_bucket, rebalance.move_chunk

Tracer names#

Every tier installs a named tracer via tp.Tracer("strata.<area>"):

Tracer nameOwner
strata.httpHTTP server middleware
strata.meta.cassandraCassandra QueryObserver
strata.meta.tikvTiKV Store decorator
strata.data.radosRADOS ObserveOp
strata.data.s3S3 backend (otelaws middleware)
strata.worker.<name>each worker (gc, lifecycle, replicator, notify, access-log, inventory, audit-export, manifest-rewriter, quota-reconcile, usage-rollup, rebalance)

The serverapp entrypoint wires the meta + data tracers after strataotel.Init runs (so the provider exists before backends are built); the supervisor passes *strataotel.Provider to every worker via workers.Dependencies.Tracer, and each worker resolves its named tracer with deps.Tracer.Tracer("strata.worker.<name>").

Filter recipes#

“Everything that ran on the gateway request path”#

strata.component=gateway

Catches HTTP server spans + every meta / data child span underneath them. Pair with request_id=<uuid> to scope to one customer ticket.

“Everything any background worker emitted”#

strata.component=worker

One Jaeger query covers all ten workers. Layer strata.worker=<name> to scope to one worker.

“GC iterations on this replica that failed in the last hour”#

strata.component=worker
strata.worker=gc
status=Error
service.instance.id=<hostname>

The iteration span flips to Error if the iteration body returns an error OR any sub-op span recorded an error (sticky-err accumulator inside gc.Worker.drainCount / lifecycle.Worker.iterErr). The tail-sampler always exports failing spans regardless of STRATA_OTEL_SAMPLE_RATIO, so failures land in Jaeger even at a 0.01 sample ratio.

“All S3 SDK calls against the secondary backend”#

strata.component=gateway
strata.s3_cluster=secondary

strata.s3_cluster is stamped per-cluster by the otelaws AttributeBuilder registered in the S3 backend’s OTel middleware hook, so multi-cluster routing (see S3 multi-cluster) is filterable end-to-end.

“Which TiKV table is hot on this trace”#

strata.component=gateway
db.system=tikv

Each meta.tikv.<table>.<op> span carries db.tikv.table=<table>; sort Jaeger by duration to find the offender.

Sampling#

The tail-sampler (otel.Sampler) decides at OnEnd:

  • status=Error → exported.
  • http.status_code >= 500 → exported.
  • Otherwise → kept at STRATA_OTEL_SAMPLE_RATIO (default 0.01).

Worker iteration + sub-op spans flow through the same sampler, so failing iterations always export regardless of the configured ratio. The ring buffer (STRATA_OTEL_RINGBUF=on, default) retains every span in-process under a bytes budget regardless of the sampler, so the operator console’s /admin/v1/diagnostics/trace/{requestID} endpoint always has the full trace for any recent request — even sampled-out ones.

Iteration-id semantics#

strata.iteration_id is a per-worker atomic.Uint64 counter (map keyed on worker name, mutated under a sync.Mutex). The counter is process-local, NOT cluster-wide — different replicas can emit the same strata.iteration_id=42 for the same worker. The intended filter shape is:

strata.worker=gc
service.instance.id=<hostname>
strata.iteration_id=42

…which scopes to one iteration on one replica. The supervisor + gc fan-out share one counter per worker name; fan-out shards do NOT get distinct counters (so the chip-level “leader heartbeat” stays the heartbeat-level acquired / released pair documented in the project CLAUDE.md).

See also#

  • Monitoring — env knobs + ring buffer + bundled tracing stack.
  • Observability deep dive — implementation rationale (tail-sampler, semconv version, observer retroactive-timestamp trick).