Latency Histograms Need a Bucket Contract Before You Merge Them
Two applications both report a histogram called checkout_latency. One uses milliseconds, the other seconds. Or they share a unit but use different bucket boundaries. Adding the arrays by position produces a plausible chart with an undefined meaning.
Before merging latency histograms, match the measurement, unit, population, time interval, aggregation temporality, and bucket layout. A bucket count is only meaningful together with the boundary and counting convention that produced it.
Know whether a bucket is cumulative
Prometheus metric types describes classic histogram buckets as cumulative counts with inclusive upper bounds. An observation of 150 ms contributes to both the 200 ms and 300 ms buckets. Summing all those buckets would count some observations repeatedly. The positive-infinity bucket represents the observation count.
This article uses that classic, cumulative convention. Do not copy the array interpretation into every telemetry format. The OpenTelemetry metrics data model carries explicit histogram populations, boundaries, and delta or cumulative temporality; its representation needs the appropriate translation before comparison with exported Prometheus series.
Reproduce one compatible merge
The following independent Node.js program uses synthetic checkout durations. Save it as histogram.cjs and run it with Node.js. It is not an OpenTelemetry exporter, a benchmark, or a production ingestion test.
const assert = require('node:assert/strict');
const bounds = [100, 200, 300, Infinity];
function cumulative(values, limits) {
return limits.map(limit => values.filter(value => value <= limit).length);
}
const a = cumulative([50, 150, 250], bounds);
const b = cumulative([50, 250, 500], bounds);
const merged = a.map((count, index) => count + b[index]);
assert.deepEqual(a, [1, 2, 3, 3]);
assert.deepEqual(b, [1, 1, 2, 3]);
assert.deepEqual(merged, [2, 3, 5, 6]);
assert.equal(merged[3], 6);
assert.equal(merged[1] / merged[3], 0.5);
assert.equal(merged.reduce((sum, count) => sum + count, 0), 16);
assert.deepEqual(cumulative([200], bounds), [0, 1, 1, 1]);
assert.notDeepEqual(bounds, [100, 250, 300, Infinity]);
console.log('PASS: 8 synthetic histogram checks');
Eight checks passed locally. Exactly three of the six observations are at or below 200 ms. Adding all cumulative bucket counts gives 16, which is not an event count. The boundary test also shows that an observation exactly at 200 belongs in the inclusive 200 bucket.
These observations represent recorded durations, not unique users or orders. If retries emit another duration, the population changes unless the measurement contract excludes them. The program deliberately does not implement retry policy or deduplication. Node's assertion API supplies the reproducible checks.
A changed boundary is a changed question
Suppose the second application instead exports boundaries 100, 250, 300, and infinity. Its second entry counts durations at or below 250, while the first application's second entry counts durations at or below 200. Those positions cannot be added as though they answer the same threshold question.
With only the aggregated buckets, the exact number in the interval above 100 and at or below 200 may be unavailable for the second source. Keep common boundaries where a valid coarsening is possible, split the report by schema version, or retain finer data for later recomputation. Do not invent an exact split inside a bucket.
The Prometheus histogram guide discusses aggregation and quantile estimation, including the differences between classic and native histograms. Native histograms have different reconciliation mechanisms; this simple positional-array example is specifically about an explicit classic bucket layout.
Put units and population in the review
The OpenTelemetry metrics API makes unit part of instrument identification. A team still needs to specify the actual measurement: browser interaction to response rendering, server request processing, or successful payment confirmation. Sharing a number and a unit does not make different start and end boundaries comparable.
For each rollout, review five pieces of evidence:
- The same start and end meaning, with missing durations reported separately.
- Explicit units and a tested conversion at any format boundary.
- The same inclusion rules for failed operations, retries, and synthetic traffic.
- Compatible bucket conventions and boundaries, plus the schema version.
- Matching time intervals and temporality, including restart behavior for cumulative streams.
Keep percentile claims narrower than the source data. A histogram can support an estimated quantile; it does not recover the exact underlying sample values. Averaging independently computed percentiles is not a general way to obtain the percentile of the combined population.
Apply the check before building a dashboard
For an event pipeline such as SensorFlow, define the latency property and its measurement contract before building a SQL chart. The integration documentation explains the actual SDK-to-storage path; this article does not claim that SensorFlow exports native histograms or performs automatic bucket reconciliation.
Disclosure: published by the SensorFlow project maintainers with AI assistance. All numerical observations above are synthetic and the example was executed locally. SensorFlow uses the official Sensors Data SDK ecosystem and is independent of Sensors Data. Open-source deployment and the synthetic Demo do not require a license; accepting real SDK payloads requires a commercial license. It is not a complete feature-equivalent replacement.
评论
发表评论