background

What a lognormal distribution is

A random variable X is lognormal if its natural logarithm, ln(X), follows a normal distribution. It is defined by two parameters: mu (”), the mean of the underlying normal, and sigma (s), the standard deviation.

Key properties that make it useful for latency modelling: it is always positive (latency can't be negative), it is right-skewed (most values cluster near the median, with a long tail of occasional outliers), and it is fully determined by just two percentile observations.

// Lognormal parameters from p50 and p99
const mu    = Math.log(p50);
const sigma = (Math.log(p99) - mu) / 2.326;
// 2.326 is the z-score of the 99th percentile of the standard normal
theory

Why latency is lognormal

Network latency is the product of many independent multiplicative factors: processing time in each queue, lock contention, memory access patterns, GC pauses, NIC driver scheduling, kernel context switches. By the central limit theorem applied to products (rather than sums), the product of many independent positive random variables converges to a lognormal distribution.

This is why the lognormal fits empirical latency data so well: fast path responses (warm cache, hot connection pool, no contention) cluster tightly near the median, while the tail stretches out to cover GC pauses, cold starts, and noisy-neighbour events — exactly the shape you see in APM dashboards.

Why not normal?

A normal distribution allows negative values and is symmetric — neither fits latency. A normal with mean 10ms would assign significant probability to -5ms. Lognormal is always positive and right-skewed, matching reality.

sampling

Box-Muller transform

To sample from a lognormal, you need a standard normal sample first. JavaScript has no built-in for this — Math.random() only gives a uniform [0, 1] value. The Box-Muller transform converts two independent uniform samples into two independent standard normal samples:

function lognormalSample(mu, sigma) {
  const u1 = Math.random();
  const u2 = Math.random();
  // Box-Muller: produces Z ~ N(0,1)
  const z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
  // Map to lognormal
  return Math.exp(mu + sigma * z);
}

slowdep caps the result at p99 * 3 to prevent runaway outliers from making tests hang. In practice the cap is rarely reached — it would require a z-score far into the tail.

fitting

Fitting mu and sigma from p50 and p99

You have two observations (p50 and p99) and two unknowns (mu and sigma). Because ln(X) ~ N(mu, sigmaČ), you can write:

ln(p50) = mu + sigma * z_0.50   // z_0.50 = 0 (median of standard normal)
ln(p99) = mu + sigma * z_0.99   // z_0.99 ˜ 2.326

// Solving:
mu    = ln(p50)
sigma = (ln(p99) - ln(p50)) / 2.326

This means slowdep only needs two numbers from you — the typical latency (p50) and the worst-case you care about (p99) — and it derives the full distribution automatically.

distributions compared

Uniform vs normal vs lognormal

distributionalways positiverealistic tailmatches APM dataparametrised by percentiles
Uniform (setTimeout)yesno — no tail at allnono
Normal (Gaussian)no — allows negativessymmetricnosort of
Lognormal (slowdep)yesyes — right-skewedyesyes — p50 + p99
further reading

Related topics