
A $20 server handling 46,000 users with no cache: the bottleneck is the database, not the framework
Vũ Khôi
Tác giả tại Wotech Electronics Blog
A $20 server handling 46,000 users with no cache: the bottleneck is the database, not the framework
A community benchmark is making the rounds among developers: the same application, on the same roughly $20 VPS (8 vCPUs, 16GB RAM, running both the application and PostgreSQL), sustained anywhere from 8,300 to more than 46,000 virtual users — up to about 11,500 requests per second. The difference did not come from picking a "fast" or "slow" language, but from how the application talks to the database.
How strict were the benchmark conditions?
The test application is Conduit — a Medium clone with 19 REST API endpoints. The conditions were realistic and deliberately harsh:
- A single server: 8 dedicated vCPUs, 16GB RAM, application and PostgreSQL on the same box (no separate DB server).
- Preloaded data: 200,000 users, 500,000 articles, 5 million favorites and 2.5 million follows.
- All caching forbidden: no Redis, no in-memory response cache. Every request must read from and write to PostgreSQL directly.
- Every login must verify passwords with bcrypt cost 10; write endpoints must commit before responding.
- Passing bar: P95 latency under 500ms, P99 under 1,000ms and an overall error rate under 1% across 5 continuous minutes of load.
The original runs by Arjay McCandless across Go, Rust, Java and Python landed between 10,000 and 24,000 users. The project was then open-sourced so the community could optimize it with familiar, real-world frameworks — and the most valuable lessons came from those submissions.
Lesson 1: The real bottleneck is usually the database
The C# entry, using ASP.NET Core Minimal APIs with Entity Framework Core, reached only 8,300 users. The resource chart tells the story: the C# application used just 19% CPU, while PostgreSQL consumed 78% of the whole machine. The application code was very fast, but the queries its ORM generated were so heavy that the database became the choke point.
Ruby on Rails (on the Falcon server with YJIT enabled) reached 10,000 users (about 2,600 RPS) with the app at 59% CPU and the DB at 30% — a similar picture: the framework was not the slowest part of the system.
Lesson 2: Splitting the pagination query is a breakthrough
The Rust entry (Axum + Tokio + Tokio-Postgres) reached 43,000 users (about 11,000 RPS). Its biggest trick was in the article-list logic: instead of JOINing a pile of tables up front, it first SELECTs only the IDs of the current page, then computes tags, favorite counts and status for just those IDs. Avoiding wasted computation on rows that will be discarded saves a large amount of database CPU.
Lesson 3: Drop ORM hydration and let PostgreSQL build the JSON
The most dramatic jumps came from Ruby and Python entries that left traditional frameworks for low-level stacks:
- Ruby without Rails (plain Rack on Falcon, no ActiveRecord): from 10,000 up to 37,000 users.
- Python with no web framework at all (uvloop + httptools + asyncpg + orjson): from 10,000 up to 46,000 users — about 11,500 RPS, the top of the table.
Both share one idea: skip mapping database rows into language-level model objects (ORM hydration) and then serializing those objects to JSON. Instead, PostgreSQL assembles the JSON itself with json_build_object, streamed straight into the response, combined with query pipelining to cut network round-trips between app and database.
Other notable results
- Kotlin (Ktor + Vert.x Postgres client): 44,000 users — thorough use of coroutines and query pipelining through the Vert.x client.
- Lua (OpenResty / Nginx + LuaJIT): 33,000 users — bcrypt verification moved to a separate worker thread pool so it never blocks the Nginx event loop.
- PHP (Symfony micro-kernel + Swoole): 32,000 users with a P95 of just 234ms — Swoole keeps workers running persistently instead of rebooting the framework per request.
- Elixir (Phoenix + Ecto): 19,000 users — hitting its ceiling when the connection pool started dropping requests that waited too long.
Takeaways for system architecture
Optimizing the framework or runtime solves only part of the story. When the application and the database share the same resources, three things deserve review first:
- Reduce the work the database does per request — optimize the SQL, and fetch IDs before joining auxiliary data.
- Cut intermediate serialize/deserialize steps between the database layer and the HTTP response.
- Configure the connection pool properly, with separate read/write pools to avoid head-of-line blocking when heavy write requests arrive.
For small businesses running systems on a modest VPS, this is good news: before upgrading the server or adding Redis, audit how the application queries the database. Your current $20 server may well handle several times its present user load — if it is treated right.
Source: the community Conduit benchmark write-up (original benchmark by Arjay McCandless, open-sourced for community optimization), November 2026.
Bài viết liên quan

2 tháng 11, 2026 · 6 phut doc
Elon Musk predicts smartphones and apps will disappear in 5–6 years, replaced entirely by AI
- On The Joe Rogan Experience 2404 (October 31, 2025), Elon Musk predicted that within about 5–6 years, traditional phones, operating systems and apps will disappear. - Instead, the mobile device bec…

2 tháng 11, 2026 · 4 phut doc
CXMT completes validation of 6th-gen 4F² DRAM: China's memory maker steps ahead of Samsung and SK hynix
CXMT — China's largest DRAM manufacturer — has confirmed it completed engineering sample validation for its sixth-generation DRAM platform built on the 4F² cell architecture, while the entire market …

2 tháng 11, 2026 · 6 phut doc
AI uncovers a PHP bug dormant for 14 years: security lessons for legacy systems
A story currently making the rounds in the developer community under the hashtag php2shell: an AI tool reviewing old source code discovered a PHP vulnerability that had quietly existed for 14 years —…