Distributed tracing in 2026: revisit Jaeger, Grafana Tempo and SigNoz before you store another span

Picture a bus, train and hotel booking platform in Kochi (an invented example, not a real client). Customers book from an Android app, an iOS app and a web app. Behind them sit about forty Go and Java services on Kubernetes: search, pricing, seat inventory, UPI and card payments, hotel partner APIs and a notification service that sends tickets on WhatsApp and SMS. Tracing exists, but barely. A Jaeger 1.x setup from three years ago writes to a small Elasticsearch cluster with 1 percent head sampling. During the Onam travel rush, the support team reported that “payment went through but no ticket came”, and the one trace that would have explained it had not been sampled. The platform team has now agreed to rebuild tracing properly and keep it self-hosted. One architect wants to move to Jaeger v2 on OpenSearch, because the team knows Jaeger and the UI already works. A second wants Grafana Tempo on object storage, because the company already runs Grafana and Prometheus and storage cost is the real worry. A third wants SigNoz, because it keeps traces, logs and metrics in one ClickHouse database with one UI.

All three are open-source tracing backends that accept OpenTelemetry (OTLP) data and run on your own servers. They are not instrumentation libraries, not log pipelines and not a replacement for metrics. This post compares Jaeger, Grafana Tempo and SigNoz on storage model and cost, sampling, search and query languages, retention, multi-tenancy, scale operations, licences and security. Facts come from release notes, official documentation, source repositories, licence files and CVE records, checked on 13 October 2026. The drill near the end is my own run on this Linux box.

The short version

  • A tracing backend has two jobs: keep spans cheaply for a fixed time, and answer two kinds of questions. “Show me trace X” is easy for all three. “Find the traces where something went wrong” is where they differ.
  • Jaeger 2.22.0 (6 October 2026, 7:57 PM IST) is now a custom build of the OpenTelemetry Collector. It is Apache 2.0, stores spans in Elasticsearch, OpenSearch, Cassandra, Badger, memory or ClickHouse, and still has the best-known trace UI. Version 1 binaries are no longer published since January 2026.
  • Grafana Tempo 3.1.0 (29 September 2026, 10:36 PM IST) stores traces as Parquet blocks in object storage and searches them with TraceQL. Tempo 3.0 replaced ingesters with a Kafka-based write path for the distributed mode; the single-binary mode needs no Kafka. The licence is AGPLv3.
  • SigNoz 0.145.0 (5 October 2026, 12:11 PM IST) is a full observability product on ClickHouse: traces, logs, metrics, dashboards and alerts. The core is MIT, the enterprise directories are under a commercial licence, and its ingestion collector is AGPLv3. It had eight CVEs published between June and September 2026, all fixed by 0.143.0.
  • Sampling matters more than the backend choice. Tail sampling in the OpenTelemetry Collector keeps every error and every slow trace; head sampling at the SDK decides before anything has gone wrong.
  • In my drill, all three answered the same four questions correctly for 12,000 spans. Jaeger search was complete at once. Tempo search caught up after about 35 seconds and never found spans that arrived with timestamps 5 to 10 minutes old, though it found them by trace ID. Tail sampling kept 66 of 66 error traces; simulated 10 percent head sampling kept 7.

Where things stand on 13 October 2026

Dates are from each project’s GitHub release page; times are converted from UTC to IST. Licences are from the LICENSE files at the tagged release.

Project

Release

Date (IST)

Notes

Licence

Jaeger

2.22.0

6 October 2026, 7:57 PM

Current tip; Elasticsearch numeric attribute indexing becomes a config option; native trace summaries gate stable; structured filters gate beta; span ordering in API v3

Apache 2.0

Jaeger

2.21.0

15 September 2026, 4:35 AM

ClickHouse storage feature gate promoted to stable; v1 HTTP endpoints the UI no longer uses removed; search depth capped at 10,000

Apache 2.0

Jaeger

2.14.0

2 January 2026, 7:59 AM

First release after legacy v1 collector, query and ingester binaries stopped being published

Apache 2.0

Jaeger UI

2.22.0

6 October 2026, 7:45 AM

Released alongside the backend

Apache 2.0

Grafana Tempo

3.1.0

29 September 2026, 10:36 PM

Current tip; redaction by TraceQL query; TraceQL metrics arithmetic; extrapolation from sampling probability; vParquet5 default; signed images

