skip to content

What is a DistributionSummary and when would you use it instead of a Timer?

level: seniorimportance: should knowfreq 45%

answer

  1. Timer = DistributionSummary specialized to time
  2. non-time magnitudes: bytes, rows, amounts
  3. record(double); count/total/max
  4. scale() and baseUnit() builder options
  5. negatives ignored; non-negative only

basics

~10 s

A DistributionSummary tracks the distribution of non-time measurements, like request payload sizes or items per batch. It records count, total, and max like a Timer but for arbitrary magnitudes instead of durations, via summary.record(value).

solid answer

~40 s

A Timer is really a specialized DistributionSummary whose unit is time. When the thing you're measuring isn't a duration — payload byte sizes, number of rows in a batch, cart totals, queue depth per poll — you use a DistributionSummary. You build it with DistributionSummary.builder("http.request.size").baseUnit("bytes").register(registry) and call summary.record(amount) for each observation; it accumulates count, total amount, and max, and can produce percentiles/histograms/SLO buckets exactly like a Timer. A useful feature is scale(double) which multiplies each recorded value before aggregation (e.g. normalize units). Because it shares the distribution machinery with Timer, everything you know about percentile approximation, client-vs-server histograms, and cardinality applies identically. Don't record negative values — records below zero are ignored/clamped. Use Timer for latency, DistributionSummary for every other magnitude you want distributed.

code

java · 22 lines
java
import io.micrometer.core.instrument.DistributionSummary;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.stereotype.Service;

@Service
class ImportService {
    private final DistributionSummary batchSize;

    ImportService(MeterRegistry registry) {
        this.batchSize = DistributionSummary.builder("import.batch.size")
            .baseUnit("rows")
            .minimumExpectedValue(1.0)
            .maximumExpectedValue(100_000.0)
            .publishPercentileHistogram()   // aggregatable buckets
            .register(registry);
    }

    void importBatch(java.util.List<Row> rows) {
        batchSize.record(rows.size());   // distribution of rows-per-batch
        // ... persist rows ...
    }
}

go deeper

for a junior

Know it records non-time numbers like sizes with record(value).

for a middle

Explain count/total/max and that it's the general form of Timer.

for a senior

Apply baseUnit/scale/min-max bounds, percentiles/SLOs, and negative-value semantics; choose Timer vs Summary vs Counter correctly.

for a principal

Reason about histogram memory/bucket bounds, cross-instance aggregation, and modeling signed/bounded magnitudes.

## The relationship to Timer Micrometer models both `Timer` and `DistributionSummary` on the same underlying **distribution** machinery. In fact a `Timer` *is* conceptually a `DistributionSummary` specialized to the **time** domain (its base unit is a time unit and it has convenience methods that take `Duration`/`TimeUnit`). A **`DistributionSummary`** is the general form for tracking the statistical distribution of **any non-time magnitude**. ## When to reach for it Use a `DistributionSummary` when the measured quantity is a number that isn't a duration: - HTTP request/response **payload sizes** (bytes). - **Batch sizes** — rows per import, messages per Kafka poll. - **Business magnitudes** — order totals, cart item counts. - **Queue depth** sampled at each dequeue. ## API ```java DistributionSummary summary = DistributionSummary.builder("orders.batch.size") .description("rows per import batch") .baseUnit("rows") .register(registry); summary.record(batch.size()); // one observation ``` Core statistics mirror Timer: **count**, **total amount** (sum of recorded values → mean = total/count), and **max**. ### Useful builder options - **`baseUnit(String)`** — documents the unit (e.g. `"bytes"`), surfaced to backends. - **`scale(double)`** — multiplies every recorded value before aggregation, handy for unit normalization. - **Distribution config** — `publishPercentiles(...)`, `publishPercentileHistogram()`, `serviceLevelObjectives(...)`, `minimumExpectedValue`/`maximumExpectedValue`. These behave exactly as on Timer (see the percentiles/SLO question). For a summary the SLO buckets are magnitudes (e.g. bytes) rather than durations. ## Edge cases & gotchas - **Negative values**: `record()` of a negative amount is **not** counted (Micrometer ignores/clamps sub-zero records) — a DistributionSummary assumes non-negative magnitudes. If you can have negatives, model differently. - **Not for time** — if it's a duration, use a `Timer` so you get correct time-unit handling and Spring's conventions; don't shoehorn milliseconds into a DistributionSummary. - **Same cardinality rules** — tags create separate series; keep them bounded. - **Percentile caveats** — client-side percentiles (`publishPercentiles`) are **not aggregatable across instances**; use `publishPercentileHistogram()`/SLOs when you need to aggregate server-side (identical to Timer semantics). - **`minimumExpectedValue`/`maximumExpectedValue`** bound the histogram range for accuracy — set them to the realistic value range (e.g. 1 byte to 1 MB) to control bucket count and memory. ## Timer vs DistributionSummary | | Timer | DistributionSummary | |---|---|---| | Measures | durations | arbitrary non-negative magnitudes | | Record via | record(Duration/Runnable), Sample | record(double) | | Base unit | time (s/ms) | whatever you declare (bytes, rows…) | | Distribution features | percentiles/histogram/SLO | same | ## When to use Use `DistributionSummary` whenever you want the distribution (not just a running sum) of a non-time value — sizes, counts, amounts — and you care about percentiles or SLO buckets on those magnitudes. For plain monotonic totals a `Counter` suffices; for durations use a `Timer`.

  • You already have a Counter of total bytes transferred. Why might you add a DistributionSummary?
    A Counter gives only a running sum/rate; it can't tell you the distribution. A DistributionSummary of per-request bytes exposes max and percentiles, so you can see typical vs tail payload sizes, not just the aggregate throughput.
  • What happens if you record a negative value into a DistributionSummary?
    It's ignored — Micrometer treats DistributionSummary values as non-negative magnitudes, so negatives don't update count/total. Model signed data differently.

saying these in an interview costs you the question

  • Using a DistributionSummary to time durations instead of a Timer
  • Assuming negative records are aggregated
  • Thinking it's fundamentally different machinery from Timer (it's the same distribution engine)
  • Believing a Counter can replace it for distribution/percentiles

context