<?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: __mr__</title>
    <description>The latest articles on DEV Community by __mr__ (@__mr__).</description>
    <link>https://dev.to/__mr__</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%2F4038781%2F893179e5-de6c-4251-9f85-417c2b19be24.png</url>
      <title>DEV Community: __mr__</title>
      <link>https://dev.to/__mr__</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/__mr__"/>
    <language>en</language>
    <item>
      <title>Benchmarking API Rate Limiters at 35k+ RPS: Nginx vs. Python vs. Rust</title>
      <dc:creator>__mr__</dc:creator>
      <pubDate>Tue, 21 Jul 2026 18:35:52 +0000</pubDate>
      <link>https://dev.to/__mr__/benchmarking-api-rate-limiters-at-35k-rps-nginx-vs-python-vs-rust-3i5a</link>
      <guid>https://dev.to/__mr__/benchmarking-api-rate-limiters-at-35k-rps-nginx-vs-python-vs-rust-3i5a</guid>
      <description>&lt;p&gt;Rate limiters are supposed to shield downstream services from traffic spikes. But what happens to the gateway itself when hit with tens of thousands of unauthorized requests per second?&lt;/p&gt;

&lt;p&gt;In this benchmark study, I evaluate an API Rate Limiter Gateway implemented across three distinct proxy engines: &lt;strong&gt;Nginx&lt;/strong&gt;, &lt;strong&gt;Python (aiohttp)&lt;/strong&gt;, and &lt;strong&gt;Rust (Axum/Tokio)&lt;/strong&gt;. All three proxies enforce a &lt;a href="https://en.wikipedia.org/wiki/Leaky_bucket" rel="noopener noreferrer"&gt;Leaky Bucket algorithm&lt;/a&gt; variant to shield downstream Flask applications served by Gunicorn.&lt;/p&gt;

&lt;p&gt;The complete code, benchmark scripts, and Docker configurations are available on the &lt;a href="https://github.com/1MahdiR/api-rate-limiter-benchmarks" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. System Architecture Overview
&lt;/h2&gt;

&lt;p&gt;The testing setup simulates a production cloud deployment using Docker Compose:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0wzz3yos78lv5hqd0kc3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0wzz3yos78lv5hqd0kc3.png" alt="Architecture Diagram" width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Traffic Generator:&lt;/strong&gt; &lt;a href="https://github.com/wg/wrk" rel="noopener noreferrer"&gt;&lt;code&gt;wrk&lt;/code&gt;&lt;/a&gt; load-testing tool executing multi-threaded concurrent connections.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proxy Gateway Layer:&lt;/strong&gt; Alternates between Nginx, Python, and Rust implementations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Downstream Services:&lt;/strong&gt; Multi-worker Python Flask applications managed by Gunicorn.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  2. Performance Comparison
&lt;/h2&gt;