AGPLv3 (some directories Apache 2.0)

Grafana Tempo

3.0.3, 2.10.8 and 2.9.5

14 August 2026, 2:07 AM

Patch releases on three lines for Go and dependency CVEs

AGPLv3

Grafana Tempo

3.0.0

28 May 2026, 8:39 PM

Kafka-based ingest replaces ingesters in microservices mode; compactor replaced by backend scheduler and workers; TraceQL metrics generally available

AGPLv3

SigNoz

0.145.0

5 October 2026, 12:11 PM

Current tip of a weekly release train; fine-grained authorisation work, log pipeline and explorer changes

MIT core, Enterprise License for ee/

SigNoz

0.143.0

23 September 2026, 12:57 PM

Fixes for the default JWT secret and session revocation CVEs

MIT core, Enterprise License for ee/

SigNoz OTel Collector

0.144.13

8 October 2026, 10:57 PM

The ingester and schema migrator that write to ClickHouse

AGPLv3

SigNoz Foundry

0.3.0

23 September 2026

Installer that replaces the deprecated Docker Compose and install script paths

AGPLv3

OpenTelemetry Collector (releases)

0.162.0

29 September 2026, 6:04 PM

Core and contrib distributions; tail sampling processor is beta

Apache 2.0

A short timeline helps explain why these three tools feel so different.

Date (IST)

Event

2015

Jaeger created at Uber (per the CNCF graduation announcement)

16 April 2016

Jaeger GitHub repository created

6 December 2017

Jaeger 1.0.0

31 October 2019

Jaeger graduates in the CNCF, the seventh project to do so

24 January 2020

Tempo repository created at Grafana Labs

23 October 2020

Tempo first release (0.2.0)

3 January 2021

SigNoz repository created

20 April 2021

Grafana Labs announces Tempo, Loki and Grafana move from Apache 2.0 to AGPLv3

8 June 2021

Tempo 1.0.0; on the same day SigNoz 0.3.1 makes ClickHouse its default storage

31 January 2023

Tempo 2.0.0

11 November 2024

Jaeger v2.0.0 (“Jaeger v2 is here”), built on the OpenTelemetry Collector

3 December 2025

Jaeger 1.76.0 and 2.13.0, the last paired v1 and v2 release

28 May 2026

Tempo 3.0.0 with the new ingest architecture

June to September 2026

Eight SigNoz CVEs published; the last two fixed in 0.143.0

29 September 2026

Tempo 3.1.0 and OpenTelemetry Collector 0.162.0

6 October 2026

Jaeger 2.22.0

First question: do you need a tracing backend at all?

Tracing earns its cost when one user action crosses several services and one service’s logs cannot explain a slow or failed request. A Kochi booking that touches search, pricing, seat hold, payment and notification qualifies; a monolith with one database usually does not.

Situation

What usually works

One or two services, one database

Structured logs with a request ID, plus RED metrics (rate, errors, duration)

Several services, but issues are mostly capacity

Metrics first; add exemplars or span metrics later

Many services, user complaints about one failed or slow action

A tracing backend with sampling designed around errors and latency

Strict cost limits

Tail sampling plus short retention, then decide the backend

If you need tracing, instrument with OpenTelemetry SDKs and send OTLP to an OpenTelemetry Collector that you control. That collector layer is the part you keep when you change backends. I covered collectors as log shippers in the log pipeline post; the same binary does sampling and fan-out for traces.

What a tracing backend actually stores and serves

A span is a timed operation with a trace ID, a parent span ID, a service name, a status and a bag of attributes. A trace is all spans that share a trace ID, often arriving from different services seconds apart. The backend must:

  • accept OTLP (and older Jaeger or Zipkin formats) at a steady rate,
  • make fetching one trace by ID cheap,
  • index or scan spans so that “errors in payment-service in the last hour” is possible,
  • delete old data on schedule and show a readable waterfall.

The big difference is in the third point. Jaeger relies on the indexes of a database you choose, Tempo scans columnar blocks in object storage, and SigNoz queries ClickHouse tables.

Jaeger

Jaeger v2 is a custom distribution of the OpenTelemetry Collector. The same binary takes a role through its configuration: collector, query, ingester or all-in-one. Storage is configured through a jaeger_storage extension, spans are written by a storage exporter, and the UI and APIs come from a jaeger_query extension. Because it is a collector, OTLP receivers, batch processors and the tail sampling processor live in the same binary.

