T-Digest for Redis on Java

Published on
July 6, 2026

Anyone running a latency-sensitive service eventually learns the same hard lesson: the average is a liar. A mean response time of 40ms can hide the fact that one request in a hundred takes two full seconds, and it is almost always tail latency — the p95, the p99, the p99.9 — that decides whether users stay or leave. To manage what you cannot see, you have to measure the shape of the distribution, not just its center.

The naive way to compute exact percentiles is to keep every observation, sort it, and index into the sorted array. Over a high-volume stream — millions of requests an hour across a fleet of services — that is hopelessly expensive in both memory and CPU. Fixed-bucket histograms are cheaper, but they force you to guess the right bucket boundaries up front, and they lose precision exactly where you need it most, out in the tail.

The T-Digest solves this. It is a probabilistic data structure that estimates quantiles, ranks, and the cumulative distribution of an unbounded stream of numbers using a small, fixed amount of memory — while staying most accurate at the tails, where percentile monitoring matters most. This article shows how to use it from Java on Redis through Redisson's RTDigest.

What Is a T-Digest?

A T-Digest is a streaming sketch for numeric data. Instead of storing every value, it groups observations into a bounded set of clusters (centroids) and keeps only a compact summary of each. From that summary it can answer questions like what value sits at the 99th percentile? or what fraction of requests came in under 200ms? without ever holding the full dataset in memory.

Three properties make it especially well suited to operational monitoring:

  • Tail accuracy. Unlike a uniform histogram, a T-Digest deliberately keeps finer resolution near the extremes of the distribution. Estimates at p1, p99, and p99.9 are far more accurate than estimates near the median — which is the opposite of what most approximate methods give you, and exactly what SLA tracking needs.
  • Fixed, sub-linear memory. The sketch size is governed by a tunable compression factor, not by how many values you feed it. A digest that has seen a billion observations occupies the same modest footprint as one that has seen a thousand.
  • Mergeability. Two digests can be combined into one without re-reading the original data. That single property is what makes the T-Digest a natural fit for distributed systems, where each node maintains its own sketch and a coordinator folds them into one global view.

On Redis, the T-Digest is provided by the TDIGEST.* commands of the Redis Bloom module. Redisson exposes them to Java through the RTDigest object, with synchronous, asynchronous, reactive, and RxJava3 APIs, so you never have to hand-write a single low-level command or worry about serialization.

Percentiles Over Averages

A mean smears slow requests into fast ones, so it can read as perfectly healthy while a meaningful slice of users wait far too long. Percentiles report the experience honestly — the median (p50) for the typical request, and p95, p99, and beyond for the tail where service-level objectives are won or lost. For the full picture of latency percentiles, tail latency, and how they trade off against throughput, see Latency vs Throughput.

The T-Digest gives you every one of those numbers — plus the inverse question, what share of traffic met my 200ms target? — from one continuously updated, memory-bounded sketch.

Getting Started With RTDigest

You obtain a digest by name from your RedissonClient and create it once before use. An optional compression factor trades memory for accuracy: a higher value yields more accurate estimates, particularly at the tails, at the cost of a larger sketch.

RTDigest tdigest = redisson.getTDigest("latencies");

// create with default compression
tdigest.create();

// or create with a higher compression for better tail accuracy
tdigest.create(200);

// empty and re-initialize the sketch when you want to start over
tdigest.reset();

That is the whole setup. The sketch now lives on Redis, shared across every application instance that connects to it.

Adding Observations

Feed values into the digest one at a time or several at once. The bulk form takes a varargs of doubles, which keeps ingestion cheap under load.

RTDigest tdigest = redisson.getTDigest("latencies");

tdigest.add(12.5);
tdigest.add(8.0, 15.3, 22.1, 9.7);

Because the structure is thread-safe and lives server-side, many producers across many JVMs can write into the same digest concurrently — there is no client-side coordination to manage.

Reading Percentiles and the Cumulative Distribution

These two methods are the heart of the structure, and they are inverses of one another.

quantile() takes one or more fractions and returns, for each, the value below which that fraction of observations fall — the median, the 95th percentile, the 99th, and so on. cumulativeProbability() goes the other way: given one or more values, it returns the fraction of observations less than or equal to each. Both accept multiple inputs in a single round trip and return one result per input.

RTDigest tdigest = redisson.getTDigest("latencies");

// median, 95th and 99th percentile latency
List<Double> percentiles = tdigest.quantile(0.5, 0.95, 0.99);

// fraction of requests served at or under 100ms and 250ms
List<Double> fractions = tdigest.cumulativeProbability(100.0, 250.0);

The first call drives a percentile dashboard. The second answers an SLA question directly — if fractions.get(0) is 0.985, then 98.5% of requests came in under 100ms.

Ranks

Ranks let you locate a value within the distribution by count rather than by fraction. rank() returns, for each value, how many observations fall below it; revRank() returns how many fall above it (both return -1 for a value outside the observed range). byRank() and byRevRank() are the inverses, returning the value at a given position counting from the smallest or the largest observation.

RTDigest tdigest = redisson.getTDigest("latencies");

// how many observations fall below / above given values
List<Long> ranks = tdigest.rank(100.0, 250.0);
List<Long> reverseRanks = tdigest.revRank(100.0, 250.0);

// the value at given ranks, from the smallest / largest observation
List<Double> values = tdigest.byRank(0, 99);
List<Double> valuesFromTop = tdigest.byRevRank(0, 9);

