🎉 Giảm 20% cho đơn hàng đầu tiên!✦🚚 Miễn phí vận chuyển cho đơn trên 500.000₫✦⚡ Flash Sale — Số lượng có hạn!✦🎁 Quà tặng kèm cho đơn từ 1.000.000₫✦🎉 Giảm 20% cho đơn hàng đầu tiên!✦🚚 Miễn phí vận chuyển cho đơn trên 500.000₫✦⚡ Flash Sale — Số lượng có hạn!✦🎁 Quà tặng kèm cho đơn từ 1.000.000₫✦
20 美元服务器无缓存扛住 4.6 万用户:瓶颈在数据库,不在框架
Quay lại Blog

20 美元服务器无缓存扛住 4.6 万用户:瓶颈在数据库,不在框架

2 tháng 11, 20262 phút đọcVũ Khôi
Tiếng Trung
#Tiếng Trung
Vũ Khôi

Vũ Khôi

Tác giả tại Wotech Electronics Blog

20 美元服务器无缓存扛住 4.6 万用户:瓶颈在数据库,不在框架

一项社区基准测试正在开发者圈子中广泛传播:同一个应用、同一台约 20 美元的 VPS(8 vCPU、16GB 内存,应用与 PostgreSQL 跑在同一台机器上),压测结果却从 8,300 到 46,000 多个虚拟用户不等——最高约每秒 11,500 个请求。差距并不来自选择了"快"或"慢"的语言,而来自应用与数据库打交道的方式。

基准条件有多苛刻?

被测应用是 Conduit——一个拥有 19 个 REST API 端点的 Medium 克隆。测试条件贴近真实生产环境且相当严苛:

  • 单台服务器:8 个专用 vCPU、16GB 内存,应用与 PostgreSQL 同机部署(不单独拆分数据库服务器)。
  • 预置数据:200,000 名用户、500,000 篇文章、500 万次收藏、250 万次关注。
  • 禁止一切缓存:不用 Redis,不用内存响应缓存。每个请求都必须直接读写 PostgreSQL。
  • 每次登录都必须用 bcrypt cost 10 验证密码;写接口必须提交成功后才返回响应。
  • 达标标准:持续 5 分钟压力下,P95 延迟低于 500ms,P99 低于 1,000ms,整体错误率低于 1%。

最初由 Arjay McCandless 在 Go、Rust、Java 和 Python 上进行的测试结果为 10,000–24,000 用户。随后项目全部开源,供社区用实际工作中熟悉的框架自行优化——而最有价值的经验恰恰来自这些社区提交。

经验一:真正的瓶颈通常在数据库

使用 ASP.NET Core Minimal APIs 加 Entity Framework Core 的 C# 提交只达到 8,300 用户。看资源图就明白问题所在:C# 应用只占 19% CPU,而 PostgreSQL 吃掉了整台机器 78% 的 CPU。应用代码跑得很快,但 ORM 生成的查询太重,数据库成了瓶颈。

Ruby on Rails(Falcon 服务器,开启 YJIT)达到 10,000 用户(约 2,600 RPS),应用占 59% CPU、数据库占 30%——画面类似:框架并不是系统中最慢的部分。

经验二:拆分分页查询带来性能突变

Rust 提交(Axum + Tokio + Tokio-Postgres)达到 43,000 用户(约 11,000 RPS)。最大的秘诀在文章列表逻辑:不一开始就 JOIN 一大堆表,而是先只 SELECT 当前页的 ID 列表,再只为这些 ID 计算标签、收藏数和状态。避免对将被丢弃的数据行做无用计算,为数据库节省了大量 CPU。

经验三:放弃 ORM 对象映射,让 PostgreSQL 直接生成 JSON

最惊人的跃升来自 Ruby 和 Python 放弃传统框架、改用底层技术栈的提交:

  • 不用 Rails 的 Ruby 版本(Falcon 上的 Rack,去掉 ActiveRecord):从 10,000 跃升至 37,000 用户。
  • 不用任何 Web 框架的 Python 版本(uvloop + httptools + asyncpg + orjson):从 10,000 升至 46,000 用户——约 11,500 RPS,全场最高。

两者思路相同:跳过把数据库行映射为语言模型对象(ORM hydration)再序列化为 JSON 的步骤,改由 PostgreSQL 用 json_build_object 直接拼装 JSON 并流式写入响应,再结合查询流水线(pipelining)减少应用与数据库之间的网络往返。

其他值得注意的结果

  • Kotlin(Ktor + Vert.x Postgres 客户端):44,000 用户——充分利用协程和 Vert.x 客户端的查询流水线。
  • Lua(OpenResty / Nginx + LuaJIT):33,000 用户——把 bcrypt 验证拆到独立的工作线程池,避免阻塞 Nginx 事件循环。
  • PHP(Symfony 微内核 + Swoole):32,000 用户,P95 仅 234ms——Swoole 让 worker 常驻运行,不必每个请求重启框架。
  • Elixir(Phoenix + Ecto):19,000 用户——上限出现在连接池开始丢弃等待过久的请求时。

对系统架构的启示

优化框架或运行时只解决了故事的一部分。当应用和数据库共享资源时,应优先检查三件事:

  1. 减少数据库在每个请求上要做的工作——优化 SQL,先取 ID 再 JOIN 辅助数据。
  2. 削减数据库层与 HTTP 响应之间的中间序列化/反序列化步骤。
  3. 合理配置连接池,读写池分离,避免重型写请求造成队头阻塞(head-of-line blocking)。

对在普通配置 VPS 上运行系统的小企业来说,这是个好消息:在考虑升级服务器或加 Redis 之前,先审视应用查询数据库的方式。您现在这台 20 美元的服务器,很可能还能扛住数倍于当前的用户量——前提是使用方式正确。

来源:开发者社区分享的 Conduit 基准测试(原测试作者 Arjay McCandless,项目已开源供社区优化),2026 年 11 月。

Bài viết liên quan