Storage backends in the 2.22 documentation:

Backend

What the docs say

Good fit

Elasticsearch 7.x and 8.x

Index rollover with ILM recommended; a cleaner job or ILM handles retention

Teams that already run Elasticsearch

OpenSearch 1.x, 2.x and 3.x

Recommended over Cassandra for large-scale production

New large deployments

Cassandra 4.x and 5.x

Schema created on start-up since 2.1.0; trace TTL default 48h in the example

Teams with Cassandra skills; native TTL

ClickHouse 26.x

The docs page says experimental and behind the storage.clickhouse feature gate, schema may change; the 2.21.0 release notes say the gate was promoted to stable

Watch, test, do not standardise yet

Badger

Embedded, local disk, single node only

All-in-one for small or edge setups

Memory

Not for production

Demos and tests

Remote storage (gRPC)

Plug in an external storage service

Custom backends

Kafka

A buffer between collectors and ingesters

Burst protection

Note the ClickHouse wording. The release notes and the docs disagree on 13 October 2026, so I would treat Jaeger on ClickHouse as early until the docs page catches up. If you choose OpenSearch, the earlier Elasticsearch and OpenSearch post covers that choice.

Jaeger search is span-based: service, operation, attribute pairs, duration range and time window, returning traces that contain one matching span. That is fast on an indexed store, but it cannot express “a Bengaluru booking whose seat hold child span took over two seconds” unless both facts sit on the same span. Jaeger also offers API v3 over HTTP and gRPC, archive storage for traces you want to keep longer, service performance monitoring from span metrics in a Prometheus-compatible store, and a dependency graph that needs a separate Spark job on the large backends.

Grafana Tempo

Tempo started with one idea: do not index traces, store them cheaply in object storage, and find them by ID from logs or exemplars. TraceQL later added real search, and vParquet made that search practical by storing spans in columns.

In Tempo 3.x the write path changed. In microservices mode, distributors write to a Kafka-compatible log, live-stores serve recent data, block-builders turn the log into Parquet blocks in object storage, and a backend scheduler with workers handles compaction, retention and redaction. Query frontends and queriers search both recent and stored data. Monolithic mode runs all of this in one process without Kafka; the old “scalable single binary” target was removed in 3.0, and 3.0 cannot be downgraded in place.

Storage is S3, GCS, Azure Blob or a local filesystem. The docs say the local backend is for development and testing, and that you cannot mix local and object storage. If you run your own S3-compatible store, the MinIO and its alternatives post is the relevant reading. Retention is block_retention, 336 hours (14 days) by default, with per-tenant overrides.

TraceQL is the main reason to choose Tempo today. It selects spansets and joins them structurally:

  • { resource.service.name = "payment-service" && status = error }
  • { span.booking.route = "Kochi-Bengaluru" } >> { resource.service.name = "seat-service" && duration > 2s } (a descendant relationship)
  • { span.customer.tier = "gold" } && { status = error } (both spansets in the same trace)
  • { status = error } | count_over_time() by (resource.service.name) (TraceQL metrics, generally available since 3.0)

vParquet5 is the default block format in 3.1. Dedicated columns (up to 20 string and 5 integer columns per scope) make frequently searched attributes faster than generic attribute maps. The metrics-generator can produce span metrics and service graphs and remote-write them to Prometheus, Mimir or similar; the Prometheus long-term storage post covers where those metrics land.

SigNoz

SigNoz is a product, not only a trace store. Its architecture has three parts: the SigNoz OpenTelemetry Collector, which receives OTLP, Jaeger, Zipkin or Kafka data and writes to ClickHouse; ClickHouse itself (with ClickHouse Keeper or ZooKeeper); and the SigNoz binary, which bundles the React frontend, API server, an OpAMP server that pushes configuration to the collector, a rule engine and an alert manager. The metadata store (users, dashboards, alert rules) is SQLite by default or PostgreSQL.

Traces land in tables such as signoz_traces.signoz_index_v3, partitioned by day and ordered by time bucket, resource fingerprint, error flag, span name and timestamp, with bloom filter and n-gram indexes on attribute maps. Common attributes such as http.route, db.system and rpc.method are materialised as their own columns. You query through the query builder, PromQL for metrics or plain ClickHouse SQL. If ClickHouse is new to your team, the real-time OLAP post explains the engine’s behaviour.

