DEV Community

drawfig
drawfig

Posted on

The 13% Penalty: Why Synchronous APIs Still Scale Better Than You Think"

When I built a lightweight framework to act as a data marshal for my decoupled single page apps, I didn’t intend to write an architectural thesis, but along the way, the data forced me to confront three pervasive myths in modern web development. First, PHP is inherently slow, second that synchronous APIs can’t scale to modern demand, and finally that you must trade developer experience for raw performance. I confronted these assumptions the only way that matters, by building. I created a framework called mouse-php, created CLI tooling that eliminated micro-framework friction, and stress tested said framework to failure. What the benchmarking revealed was illuminating. The industry consensus around these three ideas aren’t built on hard engineering limits but misattribution, vendor marketing, cargo-culting, and outdated assumptions. Here’s the story of that build, the benchmark data behind it, and why low-level simplicity routinely beats over-engineered complexity.

When I set out to build mouse-php, my goal was to create a lightweight API that acted strictly as a data marshal for decoupled single page applications. Some choices were obvious, like stripping out framework bloat and keeping execution linear. However, I quickly realized that most lightweight solutions sacrifice developer velocity in the name of absolute performance. This exposes a false dichotomy in modern web development: the belief that developer conveniences must always come at the expense of runtime performance. Because I built mouse-php to solve my own production friction, I focused on creating low overhead pipelines that preserve developer ergonomics, this led to three distinct architectural choices.

1. The Inverse-MVC Pattern & Thin PDO Data Marshals

Traditional MVC frameworks push heavy ORMs and deep object graphs into giant models. In mouse-php, I inverted this relationship and completely ditched the ORM in favor of a clean, structured PDO wrapper. My primary motivation for dropping the ORM wasn’t raw performance, it was Developer Experience. The idea that ORMs deliver a superior DX is a shallow illusion. ORMs make trivial queries like User::find(1) look convenient, but simple requests were never difficult to manage in the first place. The moment an API requires real world database operations requiring complex JOINs, aggregations, or precise index targeting the ORM becomes a hindrance rather than helpful. Developers end up having to fight method chains, specific esoteric syntax, and performance killing abstractions just to perform queries that could have been written in three lines of SQL. By replacing ORM with an easy to parse PDO wrapper, mouse-php models act as bare metal data marshals executing raw, prepared SQL statements while still being easy to maintain.

  • Better DX: Developers write clear, explicit SQL without fighting dynamic query builders or dynamic relation tracking.
  • Zero Hydration Tax: Bypasses object instantiation and reflection entirely, fetching database results directly into native arrays at C-speed.
  • High Reusability: Because models are reduced to simple data pipes rather than stateful domain objects, a single query model can be applied across multiple controllers or background jobs without instantiating heavy object trees.

The performance boost of keeping database overhead under 1ms is simply the natural byproduct of choosing a cleaner developer interface.

2. Associative Array First Routing

Industry standard routers rely heavily on complex regex engines to parse dynamic URIs on every incoming request. In mouse-php, the routing engine uses associative array hash lookup first. Because decoupled SPAs communicate most efficiently via explicit RPC-style endpoints, direct match routes can be resolved instantly at C-speed using PHP’s native array key lookups. While dynamic URIs are still supported for standard REST compatibility, they are treated as secondary paths. Bypassing regex route parsing for the majority of API traffic eliminates a massive chunk of per-request CPU tax.

3. One Way Middleware (Abandoning the Onion Model)

Most modern frameworks use an “onion model” where middleware wraps both the incoming request and outgoing response in nested closure. I always hated dealing with onion middleware in heavy frameworks, debugging nested execution stacks across response boundaries was a constant source of friction as well as trying to just figure out how to use it in some cases. I moved mouse-php to a strict one way pipeline simply to make middleware easier to write, manage, and debug. But just as with the PDO wrapper, simplifying the DX revealed a blueprint built for high-throughput performance. In this linear model, middleware has only two jobs: guarding the application and injecting context.

  • Fast Escapes: If an authentication check fails, the pipeline immediately triggers an escape hatch and terminates the request early, preventing the request from controller instantiation entirely.
  • Direct Context Injection: For example if an auth check queries user details, it injects that data directly into the request payload. The downstream controller receives pre-hydrated context without making a second query.

Middleware never touches outgoing responses. Any final response formatting happens predictably in the controller layer via lightweight utilities. By replacing wrapped stack frames with a simple, unidirectional pipeline, we gain total debugging clarity and linear execution speed.

