转自:CSDN(ID:CSDNnews)
作者:yakkomajuri
产品上线才一周,这个初创团队就做了件大胆的事——他们决定彻底重写后端。而且这次不是继续用炙手可热的 Python,而是换成了 Node。
乍听之下,这像是一场典型的“过早优化”,但他们的理由并不只是性能,而是一次对技术生态、团队节奏与长期可扩展性的全面权衡。
来源:https://blog.yakkomajuri.com/blog/python-to-node
我们刚干了一件疯狂的事:在产品上线仅一周后,我们把后端从 Python 完全重写成了 Node。

为什么要这样做?为了可扩展性
。没错,就是为了扩展,刚上线一周就这么做了。
从某些角度看,这个时间点其实挺合适的——毕竟当前代码库还小,用户体量也不多。但另一方面,这在很多开发者看来,完全违背了早期创业公司的建议,过往很多人觉得初创公司就是应该把产品先上线、先卖产品,等达到产品市场匹配再考虑扩展。
其实,我们并没有经历过那种用户暴涨、迫使我们快速扩展系统的神奇发布周。通常来说,你选择的技术栈应该能够在很长一段时间内合理扩展,直到你真正需要考虑换框架或用另一种语言重写后端(比如 Rust)的时候。
那么,为什么我们要弃用 Python 而选择 Node?接下来分享我们这么做的具体原因。