Retention is a table TTL. The schema my drill created used 1,296,000 seconds (15 days) for traces, which matches the retention guide (15 days for logs and traces, 30 days for metrics). The older Linux install page still says 7 days, so check your own tables. Installation is now through Foundry; the Docker Compose path and the old install script are marked deprecated.

Architecture side by side

Aspect

Jaeger 2.22

Tempo 3.1

SigNoz 0.145

Core idea

Collector distribution plus pluggable span store

Columnar blocks in object storage, scanned in parallel

Observability product on ClickHouse

Signals

Traces (plus span metrics through Prometheus)

Traces (plus TraceQL metrics and generated metrics)

Traces, logs, metrics, alerts, dashboards

Ingest

OTLP gRPC and HTTP, Jaeger, Zipkin, Kafka

OTLP, Jaeger, Zipkin receivers

OTLP and others through its collector

Main storage

OpenSearch, Elasticsearch, Cassandra, ClickHouse (early), Badger

S3, GCS, Azure Blob (local for dev)

ClickHouse (replicated or not)

Extra dependencies

The database you choose; Kafka optional

Kafka-compatible log in microservices mode; none in monolithic

ClickHouse Keeper or ZooKeeper; SQLite or PostgreSQL

Stateless parts

Collector and query

Distributor, query frontend, querier

SigNoz binary (state in DB), collector

Search model

Span attributes, service, operation, duration

TraceQL across spansets and structure

Query builder, ClickHouse SQL

UI

Jaeger UI

Grafana (Explore, Traces Drilldown)

SigNoz UI

Storage model and cost

Trace storage cost is mostly bytes per day multiplied by retention days, and the backend changes the multiplier.

Factor

Jaeger on OpenSearch or Elasticsearch

Tempo on object storage

SigNoz on ClickHouse

Where bytes live

Search cluster disks, with replicas

Object storage, compressed Parquet

ClickHouse disks, compressed columns

What drives cost

Index size, replica count, shard count, hot nodes

Object storage per GB plus requests; compute for queries

Disk per node, replication, merges

Retention mechanism

Rollover plus index cleaner or ILM

block_retention per tenant, enforced by backend workers

Table TTL, changed in settings

Changing retention

Change the cleaner job or ILM policy

Change block_retention or the tenant override

Applies only to newly ingested data, per the docs

Long retention

Expensive unless you add warm tiers

Usually cheapest per GB, because object storage is

Plan ClickHouse disks and merges for it

Tempo’s sizing guide gives starting points for the 3.x distributed mode: one distributor per about 10 MB/s, one live-store per 6 to 10 MB/s with 4 to 20 GB memory, one querier per 1 to 2 MB/s, one block-builder per Kafka partition, about 10 MB/s per partition, and a single backend scheduler. Those are Grafana’s own planning numbers, not measurements of mine.

For the Kochi team, the honest estimate starts with the volume after sampling. Keeping errors, slow traces and a small baseline cuts bytes far more than any storage format.

Sampling: head, tail and adaptive

Method

Where it runs

What it can keep

Cost

Head sampling (TraceIdRatioBased in the SDK)

In each service, when the trace starts

A random fraction, decided before errors or latency are known

Cheapest; misses rare problems

Jaeger remote sampling

SDK fetches per-service or per-endpoint rates from Jaeger

Probabilistic or rate-limited strategies from a file

Central control; still head-based. If no file is set, Jaeger returns a 0.1 percent probabilistic default

Jaeger adaptive sampling

Jaeger calculates rates from traffic towards a target samples per second

Keeps low-traffic endpoints visible

Needs a sampling store; still head-based

Tail sampling processor

OpenTelemetry Collector (also inside the Jaeger binary)

Errors, latency over a threshold, attributes, plus a probabilistic share

Memory for in-flight traces; all spans of a trace must reach the same collector

The tail sampling processor documentation is clear on two operational points. It keeps spans in memory while it waits (decision_wait, 30 seconds by default, and num_traces, 50,000 by default), and every span of a trace must reach the same collector instance. In practice that means two collector layers: a front layer with the load-balancing exporter routing by trace ID, and a tail sampling layer behind it. The processor’s stability is beta for traces.