Summary Statistics

For a quick characterization of the stream, getMin() and getMax() return the smallest and largest observations (or NaN when the sketch is empty). trimmedMean() returns the mean of the observations between two cut quantiles, ignoring the outliers at both tails — a far more robust central estimate than a plain average when the data is noisy.

RTDigest tdigest = redisson.getTDigest("latencies");

double min = tdigest.getMin();
double max = tdigest.getMax();

// mean ignoring the bottom 10% and top 10% of observations
double trimmed = tdigest.trimmedMean(0.1, 0.9);

Merging Sketches Across Nodes

This is where the T-Digest earns its place in a distributed architecture. Each service instance can maintain its own per-node digest, and a coordinator folds them into a single cluster-wide view — no need to ship raw observations anywhere. mergeWith(String...) merges the named source sketches on top of the destination's current contents, while the TDigestMergeArgs form lets you set the resulting compression and optionally override the destination, discarding its existing data before the merge.

RTDigest global = redisson.getTDigest("latencies:global");
global.create();

// merge per-node sketches into the destination
global.mergeWith("latencies:node1", "latencies:node2");

// set the resulting compression and discard the destination's current contents
global.mergeWith(TDigestMergeArgs.keys("latencies:node1", "latencies:node2")
                    .compression(200)
                    .override());

Merging folds the source sketches together without re-reading any of the original observations, so you can roll per-minute digests into per-hour and per-day digests, or per-shard digests into one global digest. A merged percentile is still an estimate rather than an exact value — as with any T-Digest result — but the error stays small and bounded, and it remains strongest at the tails, which is exactly what fleet-wide latency monitoring depends on.

Inspecting the Sketch

getInfo() returns a TDigestInfo describing the digest: its compression and capacity, the number of merged and unmerged nodes, the total observation count, the number of compressions performed, and the estimated memory usage. It is handy for capacity planning and for confirming that compression is set where you expect.

RTDigest tdigest = redisson.getTDigest("latencies");

TDigestInfo info = tdigest.getInfo();
long compression = info.getCompression();
long observations = info.getObservations();
long memoryUsage = info.getMemoryUsage();

Putting It Together: Latency and SLA Monitoring

The example below wires the pieces into a small, realistic latency monitor. Each completed request is recorded as it finishes; at any moment the service can read its tail percentiles for a dashboard and check what fraction of traffic met the SLA target — all from a single fixed-size structure on Redis.

import org.redisson.api.RTDigest;
import org.redisson.api.RedissonClient;
import org.redisson.api.TDigestInfo;
import java.util.List;

public class LatencyMonitorExample {

    public static void main(String[] args) {
        RedissonClient redisson = ... // Initialize your RedissonClient

        // 1. Create the sketch (higher compression = sharper tail estimates)
        RTDigest latencies = redisson.getTDigest("latencies:checkout");
        latencies.create(200);

        // 2. Record each request's latency in milliseconds as it completes
        latencies.add(42.0);
        latencies.add(55.3, 38.1, 1200.0, 47.9, 61.2);

        // 3. Read p50 / p95 / p99 for the dashboard
        List<Double> percentiles = latencies.quantile(0.5, 0.95, 0.99);
        System.out.println("p50/p95/p99 latency (ms): " + percentiles);

        // 4. SLA check: what fraction of requests beat the 200ms target?
        double withinSla = latencies.cumulativeProbability(200.0).get(0);
        System.out.printf("Requests under 200ms: %.2f%%%n", withinSla * 100);

        // 5. A robust baseline that ignores the heaviest 5% at each tail
        double baseline = latencies.trimmedMean(0.05, 0.95);
        System.out.println("Trimmed-mean latency (ms): " + baseline);

        // 6. Inspect the sketch
        TDigestInfo info = latencies.getInfo();
        System.out.println("Observations: " + info.getObservations());
        System.out.println("Memory usage (bytes): " + info.getMemoryUsage());
    }
}

When to Reach for a T-Digest

A T-Digest answers what does the value distribution look like? over an unbounded stream — any quantile, rank, or trimmed mean — in a small, fixed footprint, and it stays most accurate at the tails. That makes it a strong fit for:

  • Latency and SLA monitoring. Continuously updated p50/p95/p99 latency, plus direct "fraction under target" answers via the cumulative distribution, with per-instance sketches merged into one fleet-wide view.
  • Adaptive thresholds and outlier detection. Use a live quantile (say, the current 99th percentile) as a dynamic cutoff instead of a hard-coded number, and place each new value within the historical distribution to judge how extreme it is.
  • Distribution-based analytics at scale. Order-amount distributions, sensor readings, model scores, or any high-volume numeric stream where retaining every observation to compute exact percentiles would be far too expensive.

Bring Distributed Percentiles to Your Java Apps With Redisson

Computing accurate, tail-aware percentiles over a high-volume stream used to mean either drowning in memory or settling for the misleading average. The T-Digest gives you the full shape of the distribution in a tiny, mergeable, fixed-size sketch — and Redisson lets you use it from Java through a clean, object-oriented RTDigest interface, with synchronous, asynchronous, reactive, and RxJava3 variants.

To bring enterprise-grade tooling, superior performance, and a full catalog of highly optimized distributed data structures to your Java applications, learn more about Redisson PRO today, or explore the T-Digest reference documentation to go deeper.