Once mouse-php was running, I needed empirical data to verify its throughput limits. Benchmarking modern HTTP APIs accurately is surprisingly nuanced; preliminary tests with closed loop tools like autocannon produced inconsistent, warped latency figures, likely due to Coordinated Omission. To get accurate, un-skewed latency profiles, I migrated the test bench to wrk2, an open loop benchmarking tool that maintains a fixed, constant throughput rate.

The Test Environment

All benchmarks were executed locally on bare-metal hardware to isolate the stack’s raw execution capability:

  • Hardware: Lenovo IdeaPad 5 2-in-1 (AMD Ryzen AI 7 350, 16GB LPDDR5)
  • Operating System: Arch Linux (Kernel 7.2.8, KDE Plasma, non-essential background processes terminated)
  • CGI & Web Server: Apache httpd paired with PHP 8.5 PHP-FPM
  • PHP Runtime Tuning: OPcache enabled with aggressive timestamp checking disabled, JIT compiler active.

Test Parameters & Sweeps

  • Load Profile: wrk2 was configured with 4 threads, 100 concurrent connections, and a 60-second duration per run.
  • Rate Sweeps: Tests began at a 100 Req/s baseline and scaled up in increments to locate the framework's saturation knee.
  • Averaging & Outliers: Each target request rate was executed in three 60 second rounds and averaged together. If an anomalous system hiccup occurred, a 4th validation run was conducted to eliminate system jitter.
  • Thermal Mitigation: Because mobile SoCs aggressively thermal-throttle under sustained multi-core loads, a mandatory 5 minute system idle break was enforced between every run above 10,000 Req/s to prevent thermal soak from skewing tail-latency measurements.
  • Middleware Context Disclosures: Test endpoints were assigned to the native global_bypass middleware group. This bypassed client facing rate limiters and payload formatting guards that would otherwise reject standard synthetic wrk2 traffic, while mirroring how high throughput public endpoints are configured in production.

Using these test parameters, our control run targeted a route that returned a static JSON payload, isolating mouse-php’s routing request handling, and output pipeline from external I/O bound dependencies.

Static Endpoint Results

Graph of static endpoint benchmarks

Req/S Average Latency(ms) P99 Latency(ms) Max Latency(ms)
100 1.6 3.75 4.69
500 1.58 3.58 4.64
1000 1.43 2.49 3.29
2000 1.41 2.12 2.75
5000 1.7 2.82 14.74
10,000 3.08 4.59 45.18
12,000 1.66 3.13 37.12
14,000 1.78 4.81 51.7
15,000 1.79 4.53 52.73
15,500 2 11.88 74.94
16,000 1.93 13.61 68.73
17,000 1.99 12.4 57.15
17,500 2.27 19.4 72.04
18,000 3550 6843.33 7446.67

Analyzing this dataset reveals clear systems level behaviors regarding where the stack excels and precisely where hardware limits force it to break:

  1. The 2000 Req/s Efficiency Sweet Spot:

    Across the entire test suite, 2000 Req/s represents the optimal operational state. At this rate, latency hits its absolute minimum across all metrics : 1.41ms Average, 2.12ms P99, and a tight 2.75ms Max Latency. This is where request frequency perfectly aligns with CPU governor frequency scaling, worker process scheduling, and L1/L2 cache retention without triggering thread contention or queue accumulation.

  2. The 15,000 Req/s P99 stability Zone:

    While Max Latency tail drift begins decoupling after 2000 Req/s as individual process scheduling delays occur, the Average and P99 latency curves remain tightly locked together all the way to 15,000 Req/s. P99 latency does not break 5ms across a 15x increase in load. This proves that under normal operating conditions, 99% of requests pass through the framework’s array routing and one-way pipeline nearly instantaneously.

  3. The Warning Sign (15,500 Req/s):

    At 15,500 Req/s, P99 jumps from 4.53ms to 11.88ms. This decoupling between Average Latency(~2.0ms) and P99 marks the inflection point where OS socket backlogs and FastCGI worker queues begin running near capacity.

  4. The 17,500 Req/s Saturation Knee:

    The system maintains stability up to 17,500 Req/s with a 2.27ms average response time. Pushing the load to 18,000 Req/s causes the latency to explode immediately to 3.55 seconds.