Tempo 3.1 adds an opt-in TraceQL metrics option to extrapolate counts from the sampling probability carried in W3C tracestate, and the tail sampler has an alpha feature gate to read and write those fields. Both are new; treat them as useful but young.

Query, search and the questions people actually ask

Question

Jaeger

Tempo

SigNoz

Show trace by ID

Yes, from UI or API v3

Yes, from Grafana or API

Yes, waterfall API and UI

Errors in one service in last hour

Service plus error=true attribute

TraceQL selector

Query builder or SQL

Spans slower than 2 seconds

Duration filter

duration > 2s

Duration filter or SQL

Parent with attribute X and child slower than Y

Only if both facts sit on one span

Structural operators such as >> and ~

SQL self-join on trace_id

Count errors per service over time

Service performance monitoring via span metrics

TraceQL metrics

Built-in dashboards and SQL

Search by trace from logs

Through a log tool that stores trace IDs

Grafana links between Loki and Tempo

Same product, logs to traces

The parent and child question matters. Real incidents often look like “the slow seat hold only happens on one route”. Tempo answers that natively, SigNoz with an SQL join, and Jaeger only if the attribute is copied onto the span you filter by.

Self-hosting operations

Area

Jaeger

Tempo

SigNoz

Smallest useful setup

One binary with Badger

One binary, monolithic, local disk (dev) or S3

ClickHouse, Keeper, SigNoz, SigNoz collector

Scale-out

Stateless collectors and query; scale the database

Microservices mode with Kafka-compatible log

Replicated and sharded ClickHouse; more collectors

High availability

Depends on the database cluster

Replication through Kafka; queriers scale out

ClickHouse replication through Keeper

Multi-tenancy

Header-based tenancy exists in code; no current docs page

First class: X-Scope-OrgID, per-tenant limits and retention

An organisation model exists; the pricing page lists multi-tenancy as coming soon

Auth for UI

None built in; put a reverse proxy with SSO in front

Grafana handles users; Tempo itself expects a gateway

Built-in users and roles; SSO and SAML in paid editions

Upgrade risk

Breaking changes appear in minor releases; read notes

3.0 is a one-way migration; 3.1 refuses to write vParquet3

Weekly releases; the schema migrator runs on upgrade

Tempo uses the same tenant header as Mimir and Loki, so a Grafana estate that already works that way gets tenancy almost for free.

Licences and the open-core line

Component

Licence at tag

What it means in practice

Jaeger 2.22.0

Apache 2.0

Permissive; no network copyleft

Tempo 3.1.0

AGPL-3.0-only by default; operations/ and pkg/tempopb are Apache 2.0 (LICENSING.md)

Using it internally is fine; modifying and offering it over a network to others triggers source sharing

SigNoz 0.145.0

MIT Expat, except ee/ and cmd/enterprise/ under the SigNoz Enterprise License

Enterprise code is free for development and testing; production use needs a subscription

SigNoz OTel Collector 0.144.13

AGPLv3

The ingestion path you run is AGPL even if the server is MIT

SigNoz Foundry 0.3.0

AGPLv3

Installer only

OpenTelemetry Collector contrib

Apache 2.0

Permissive

SigNoz publishes separate release archives for the community build and the default build. I used the community archive in the drill. Check which archive or image you deploy, and which features you depend on, before you assume the MIT line covers you.

Security and CVEs

Dates are when the record was published, converted to IST. Scores are those listed by the CNA or NVD.

Project

CVE

Published (IST)

Issue

Fixed in

Jaeger

CVE-2023-36656

17 July 2023

Stored XSS in the Jaeger UI key-values table (5.4)

Jaeger UI 1.31.0

Jaeger

CVE-2020-10750

20 June 2020

Sensitive data written to logs when Kafka storage is used

1.18.1

Related

CVE-2026-59892

8 July 2026

OpenTelemetry JS Jaeger propagator crashes Node.js on a malformed header (7.5)

@opentelemetry/propagator-jaeger 2.9.0

Tempo

CVE-2026-28377

27 March 2026

S3 SSE-C key shown in plain text on /status/config (7.5)

2.10.3

Tempo

CVE-2026-21728

24 April 2026

Queries with large limits cause large memory allocations (7.5); mitigation max_result_limit

2.8.4, 2.9.2, 2.10.2

Tempo

CVE-2026-27878

20 June 2026

Large exemplars hint in TraceQL can cause out-of-memory (6.5)

