<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kyrylo Latysh</title>
    <description>The latest articles on DEV Community by Kyrylo Latysh (@kirilllatish).</description>
    <link>https://dev.to/kirilllatish</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4011134%2Fb72c1cca-a7de-4ecd-b5ad-3738fe048ebc.jpg</url>
      <title>DEV Community: Kyrylo Latysh</title>
      <link>https://dev.to/kirilllatish</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kirilllatish"/>
    <language>en</language>
    <item>
      <title>Average latency is lying to you: Little's Law in production</title>
      <dc:creator>Kyrylo Latysh</dc:creator>
      <pubDate>Thu, 02 Jul 2026 09:32:15 +0000</pubDate>
      <link>https://dev.to/kirilllatish/average-latency-is-lying-to-you-littles-law-in-production-4jh1</link>
      <guid>https://dev.to/kirilllatish/average-latency-is-lying-to-you-littles-law-in-production-4jh1</guid>
      <description>&lt;p&gt;Your p50 latency is 40 ms. The dashboard is green. And yet users are complaining that the app "feels slow" during peak hours. If you've been on call long enough, you know this scene - and you know the average is not telling you the truth.&lt;/p&gt;

&lt;p&gt;Here's the mental model that fixes it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Little's Law: the one equation you actually need
&lt;/h2&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;L&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;λ&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;×&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;W&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;L&lt;/strong&gt; - average number of requests &lt;em&gt;concurrently in the system&lt;/em&gt; (in flight)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;λ&lt;/strong&gt; - arrival rate (requests per second)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;W&lt;/strong&gt; - average time a request spends in the system (latency)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's deceptively simple and it holds for any stable system, regardless of internal implementation - no assumptions about distribution, scheduling, or arrival pattern. That generality is exactly what makes it useful for capacity reasoning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why you should care
&lt;/h2&gt;

&lt;p&gt;Rearrange it and it becomes a capacity tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;concurrency needed = throughput × latency

L = 2,000 rps × 0.05 s = 100 concurrent requests in flight
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If each in-flight request holds a thread, a DB connection, and some memory, then &lt;strong&gt;100&lt;/strong&gt; is the number your pool sizes, thread counts, and autoscaling targets must survive. Not "we get 2,000 rps" - that number alone tells you nothing about what you must provision.&lt;/p&gt;

&lt;p&gt;Now watch what happens when latency degrades. Say W creeps from 50 ms to 200 ms under load:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;L = 2,000 rps × 0.20 s = 400 concurrent requests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same traffic. &lt;strong&gt;4× the concurrency.&lt;/strong&gt; Your connection pool of 100 is now a queue, the queue adds latency, the added latency raises L further - and you're in a feedback loop that looks, from the outside, like a sudden cliff.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the average hides the cliff
&lt;/h2&gt;

&lt;p&gt;Averages smooth over the exact thing that hurts you: the &lt;strong&gt;tail&lt;/strong&gt; and the &lt;strong&gt;knee&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Queueing theory (M/M/1 as the toy model) says wait time grows non-linearly with utilization ρ:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;W ∝ 1 / (1 - ρ)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;At 50% utilization, you're fine.&lt;/li&gt;
&lt;li&gt;At 80%, still okay.&lt;/li&gt;
&lt;li&gt;At 90% → 95%, wait time roughly &lt;strong&gt;doubles&lt;/strong&gt; for a 5-point move.&lt;/li&gt;
&lt;li&gt;Past that, it goes vertical.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the "knee." A system at 70% average utilization can be dropping requests at peak because the &lt;em&gt;instantaneous&lt;/em&gt; utilization is hitting 98%. Your one-minute average never shows it. That's why you measure &lt;strong&gt;saturation&lt;/strong&gt; (how full the resource is) and &lt;strong&gt;tail percentiles&lt;/strong&gt; (p99/p99.9), not just the mean.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fan-out multiplier everyone forgets
&lt;/h2&gt;

&lt;p&gt;One more trap. If a single user request fans out to N downstream calls, the arrival rate &lt;em&gt;your backend actually sees&lt;/em&gt; is not the user rate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1,000 user rps × 8 internal calls = 8,000 rps at the service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plug the real λ back into Little's Law and the required concurrency - and the blast radius of a downstream slowdown - is 8× what your "1,000 rps" intuition suggested. Most "mysterious" saturation incidents are just an un-budgeted fan-out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Measure three things, not one:&lt;/strong&gt; throughput (λ), latency distribution (W, including p99), and concurrency/saturation (L). Any two give you the third - and disagreement between them is a signal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Size pools from L, not from λ.&lt;/strong&gt; Throughput without latency is meaningless for capacity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch the knee.&lt;/strong&gt; Alert on saturation trending toward the non-linear zone, before latency explodes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Budget your fan-out.&lt;/strong&gt; Multiply user-facing rate by internal call count before you trust any capacity estimate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stop trusting the mean.&lt;/strong&gt; The average request is fine. The angry ones live in the tail.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of this requires exotic tooling - it requires reasoning about your system with the right model before the incident, not during it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I go deep on this - capacity, observability, queueing, resilience, and benchmark-backed labs - in a 3-month course on highload architecture (2nd cohort starts July 20). If the reasoning above was useful, the &lt;a href="https://skillhub.pro/uk/courses/highload-software-architecture-2" rel="noopener noreferrer"&gt;full program is here&lt;/a&gt;. Happy to answer questions in the comments either way.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>backend</category>
      <category>performance</category>
      <category>systemdesign</category>
    </item>
  </channel>
</rss>