This vertical wall isn’t an application level framework crash, it is classic FastCGI socket backlog exhaustion. When incoming connection rates exceed available PHP-FPM process slots, the OS TCP backlog fills up, incoming requests queue in kernel space, and the system hits its absolute physical CGI throughput ceiling on this hardware.

With the static baseline established, the next phase of stress-testing targeted an API endpoint that executes a live database query on every request, simulating a realistic application workload.

Database Endpoint Results

Graph of endpoint hitting database benchmarks

Req/S Average Latency(ms) P99 Latency(ms) Max Latency(ms)
100 2.2 4.94 5.59
500 1.66 3.8 5.04
1000 1.52 2.64 3.46
2000 1.63 2.63 4.03
5000 1.96 3.2 15.48
10,000 3.43 5.83 55.38
12,000 1.69 3.29 37.42
13,000 1.93 6.02 56.91
14,000 1.98 11.38 59.11
15,000 2.26 13.71 60.84
15,250 2.27 16.4 58.37
16,000 3380 6755 7280

Comparing this dataset directly against our static control run reveals a great deal about mouse-php's execution efficiency under real-world I/O pressure:

  1. The Remarkable 13% Delta:

    The standout takeaway from these benchmarks is how little performance degrades when introducing a database query. Moving from a static JSON response to a live MariaDB lookup shifts our maximum stable throughput ceiling from 17,500 Req/s down to 15,250 Req/s is a mere 13% reduction. In typical web frameworks, the ORM and object hydration layer create a massive performance cliff. Here, because mouse-php uses a thin PDO wrapper that bypasses hydration overhead entirely, the database roundtrip adds less than 1ms of average latency across the board.

  2. The Persistent 2,000 Req/s Sweet Spot:

    Just like the static control run, 2,000 Req/s remains the absolute peak efficiency zone. Even with database reads executing on every single request, the system hits its tightest latency alignment (1.63ms Average, 2.63ms P99) right at this threshold. This confirms that CPU cache locality and process scheduling dynamics dominate the profile long before database I/O becomes the bottleneck.

  3. Predictable P99 Coupling & Early Decoupling:

    The P99 latency behavior mirrors the static control curve, though it begins its upward drift slightly earlier (~13,000 to 14,000 Req/s). This minor variance makes complete sense: as concurrent requests climb, the local MariaDB connection pool and socket buffers absorb additional load, causing occasional tail-latency stretching just before the saturation threshold.

  4. The 15,250 Req/s Saturation Wall:

    At 15,250 Req/s, the system holds the line with a sub-3ms average latency (2.27ms). Pushing past this edge to 16,000 Req/s triggers an immediate system blowout, with average latency jumping to 3.38 seconds. Just like the static run, this wall represents the physical concurrency limit of our local FastCGI and database socket queues on mobile hardware, rather than an application-layer failure.

The parallel behavior of these two charts tells a definitive story. mouse-php is bounded primarily by operating system socket limits, CGI throughput, and local hardware constraints, not by the application code or data-fetching pipeline. When your database backed endpoints can sustain over 15,000 requests per second with sub 2.5ms latency on a laptop, the argument that "synchronous PHP can't scale" completely falls apart.

Now with the data we have from our benchmarks we can more closely examine and determine the validity of pervasive myths and popular beliefs that dictate many of the design choices in web development. The numbers show us that the limitations that are often attributed to entire programming languages or execution models are almost always the result of something more specific that became an artificial dependency.

Myth 1: “PHP is Inherently Slow”

For over a decade, “PHP is slow” has permeated the zeitgeist of web developers through the industry. This is usually cited as a reason to justify migrating simple applications to heavier enterprise run times or microservices. However, this myth is the product of two sources that this data shows are both incorrect now or assigning the blame to the wrong source. The first source of this was likely from bad experiences from old versions of PHP, and the other misattribution: blaming the language runtime for the sins of the framework.

PHP a decade ago when this argument started was a much different beat then what we have now, and part of the problem is these critics of PHP have not been paying attention to that fact. As shown with these benchmarks PHP 8.2+, PHP-FPM, and optimizations like OPcache can make even synchronous PHP extremely fast and reliable while still letting you have the advantages of synchronous operations. There are also operations in PHP that are just faster than anything else with the same accessibility as PHP, like database operations and encryption. One of the things I learned building mouse-php and mRPC/colby-jack was how to implement HMAC for both PHP and Javascript, and not only was PHP’s implementation more performant but far easier which means more secure as there are less chances for me as a developer to make an error.