2.8.4, 2.9.2, 2.10.2 (per release notes)

Related

CVE-2025-2842

2 April 2025

Tempo Operator creates an over-broad cluster role binding when the Jaeger UI monitor tab is enabled (4.3)

Tempo Operator fix (see record)

SigNoz

CVE-2026-57955

29 June 2026

SQL injection in alert history via rule ID (CVSS 4.0: 8.3)

Affects up to 0.130.1

SigNoz

CVE-2026-57956

29 June 2026

Access to other organisations’ alert rules (6.1)

0.133.0

SigNoz

CVE-2026-63094

17 July 2026

Open redirect in SSO flow that can leak session tokens (7.6)

0.134.0

SigNoz

CVE-2026-92729

17 September 2026

Trace funnel endpoints without authorisation (8.8)

0.141.1

SigNoz

CVE-2026-93292

17 September 2026

SQL injection in trace funnel endpoints (8.4)

0.142.1

SigNoz

CVE-2026-93426

18 September 2026

SQL injection through field-key names in the v5 query API (8.4)

0.142.0

SigNoz

CVE-2026-97055

24 September 2026

Empty default JWT signing secret allows forged sessions (9.2)

0.143.0

SigNoz

CVE-2026-97056

24 September 2026

Sessions not revoked on password reset or user delete when the opaque tokenizer is used (7.6)

0.143.0

I found no CVE against the Jaeger server itself after 2023 in NVD keyword searches. Tempo 3.1.0 also fixed a cross-tenant redaction path (the tenant is now taken only from X-Scope-OrgID) and started signing images with cosign and SLSA provenance. SigNoz’s cluster of 2026 records is not a reason to avoid it, but it does mean you must keep up with its weekly releases and never expose an old version.

Practical controls for all three:

  1. Never expose the trace UI or query API directly to the internet. Put SSO in front of Jaeger and Tempo, and use SigNoz’s own users behind a VPN or gateway.
  2. Treat span attributes as sensitive data. Strip card numbers, phone numbers and tokens in the collector before they are stored.
  3. Set query limits (max_result_limit and related settings in Tempo, search depth in Jaeger) so one heavy query cannot take the backend down.
  4. For SigNoz, set SIGNOZ_TOKENIZER_JWT_SECRET to a long random value even on 0.143.0 and later, and keep its OpAMP and API ports off public interfaces.
  5. Lock down config and status endpoints such as Tempo’s /status/config; they may print secrets.
  6. Patch on a schedule and read release notes for security sections.

Prefer each when

Prefer

When

Jaeger

You want a permissive licence, a familiar trace UI, and you already run OpenSearch, Elasticsearch or Cassandra well. Trace volume is moderate or retention is short.

Tempo

Storage cost and long retention matter, you already run Grafana, and you want TraceQL for structural questions. You can run object storage and, at scale, a Kafka-compatible log.

SigNoz

You want one product for traces, logs and metrics, your team is comfortable with ClickHouse, and you accept a weekly upgrade rhythm and the edition split.

None yet

Instrumentation is incomplete or sampling is not designed. Fix that first; the backend will not save you.

Decision guide

If your main concern is

Lean towards

Because

Cheapest storage for 30+ days

Tempo

Object storage and compressed Parquet

“Find the trace where X happened under Y”

Tempo, or SigNoz with SQL

Structural TraceQL; SQL joins

One UI for all signals

SigNoz

Logs, metrics and traces in one product

Permissive licence everywhere

Jaeger

Apache 2.0 server and UI

Existing OpenSearch skills

Jaeger

Recommended production backend

Existing Grafana, Prometheus and Loki

Tempo

Same tenancy, same UI, links between signals

Small team, one VM

Jaeger all-in-one with Badger, or Tempo monolithic

One process each

Many internal teams as tenants

Tempo

Header-based tenancy with per-tenant limits

Search that must be complete within seconds

Jaeger or SigNoz

Indexed writes; see the drill for Tempo’s lag

My recommendation for the Kochi booking platform: put an OpenTelemetry Collector layer in front first, with load-balanced tail sampling that keeps all errors, all traces slower than two seconds and 10 percent of the rest. Then choose Tempo in microservices mode on their existing S3-compatible store, because they already run Grafana and Prometheus, they want 30 days of traces for refund disputes, and TraceQL answers the route-specific questions their support team asks. Keep Jaeger UI only if developers insist; Grafana can show the same waterfalls. Choose SigNoz instead only if the team also wants to retire its log stack and is ready to run ClickHouse with Keeper as a first-class database.