Python 的异步真的太糟糕了
我是 Django 的铁粉。在知名的开源产品分析平台 PostHog 项目中,我第一次接触它,从那以后它几乎成了我大部分项目的首选后端框架。Django 能让你快速启动项目,提供优秀的工具和抽象,同时也足够灵活,能按需调整。
所以自然地,当我开始为自己的 Skald(一个用于构建 AI 原生应用的 API 平台,https://github.com/skaldlabs/skald)项目编写后端代码时,也选择了 Django。
问题是,Skald 经常调用大语言模型(LLM)和向量嵌入 API,这意味着我们有大量的网络 I/O,希望是异步的。不仅如此,我们还经常希望并发发送大量请求,比如在为文档的不同片段生成向量嵌入时。
在 Django 中,这些操作很快就变得非常混乱。
我先说明一下,我和同事都没有太多 Python 异步编程经验(我以前主要在 Node 上做异步密集型服务),但我觉得这正是问题的核心:写出稳健高效的 Python 异步代码非常困难,也不直观。你必须深入理解各种底层原理才能做到。
我其实很想花时间真正掌握 Python 异步,但在我们的场景下,你会面临两个问题:
作为早期创业公司,你会浪费宝贵的时间,而这些时间本该用来上线产品。
在这个过程中,很容易踩坑,把自己搞得一团糟。
起初,我把问题归咎于自己——脑子里一直在响起“糟糕的程序员!糟糕的程序员!”的声音。虽然更有经验的人会轻松些,但我们发现 Python 异步编程的基础其实也有些不牢固。
与 JavaScript 不同,它从一开始就有事件循环;Go 也创造了 goroutine 的概念(这两种并发模型我都很喜欢,也在生产环境中用过)。而 Python 的异步支持是后来才补上的,这正是困难所在。
有两篇博客很好地解释了这个问题:
《Python has had async for 10 years — why isn’t it more popular?》:https://tonybaloney.github.io/posts/why-isnt-python-async-more-popular.html
《Python concurrency: gevent had it right》:https://harshal.sheth.io/2025/09/12/python-async.html
这两篇内容是在我开始深入研究之前不久发布,非常及时。
从中,我们学到的几点经验:
Python 没有原生的异步文件 I/O。
Django 仍然没有完全支持异步。ORM 的异步还没做完,colored functions 问题在这里非常明显。理论上可以用 Django 写异步,但官方文档里有太多限制和警告,几乎会吓退任何人。
-
你到处都得写 sync_to_async 和 async_to_sync。
Python 生态中出现了各种模型来改善异步支持,但它们不是原生的,各有局限。例如,aiofiles 提供异步文件操作接口,但底层使用了线程池;Gevent 的 greenlets 很酷,但它实际上是对标准库进行了补丁才能工作。
由于很多 Python 异步支持依赖于语言之上的层,而非原生,你写的异步代码要非常小心,否则会受到运行环境的影响,例如 Gunicorn 的 worker 类型(顺便说一句,从 Gunicorn 文档里很难学到这些)。
总的来说,仅仅想实现一个类似 Promise.all 的功能,同时还要理解它所有的坑,根本不是件简单的事。
面对这个问题,我回到了 PostHog 的代码库看看。
我之前在 PostHog 工作了三年,当时 Django 代码库里完全没有异步,但他们是大公司,现在还加入了 AI 功能,他们肯定已经解决了这些问题!
结果我发现,他们仍然运行的是 WSGI(不是 ASGI),用的是 Gunicorn Gthread workers(最大并发请求通常是 CPU 核心数的 4 倍),所以从异步运行中几乎没有获得多少好处。代码库里有很多工具来让异步正常工作,比如他们自己实现的 async_to_sync。所以我猜,他们处理大量负载的方式可能还是水平扩展。
总结一下,Django 没有一个真正优秀的异步解决方案。

那接下来怎么办?
我们基本上得出结论:Django 很快就会成为我们的瓶颈,不只是当负载很大时才会出现问题。
即使用户不多,我们也已经需要开始运行多台机器来避免延迟太高,而且还得写很多笨重、难维护的代码。
当然,我们也可以暂时“做那些不易扩展的事情”,用钱(或者 AWS 额度)解决问题,但感觉不对。而且现在阶段还早,把后端迁移到另一个框架反而会更容易。
这时候,有人可能会建议道:“直接用 FastAPI 吧!”——事实上我们确实考虑过。
FastAPI 支持真正的异步,而且很受欢迎,性能据说不错。如果你想搭配 ORM,可以用 SQLAlchemy,它也支持异步。
迁移到 FastAPI 可能会帮我们省一两天时间(我们的迁移花了 3 天),因为很多代码可以直接复用,无需迁移。但到这个时候,我们对 Python 异步生态整体并不太有信心,而且我们其实已经用 Node 写好了后台 worker 服务,所以我们觉得这是一次机会——干脆全力投入一个生态系统。
于是,我们选择了迁移到 Node。我们花了一点时间挑选框架 + ORM 的组合,最终决定用 Express + MikroORM。
是的,Express 有些老,但经过验证很可靠,而且用起来很熟悉。反正我们迁移的重点就是JS 事件循环。

收获与失去
收获:效率
初步基准测试显示,我们的吞吐量提升了大约 3 倍,而且这只是把主要是顺序执行的代码放到异步环境里。现在用 Node 后,我们计划在分块、嵌入、重排序等环节做大量并发处理。这意味着,随着时间推移,这次改动的回报会更大。
失去:Django
失去 Django 让人心痛,而且我们已经发现,在 Express 端需要自己写更多中间件和工具。Node 里也有功能更全面的框架,比如 Adonis,但迁移到一个全新的生态对我们来说工作量太大,所以还是选择了一个最小化的方案。
我最怀念的是 Django 的 ORM,它真的很人性化。虽然在追求极致性能时,ORM 总得小心使用,但 Django ORM 在底层做了很多优化,让你可以在 Python 里写查询而仍有不错性能。我们在把 Django 模型迁移到 MikroORM 实体时,也学到了一些这方面的经验。
收获:MikroORM
MikroORM 是这次迁移中的一个安慰奖。虽然我仍然更喜欢 Django ORM,但不同生态需要不同工具。
以前我从未使用过 MikroORM,但惊喜地发现,它有类似 Django 的懒加载机制,迁移设置感觉比 Prisma 更好,同时 API 也相对人性化(前提是你手动把基础设施搭好)。
总体来说,我们刚开始使用 MikroORM,但目前很高兴在它和原本的 Prisma 之间选择了 MikroORM。
失去:Python 生态
这个其实不用多解释。虽然大多数构建 RAG(Retrieval-Augmented Generation)和智能体的工具都有 Python 和 TypeScript SDK,但 Python 仍然是首选,这里我们只是说 API 封装层。
一旦你想自己深入做 ML,Python 根本无可匹敌。我猜随着我们项目复杂度增加,最终可能还是会有一个 Python 服务,但现在我们还没这个需求。
收获:统一的代码库
我们一直意识到迁移到 Node 意味着我们会有两个 Node 服务,而不是一个 Python 服务加一个 Node 服务。但直到有一天,我们才意识到其实可以把代码库合并,这会非常有帮助。
Node worker 和 Django server 之间有很多重复逻辑,现在我们把 Express server 和后台 worker 统一到了同一个代码库,感觉好太多了。它们都能用同一个 ORM(之前 worker 还得用原生 SQL),还能共享大量工具函数。
收获:更好的测试
这并不是 pytest vs jest 的问题,而是为了确保迁移后所有功能正常,我们写了大量测试。同时做了一些重构,这也是一个很棒的附加收获。

我们是怎么做的?
差不多该总结这篇文章了,但这里简单记录一下实际迁移过程:

我们会再做一次吗?
老实说,我们对这个决定非常满意,100% 会再做一次。这不仅在长期会带来回报,实际上现在就已经开始看到好处了。
在这个过程中,我们也学到了很多东西。
如果你想看看实际代码,可以查看以下 PR:
Skald 是一个 MIT 授权的 RAG API 平台:https://github.com/skaldlabs/skald