The other source of this becomes obvious because mouse-php makes engineering choices that removes many of the sources of bloat that come from typical frameworks. As a result the performance uplift over a typical batteries included framework is obvious. I wasn’t the first to notice this naturally, microframeworks like slim exist to address this same bloat but this remains a source of this myth because typically people bench marking PHP reach for Laravel or Symfony as that’s what most people outside the PHP ecosystem think of for PHP backends. What our benchmarks show just like the outdated idea of PHP that the framework tax and the abstraction baggage that comes with the massive frameworks is the true performance killer and not the language runtime.

Myth 2: "Synchronous APIs Can’t Scale to Modern Demand”

The modern cloud-native ecosystem is built on the premise that synchronous execution models hit a hard wall under real world load, necessitating complex asynchronous event loops, reactive streams, or Kubernetes-orchestrated microservices.

Our MariaDB benchmark challenges this assumption. By pushing a database backed endpoint to 15,250 requests per second with sub 2.5ms baseline latencies we showed that a simple synchronous request response model can absorb massive production traffic. To put this number in perspective 15,250 requests per second equates to 1.3 billion requests a day running on a laptop not built for web hosting. For 99% of web applications, microservice splits and async complexity solves problems they will simply never have and just introduces operation overhead, or latency taxes that definitely don’t need.

This does reveal the source of this myth however, cargo-culting and vendor marketing, the idea that these complex solutions are the best practice primarily comes from people seeing FAANG companies use these solutions in their own stacks. The problem with this is the colossal delta in traffic and developers between even a decently high traffic application and Google. The scale that a FAANG corporation’s infrastructure is working at means that the economics and what makes something efficient change drastically meaning that trying to copy their infrastructure leads to unnecessary complexity or even worse performance in certain areas then the simpler solution.

Myth 3: “Performance vs. DX is a Zero-Sum Game”

Perhaps the most damaging myth in web development and one I began to be frustrated with the trade off narrative: the belief that if you want high performance, you must write low level, tedious boilerplate; and if you want great Developer Experience(DX), you must accept a heavy runtime tax.

Building mouse-php showed me that not only was this not true, but many features touted as making things easier for developers are shallow making the performance trade offs bad deals for the convenience, actually make things more difficult when you actually try building something, are just a development tool and don’t need to affect the performance of the production runtime. The interesting part of the performance uplift shown in this post was that it wasn’t the direct target of the choices made, removing ORMs and simplifying middleware management and flow was all done to just make it easier to understand and work with. These benchmarks are particularly interesting because this API was created from a blank directory. From that blank directory I created and configured the endpoints in around ten minutes, which was a great demonstration that performance doesn’t need to be hard.

Good developer experience isn’t about hiding complexity behind magic abstraction, it's about making the complexity easier to understand or removing redundant work. True DX is about clarity, predictability, and getting out of an engineer’s way. When tools are simple to reason about, high performance isn’t something you have to explicitly aim for; good engineering choices become obvious as a natural byproduct of clean design.
When we strip away industry dogma, cargo-culted microservices, and multilayered abstraction taxes, web architecture becomes remarkably clear. The benchmarks suggest to us: simplicity scales, hardware is rarely the real bottleneck, and high performance is simply the natural byproduct of clean design.
For 99% of web applications, the push toward "cloud complex" architecture solves a scaling problem they will never face, while saddling them with operational overhead they definitely don't need. You don't need a thousand dollar auto scaling cluster to build something fast.
What all of this has shown me is that respecting developer velocity and application performance are not mutually exclusive. When you look past industry assumptions and focus on building well reasoned solutions to immediate problems, you can create outcomes that defy expectations.
This approach gave me a tool that not only delivered sub 3ms latencies on database backed requests, but did so with a backend that took ten minutes to build from an empty directory, all without the added complexity of managing asynchronous loops, explicit state lifecycles, or heavy framework abstractions.
Sometimes, all it takes is stepping back, cutting out the bloat, and letting the metal do what it was built to do.

If you want to check out the code, clone the repo, or test mouse-php yourself, you can find it here:
https://github.com/drawfig/mouse-php

If you’d like to learn more about how to install it using create-mouse-app or some of the other tooling and architectural choices I made in this project the Docs are here:
https://github.com/drawfig/mouse-php/wiki

Top comments (0)