Checklist before you standardise

  1. Instrument with OpenTelemetry SDKs and send OTLP only to your own collectors.
  2. Write the sampling policy down: errors, latency threshold, baseline percentage.
  3. Deploy the load-balancing exporter in front of tail samplers, routed by trace ID.
  4. Agree on attribute names (route, tenant, customer tier) and put the searchable ones where your backend can filter them.
  5. Estimate bytes per day after sampling, then multiply by retention.
  6. Test the question your support team asks most, end to end, in the chosen backend.
  7. Measure how long a new trace takes to become searchable, not only to be stored.
  8. Put the UI and APIs behind SSO; scrub sensitive attributes in the collector.
  9. Practise an upgrade on a copy, including any schema migration.
  10. Set query limits and alerts on the backend’s own memory and ingestion lag.

Common mistakes

  1. Choosing the backend before designing sampling, then paying to store spans nobody reads.
  2. Running tail sampling on several collectors without routing by trace ID, so traces are split and decisions are wrong.
  3. Assuming “stored” means “searchable”. In Tempo, recent data and late-arriving spans behave differently.
  4. Leaving the trace UI open on an internal network without authentication.
  5. Putting customer data in span attributes and then retaining it for months.
  6. Treating Jaeger with Badger or Tempo with a local disk as production storage.
  7. Ignoring the licence of the exact component you run, such as an AGPL collector beside an MIT server.

A drill I ran on 13 October 2026

Environment: shared Linux VM, 8 vCPU, about 15 GiB RAM, no root, no Docker, loopback only. Between about 2:30 and 2:52 AM IST I ran:

  • Jaeger 2.22.0 all-in-one with Badger storage, and Tempo 3.1.0 in monolithic mode with the local backend, both from official release archives with checksums verified.
  • An OpenTelemetry Collector contrib 0.162.0 in front, fanning the same OTLP batches out to both backends.
  • A second contrib collector with tail sampling (errors, latency over 2 seconds, 10 percent probabilistic, decision_wait 5 seconds).
  • SigNoz 0.145.0 community build without Docker: ClickHouse 25.12.5 (the version Foundry pins) as a static binary, clickhouse keeper, SQLite metadata and SigNoz OTel Collector 0.144.13.

Workload: a seeded Python generator using the OpenTelemetry SDK sent 2,000 booking traces of 6 spans each (booking-api, search, pricing, seat hold, payment, notify), with about 3 percent payment failures and about 5 percent slow seat holds. The generator wrote a ground-truth file so I could check every answer.

Ingest (seed 31, 2,000 traces, 12,000 spans)
  fan-out collector: 12,000 spans sent to each backend
  Jaeger stored 12,000; Tempo distributor received 12,000
  SigNoz: 12,000 spans, 2,000 traces in signoz_index_v3

Trace by ID, 50 samples
  Jaeger 50/50 complete, median 1.1 ms
  Tempo  50/50 complete, median 2.7 to 5.7 ms
  SigNoz waterfall API, 6 spans, about 30 ms

Four searches (expected / Jaeger / Tempo / SigNoz SQL)
  payment errors                         60 / 60 / 60 / 60
  seat holds > 2 s                       96 / 96 / 96 / 96
  Bengaluru route with slow seat hold    12 / 12 / 12 / 12
  gold tier with failed payment          17 / 17 / 17 / 17

Tempo search over time (payment errors)
  ~8 s after send:   7 of 60
  ~15 s after send: 41 of 60
  ~35 s after send: 60 of 60 (first block completed)
  Jaeger and SigNoz: complete on the first query

Late spans (1,000 traces stamped 5 to 10 minutes in the past, 22 errors)
  by ID:   Jaeger 100/100, Tempo 100/100
  search:  Jaeger 22/22,  Tempo 0/22, still 0 after 7 minutes

Sampling (seed 33, 2,000 traces)
  tail sampling kept 369 complete traces
    errors 66/66, slow 116/116, others 188 of 1,819
  simulated SDK head sampling at 10 percent kept 183
    errors 7/66, slow 5/116

