
A $12 server (1 vCPU, 2GB RAM) handling 280,000 users: how 218 developers optimized down to the deepest layer
Vũ Khôi
Tác giả tại Wotech Electronics Blog

A $12 server (1 vCPU, 2GB RAM) handling 280,000 users: how 218 developers optimized down to the deepest layer
How many simultaneous users can a $12 server with a single vCPU and 2GB of RAM actually serve? In a challenge organized by Arjay McCandless, 218 developers submitted entries building a social media API on exactly that "poverty-spec" hardware — and the top 10 handled between 240,000 and 280,000 concurrent users, roughly 23,000–26,800 requests per second, with P95 latency under 500ms. This article breaks down the deep-layer techniques behind those implausible numbers.
Quick answer
- The task: build a Social Media API on a 1 vCPU, 2GB RAM server (~$12/month), with SQLite mandatory, caching forbidden for feed results and token-auth state — every write request must re-verify its token from scratch and successfully commit to the database before responding.
- The top result: 280,000 concurrent users (~26,800 requests/second), P95 under 500ms, P99 under 1 second, error rate below 1% over 5 minutes of sustained load — on a seed database of 500,000 posts and 2 million likes.
- The shared formula of the entire Top 10: use
io_uringwith Direct Descriptors to break past connection limits, batching from the I/O layer down to transactions, buffer reuse to avoid memory allocation, and separating SQLite's WAL checkpointing from the main request loop. - The two co-champions took extreme paths: one used Lean, the theorem-proving language, to generate mathematically verified C code; the other rewrote an entire storage engine for SQLite's file format — hitting a P99 of just 23ms, but judged by the organizer to exceed the spirit of the rules and unusable in real production.
What was the challenge?
Entrants had to build a social media API with these operations: load the feed (20 newest posts with author and like counts), open a post, create a post, like, and a health check. The rules were tightened so nobody could "cheat" with caching:
- SQLite was mandatory, seeded with 500,000 posts and 2 million likes.
- Caching feed results was forbidden, and caching token-auth state was forbidden — every write still had to verify its token from scratch.
- Writes had to commit successfully to the database before the response was sent.
- The system had to sustain load continuously for 5 minutes with P95 under 500ms, P99 under 1 second, and an error rate below 1%.
Why does the ordinary approach collapse immediately?
In a typical web app, a single GET /feed request passes through a long chain: receive the network packet via socket, the HTTP server parses bytes into method/path/headers, run through middleware, call the SQLite driver to execute a query scanning B-Trees and database pages, map each row into objects, serialize to JSON, then push it back out the socket. Repeating that pipeline tens of thousands of times per second on exactly 1 vCPU is hopeless.
There is also an operating-system wall: each process gets at most 65,000 file descriptors. When hundreds of thousands of users hold persistent (keep-alive) connections, churning connections open and closed to dodge the limit burns so much CPU on accept and teardown that the server falls over by itself.
The Top 10's shared weapon: io_uring and batching
The solution appearing in all 10 highest-scoring entries is Linux's io_uring combined with Direct Descriptors: instead of the process's regular file descriptors, sockets live directly in tables attached to io_uring's rings, load-balanced across multiple rings to break the 65,000-connection ceiling. io_uring also lets I/O operations be submitted and their completions harvested in batches, minimizing context-switch cost.
On that foundation, each author improvised in their own language — from pure Assembly, C, Rust, F# and Crystal to Ada and even Lean, a language built for proving mathematical theorems.
The most notable approaches in the Top 10
| Approach | Result | Key technique |
|---|---|---|
| Entire web server in x86_64 Assembly | 240,000 users, ~23,000 RPS, least RAM (545MB) | Hand-written HTTP parsing, routing, JSON, auth, event loop; a hard cap on completions processed per loop to protect P99 |
| F# "hijacking" ASP.NET Core's Kestrel | 247,000 users, ~24,000 RPS, P99 just 72ms | Replaced the entire transport layer with hand-written io_uring, bypassing Kestrel's HTTP pipeline; JSON written straight into the buffer; WAL checkpoint on its own thread |
| C and Assembly from the same author | Both hit 250,000 users, ~24,100 RPS | Deliberately wait 6ms to gather a batch of 128 requests, then commit once; the C version had better P99 (294ms vs 397ms) and used less RAM |
| Rust with a smart feed query | 264,000 users, ~25,000 RPS | No framework, no async runtime; if post IDs are close together, one small range scan; if spread apart, fall back to the standard query |
| C: SIMD authentication + direct B-Tree access | 266,000 users, ~25,500 RPS | Batch tokens and verify many signatures at once with SIMD; skip the SQL layer and call SQLite's internal B-Tree cursors directly |
| Crystal with the garbage collector disabled | 270,000 users, ~25,700 RPS | Fully manual memory: in-place parsing, shared buffer pools, fixed arena memory per batch; SQLite compiled with PGO |
| Ada with explicit checkpoint coordination | 270,000 users, ~25,900 RPS, P99 177ms | WAL checkpoint coordinated between main and helper processes; naming the exact operation to cancel at kernel level when closing connections en masse |
Some details show performance is often won in tiny places: one author's Assembly entry initially set its in-flight accepts constant to 16 and scored 125,000 users — changing that single constant to 256 doubled the score to 250,000. The F# entry cut write waits from 57ms to 6ms merely by letting the checkpoint thread be woken immediately instead of sleeping 50ms.
The two co-champions: mathematics vs "rewriting the database engine"
At 280,000 users (~26,800 RPS) sit two highly unusual approaches:
- Using Lean — the theorem-proving language — to generate C code (P99 316ms). The author defined the API and the data's invariants in Lean: post IDs are consecutive with no gaps, timestamps increase with ID. They then mathematically proved that an optimized read plan returns results identical to the specification, and generated the C code from that. With those invariants, the server reads backwards directly over rows through the B-Tree without consulting the timestamp index; if at runtime the data no longer satisfies them, it falls back to standard SQL at once.
- A hand-written storage engine for SQLite's file format in C (P99 just 23ms). This entry does not even link the SQLite library; its own engine reads and writes the B-Tree and WAL structures of SQLite files directly, keeping a table of pointers straight to each database page in memory or in WAL frames. That is the contest's record-low P99 — but the organizer himself judged it beyond the spirit of the "use SQLite" rule, and nobody would hand-write an entire database engine for real production: the data-loss risk and maintenance burden are far too large.
What lessons apply to everyday work?
Few working developers will ever rewrite an HTTP server or touch SQLite's internals. But the fact that all 10 entries — regardless of language — converged on the same place reveals the core performance principles:
- Control tightly the real work happening per request: from the query and its execution plan, to the number of memory allocations and the cost of serialization.
- Exploit batching and buffer reuse to relieve the CPU and the garbage collector — overhead gets amortized across many requests instead of being paid per request.
- Never let the user-serving main path be blocked by housekeeping: cleanup and disk-sync work such as database checkpoints must be taken off the request path.
The analysis closes with an open question to the backend community: handed a 1 vCPU, 2GB RAM VPS with your everyday stack (Java/Spring Boot, Go, Node.js or .NET) at default settings, how many RPS would it hold before hitting the ceiling — and where would you optimize first?
Frequently asked questions
What is io_uring, and why did every top entry use it?
io_uring is the Linux kernel's asynchronous I/O mechanism: applications submit and reap many I/O operations in batches through ring buffers shared with the kernel. Combined with Direct Descriptors (sockets stored in the ring's own tables instead of process file descriptors), one process can serve far more than the usual 65,000-descriptor ceiling while slashing context-switch cost — exactly this challenge's two biggest bottlenecks.
Why ban caching if the goal is maximum load?
Because caching would turn the contest into a memory contest instead of a processing one: with feed results and token state cached, the server would barely touch the database. Forbidding it forced every entry to optimize at the deep layer — I/O, queries, allocation — with every write still verifying its token and committing for real to SQLite.
Did the hand-written storage engine really "win"?
On numbers, yes: P99 of 23ms, the contest's lowest, tied for first at 280,000 users. But the organizer considered it beyond the spirit of the "use SQLite" rule, and the approach does not transfer to real life: hand-writing an engine for a database file format carries enormous data-loss risk and maintenance cost. It wins as a technical demonstration, not as a solution.
What do P95 and P99 mean?
They are latency percentiles: P95 under 500ms means 95% of requests completed within 500ms; P99 under 1 second means 99% finished within a second. Percentiles are used instead of averages because an average can hide the slowest requests — the ones real users feel most.
Can these techniques be used in real systems?
The principles, yes: batching, buffer reuse, fewer allocations on the hot path, moving checkpoints and cleanup off the request path, and scrutinizing query plans all work in ordinary stacks. The extreme parts — a server in Assembly, GC switched off, a bespoke storage engine — are contest demonstrations that trade reliability and maintainability for performance.
Conclusion
The challenge shows that the limits of cheap hardware lie far beyond our default assumptions — the bottleneck is usually unoptimized software, not the numbers 1 vCPU and 2GB of RAM. And the biggest lesson is not any single technique but the convergence itself: pushed to the limit, every language and every school of thought returns to the same few foundations — less wasted work per request, work in batches, reuse memory, and never let the system's housekeeping block the user's path.
Source: an analysis by Lu Minh Khuong in the VGU Tech Alumni Facebook group (4 Nov 2026) of the optimization challenge organized by Arjay McCandless; figures are the contest results as summarized in that post, edited and structured by Wotech Electronics. Illustration: the challenge diagram attached to the original post.
Bài viết liên quan

5 tháng 11, 2026 · 5 phut doc
Knowledge Engine: building a 'second brain' for office work
Every day an office worker takes in information from dozens of channels: group chats, email, meetings, shared files, a word in the hallway. Then when it matters, you cannot remember which file held t…

4 tháng 11, 2026 · 9 phut doc
NAS suspected of ransomware? A 6-phase incident response playbook: isolate, preserve evidence, recover safely
Files on the NAS are being encrypted en masse, a ransom note has appeared, or the device is behaving strangely — and the common reflex is to "just reinstall it quickly". According to a technical writ…

4 tháng 11, 2026 · 5 phut doc
Turn a Raspberry Pi into a conversational AI agent that runs fully local: privacy and cost
At a time when every AI assistant "lives" on some tech corporation's cloud, an open-source project is swimming against the current: Be More Agent turns the Raspberry Pi — a tiny, cheap palm-sized com…