&lt;p&gt;Load generation was running for 30 seconds using &lt;code&gt;wrk&lt;/code&gt; with 2 threads and 100 persistent concurrent connections (&lt;code&gt;wrk -t2 -c100 -d30s --latency&lt;/code&gt;). Resource utilization was sampled directly via the Docker API (&lt;code&gt;docker stats&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;The benchmarks evaluate performance across three traffic profiles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scenario 1 (Baseline / No Limit):&lt;/strong&gt; All incoming requests are allowed through to the downstream Flask apps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scenario 2 (High Rate-Limit):&lt;/strong&gt; A moderate rate-limit threshold where the gateway rejects the majority of incoming traffic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scenario 3 (Strict Rate-Limit):&lt;/strong&gt; A highly restrictive rate-limit threshold triggering immediate rejection for nearly ~100% of incoming flood traffic.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario / Engine&lt;/th&gt;
&lt;th&gt;Avg Latency&lt;/th&gt;
&lt;th&gt;p99 Latency&lt;/th&gt;
&lt;th&gt;Throughput (RPS)&lt;/th&gt;
&lt;th&gt;Peak CPU %&lt;/th&gt;
&lt;th&gt;Peak RAM&lt;/th&gt;
&lt;th&gt;Non-2xx Ratio&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;S1: Baseline (No Limit)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Nginx&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;108.49ms&lt;/td&gt;
&lt;td&gt;126.28ms&lt;/td&gt;
&lt;td&gt;918.69&lt;/td&gt;
&lt;td&gt;322.27%&lt;/td&gt;
&lt;td&gt;20.25MiB&lt;/td&gt;
&lt;td&gt;0.26%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Python&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;128.88ms&lt;/td&gt;
&lt;td&gt;170.39ms&lt;/td&gt;
&lt;td&gt;773.15&lt;/td&gt;
&lt;td&gt;105.58%&lt;/td&gt;
&lt;td&gt;38.34MiB&lt;/td&gt;
&lt;td&gt;0.00%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Rust&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;64.29ms&lt;/td&gt;
&lt;td&gt;85.24ms&lt;/td&gt;
&lt;td&gt;1,547.96&lt;/td&gt;
&lt;td&gt;882.31%&lt;/td&gt;
&lt;td&gt;15.14MiB&lt;/td&gt;
&lt;td&gt;0.00%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;S2: High Rate-Limit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Nginx&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;1.77ms&lt;/td&gt;
&lt;td&gt;4.50ms&lt;/td&gt;
&lt;td&gt;1,220.85&lt;/td&gt;
&lt;td&gt;35.17%&lt;/td&gt;
&lt;td&gt;12.17MiB&lt;/td&gt;
&lt;td&gt;97.85%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Python&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;13.92ms&lt;/td&gt;
&lt;td&gt;21.29ms&lt;/td&gt;
&lt;td&gt;7,300.03&lt;/td&gt;
&lt;td&gt;108.50%&lt;/td&gt;
&lt;td&gt;28.40MiB&lt;/td&gt;
&lt;td&gt;99.92%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Rust&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;2.72ms&lt;/td&gt;
&lt;td&gt;3.05ms&lt;/td&gt;
&lt;td&gt;37,187.73&lt;/td&gt;
&lt;td&gt;226.38%&lt;/td&gt;
&lt;td&gt;10.60MiB&lt;/td&gt;
&lt;td&gt;99.99%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;S3: Strict Rate-Limit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Nginx&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;3.30ms&lt;/td&gt;
&lt;td&gt;9.48ms&lt;/td&gt;
&lt;td&gt;1,267.92&lt;/td&gt;
&lt;td&gt;26.96%&lt;/td&gt;
&lt;td&gt;10.86MiB&lt;/td&gt;
&lt;td&gt;99.89%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Python&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;13.21ms&lt;/td&gt;
&lt;td&gt;20.01ms&lt;/td&gt;
&lt;td&gt;7,568.54&lt;/td&gt;
&lt;td&gt;103.06%&lt;/td&gt;
&lt;td&gt;25.98MiB&lt;/td&gt;
&lt;td&gt;100.00%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Rust&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;2.75ms&lt;/td&gt;
&lt;td&gt;3.18ms&lt;/td&gt;
&lt;td&gt;36,538.96&lt;/td&gt;
&lt;td&gt;228.07%&lt;/td&gt;
&lt;td&gt;9.82MiB&lt;/td&gt;
&lt;td&gt;100.00%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2F1MahdiR%2Fapi-rate-limiter-benchmarks%2Fraw%2Fmaster%2Fresults%2Fbenchmark_plots.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2F1MahdiR%2Fapi-rate-limiter-benchmarks%2Fraw%2Fmaster%2Fresults%2Fbenchmark_plots.png" alt="Benchmark Visualizations" width="800" height="1120"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Architectural Insights &amp;amp; Bottlenecks
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Multi-Core Efficiency vs. The Thread Barrier
&lt;/h3&gt;

&lt;p&gt;Rust leverages a multi-threaded, work-stealing execution framework (&lt;code&gt;tokio&lt;/code&gt;), allowing it to dynamically distribute network polling across all allocated host CPU threads (spiking to &lt;strong&gt;882.31% CPU&lt;/strong&gt; in Scenario 1 and &lt;strong&gt;228.07%&lt;/strong&gt; in Scenario 3). Conversely, Python relies on a single-threaded asynchronous loop constrained by the Global Interpreter Lock (GIL), capping compute scaling to roughly &lt;strong&gt;105% CPU&lt;/strong&gt; (one physical core) regardless of incoming pressure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rate-Limit Short-Circuiting
&lt;/h3&gt;

&lt;p&gt;When active enforcement triggers in Scenarios 2 and 3, average processing latencies across all engines drop to low single digits. This occurs because the proxies reject unauthorized traffic in-memory via an immediate HTTP 429 response. This gateway short-circuiting bypasses downstream payload processing, Gunicorn socket allocation, and Flask app execution entirely, preventing resource saturation on the underlying services.&lt;/p&gt;

&lt;h3&gt;
  
  
  Throttled Throughput Analysis
&lt;/h3&gt;

&lt;p&gt;The throughput variance in Scenarios 2 and 3 (~36,500+ RPS for Rust, ~7,500 RPS for Python, and ~1,200 RPS for Nginx) is determined by specific runtime boundaries under load.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Python's&lt;/strong&gt; throughput is capped by single-core execution due to the GIL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nginx's&lt;/strong&gt; performance drop stems from connection slot exhaustion (&lt;code&gt;worker_connections&lt;/code&gt; saturation) or socket allocation overhead under intense traffic spikes. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rust&lt;/strong&gt; circumvents these limitations by utilizing a native, multi-threaded async runtime to evaluate in-memory limits and drop rejected sockets instantly across multiple CPU cores without garbage collection or worker slot starvation.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  4. Final Thoughts
&lt;/h2&gt;

&lt;p&gt;This benchmark isn't a verdict on which proxy engine is "best". It highlights how different runtimes behave under active rate enforcement.&lt;/p&gt;

&lt;p&gt;Nginx remains one of the most reliable, deterministic reverse proxies in the industry for static assets, routing, and standard load balancing. However, when serving as an &lt;strong&gt;in-memory rate-limiting shield during extreme connection floods&lt;/strong&gt;, its out-of-the-box worker pool limits can become a bottleneck unless fine-tuned for high-concurrency socket dropping.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>rust</category>
      <category>python</category>
      <category>nginx</category>
    </item>
  </channel>
</rss>