Footprint (peak RSS, disk after test)
  Jaeger 243 MiB, Badger 28 MB allocated (2.1 GiB apparent, sparse prealloc)
  Tempo 297 MiB, about 6 MB of blocks and WAL
  SigNoz stack about 1.4 GiB: ClickHouse 869 MiB, Keeper 195, collector 315, server 114
  signoz_index_v3 830.6 KiB for 12,000 spans
  first SigNoz start: schema migrations 34.2 s
  restart: Jaeger and Tempo kept all data; Tempo took about 30 s to stop

What this shows:

  • All three returned correct answers once data was searchable, and trace by ID was fast everywhere.
  • Jaeger answered the Bengaluru question only because my generator put the route and the overall duration on the root span. Tempo expressed the real parent and child question with >>; SigNoz needed a SQL self-join. Attribute placement decides what Jaeger can find.
  • Tempo search is eventually consistent. New traces became fully searchable after about 35 seconds on this setup, while lookups by ID worked at once.
  • Spans whose timestamps were 5 to 10 minutes old were stored and retrievable by ID in Tempo but never appeared in search. Tempo’s docs note that ingestion_time_range_slack (2 minutes by default) can make traces outside the slack unfindable. Clock skew, retries from mobile apps and replay from queues can all produce such spans, so measure it before you rely on search.
  • Tail sampling kept every error and every slow trace for about 18 percent of the volume. Head sampling at 10 percent kept about one error in ten. This was the single most useful result of the drill.
  • SigNoz ran without Docker, but it is a real database deployment: ClickHouse, Keeper and more than a hundred tables. Its collector got a no-op configuration through OpAMP until the first admin user existed, and some listeners bound to all interfaces by default.

Limits of the drill: one VM and loopback; one run of each step; Badger and Tempo’s local backend instead of OpenSearch and object storage; no Kafka, so no Tempo microservices mode; no multi-tenancy, UI or Jaeger adaptive sampling tests; small data where fixed overheads dominate disk numbers; timings include Python client overhead and are a smoke test, not a benchmark. Scripts, configs and results are under /workspace/revisit/drill31/.

Unlearn / re-learn

Unlearn

  • Tracing means storing every span.
  • Head sampling at 1 percent is good enough for incident work.
  • If a trace is stored, search will find it.
  • The backend’s query language is a detail.
  • An open-source tracing tool has one licence.

Re-learn

  • Design sampling around errors and latency, then size storage for what you keep.
  • The collector layer is the stable part of the stack; backends can change behind it.
  • Ask your hardest real question in each tool before choosing, including parent and child conditions.
  • Practise late-arriving spans, restarts and upgrades on a small copy before the festival rush.

Closing

Self-hosted tracing in 2026 has three healthy, different answers. Learn how each one stores spans: in a search index for Jaeger, in Parquet blocks on object storage for Tempo, in ClickHouse tables for SigNoz. Unlearn the habit of sampling 1 percent at the start of a request and hoping the bad trace is in it. Re-learn the licence and edition lines, because Tempo and SigNoz’s collector are AGPLv3 and SigNoz keeps some features in a commercial edition. Practise with a small drill like the one above: one seeded workload, a known answer, and a check of how long search takes to catch up. Apply one backend behind your own OpenTelemetry Collectors, with tail sampling in place, and keep the trace UI behind SSO.

Sources

Jaeger

Grafana Tempo

SigNoz

OpenTelemetry Collector

Security (CVE Program records)

comments powered by Disqus

Releted Posts

Lakehouse table formats in 2026: revisit Apache Iceberg, Delta Lake and Apache Hudi before you pick another format

Picture a payments and lending analytics platform in Hyderabad (an invented example, not a real client). Card authorisations and EMI schedules still land in PostgreSQL 16.

Read more

Real-time OLAP in 2026: revisit ClickHouse, Apache Doris and StarRocks before you build another dashboard warehouse

Picture an ecommerce and last-mile logistics platform in Bengaluru (an invented example, not a real client). Order capture and inventory still run on PostgreSQL 16.

Read more

Durable execution in 2026: revisit Temporal, Restate and DBOS before you hand-roll retries, sagas and cron jobs

Picture an online pharmacy and diagnostics booking company in Ahmedabad (an invented example, not a real client). An order goes through five steps: hold the medicine stock, take the UPI payment, book a courier slot, send the customer an SMS, and, if the courier cannot pick up within four hours, release the stock and refund.

Read more