The standard observability stack is three products. A metrics system, a log store, and a tracing backend, each with its own query language, retention policy, access model, and invoice. This is not a design. It is an accident of the order in which the categories were invented.
The cost of the accident is paid during incidents, in the seconds and minutes an engineer spends carrying context across tool boundaries by hand — copying a trace ID out of one interface and into another, adjusting a time range for the third time, checking whether two dashboards mean the same thing by the word “error”.
Three views of one event
A request that fails produces a span, increments a counter, and writes a line. These are not three occurrences. They are three projections of one occurrence, and the boundaries between them are an artifact of storage, not of what happened in the system.
Once you accept that, the interesting operations become obvious and mostly consist of moving between projections without leaving the page. From an elevated error rate, show the failing spans. From a span, show the log lines written inside it. From a log line, show the trace that produced it, and the deploy that was live when it was written.
If answering a question requires a second tab, the tool has handed part of its job back to you.
What it cost
A single store is harder than three. Metrics want columnar layout and aggressive downsampling. Logs want full-text search over high-cardinality text. Traces want efficient retrieval of a tree by ID. These access patterns are genuinely in tension, and pretending otherwise produces a system that is mediocre at all three.
- We ingest OpenTelemetry natively. There is no proprietary agent, and no translation layer that can drift from the spec.
- One write path assigns a shared identity to every signal at the edge, before storage decisions are made.
- Three physical layouts sit behind that write path, each tuned to its access pattern.
- One query planner spans all three, so a single query can cross projections without a client-side join.
The edge write path took a year longer than we estimated, which is the honest headline of this post. The correlation identity has to be assigned before any storage decision, it has to survive sampling, and it has to be cheap enough to add at 3 ms without becoming the bottleneck it is meant to help diagnose.
The part that is not technical
Unifying the store also unifies the bill. Per-host, per-metric, per-seat pricing does something specific to engineering behaviour: it makes instrumenting a service a budget conversation. We have watched teams decline to add a span because of what it would cost, and then spend a night without it.
A fixed annual cost removes that conversation. It is a commercial decision with an engineering consequence, and of everything described here it is probably the change that alters how teams work the most.