1762188733
2025-11-03 16:35:00
私たちはとんでもないことをしたのです。立ち上げからわずか 1 週間でバックエンドを Python から Node に完全に書き直しました。
これを行ったのは、スケールできるようにするためです。はい、スケールです。 1週間後。
ある意味、いい時代になったんじゃないでしょうか?コードベースはまだ小さく、ユーザーもそれほど多くありません。
しかしその一方で、これは初期段階のスタートアップに与えられる、ただ出荷して販売し、プロダクトマーケットフィットに達したらスケールを心配するというアドバイスに完全に反します。 「スケールしないことをする」、PGが言ったように。
ご存知のとおり、私たちはユーザーが殺到してスケールを強いられるような魔法のようなローンチ週はありませんでした。そして一般に、選択したスタックは、実際にフレームワークの変更やバックエンドを別の言語 (Rust) で書き直すことを検討する必要がある段階に達するまで、長期間にわたって適切に拡張できるはずであると期待できます。
では、なぜそれをするのでしょうか?
Python の非同期は最悪です
私はジャンゴの大ファンです。で紹介させていただきました ポストホッグ それ以来、ほとんどのプロジェクトで私の頼りになるバックエンドになりました。非常に迅速に作業を開始でき、優れたツールと抽象化が備えられており、ニーズに合わせて調整できる柔軟性も備えています。
当然のことながら、私がバックエンドを書き始めたとき、 ハゲ私も Django から始めました。
現在、Skald では LLM と埋め込み API を頻繁に呼び出しているため、通常は非同期にしたいネットワーク I/O を大量に実行しています。それだけでなく、ドキュメントのさまざまなチャンクに対してベクトル埋め込みを生成する必要がある場合など、多くのリクエストを同時に発行したい場合もよくあります。
そして、Django ではすぐに事態が非常に混乱してしまいました。
前置きとして、私たち二人とも Python 非同期コードを書いた経験はあまりありません (私はほとんどの経験があります) 取り組んだ Node の非同期負荷の高いサービス) ですが、これが部分的にここでのポイントだと思います。 堅牢でパフォーマンスの高い Python 非同期コードを記述するのは非常に難しく、直感的ではありません。そうするためには、すべての基礎を深く掘り下げる必要があります。
実際、私は Python 非同期についての知識を深めるために適切な時間を費やすことに非常に興味がありますが、ここでの文脈では、a) 初期段階のスタートアップとして出荷するために必要な貴重な時間を失い、b) その過程で非常に簡単に足を痛める可能性があります。
それでも、私は自分に責任があると思っていました。 「悪いプログラマー!悪いプログラマー!」すべてを理解しようとしていたとき、頭の中で聞こえていたのです。しかし、より知識のある人は間違いなくより良い時間を過ごすことができるでしょうが、Python 非同期の基礎も実際には少し不安定であることがわかりました。
最初からイベント ループを備えた JavaScript や、ゴルーチンの概念を作成した Go (どちらも私が非常に気に入っており、運用環境で使用している同時実行モデル) とは異なり、Python の非同期サポートは後からパッチされたものであり、そこに問題があります。
これについて詳しく説明している 2 つのブログ投稿は次のとおりです。 「Python には 10 年前から非同期があります。なぜもっと普及していないのですか?」 そして 「Python の同時実行性: gevent は正しく機能していました」、両方とも私がこれらすべてを掘り下げ始める少し前に都合よく出版されました。
私たちに関して言えば、いくつかのことを学びました。
- Python にはネイティブの非同期ファイル I/O がありません。
- Django はまだ完全な非同期サポートを持っていません。 ORM の非同期はまだ完了していないため、色付き関数の問題がここで顕著になります。技術的には Django を非同期で使用できますが、 これに関するドキュメント 注意事項が多すぎるので誰でも怖がるはずです。
- 書かなきゃ
sync_to_asyncそしてasync_to_syncどこでも。 - Python エコシステムのさまざまな部分により良い非同期サポートを提供するために、あらゆる種類のモデルが登場していますが、それらはネイティブではないため、独自の注意事項があります。例えば、 aioファイル 非同期 API 互換のファイル操作を実現しますが、内部でスレッド プールを使用します。 ゲント greenlets を備えたこれは非常にクールですが、動作するために文字通り stdlib にパッチを適用します。
- Python の非同期サポートの多くは、ネイティブではなく言語の最上位にあるレイヤーに依存しているため、実行する Gunicorn ワーカーの種類などに応じて異なる影響を与えるため、作成する非同期コードには注意する必要があります (ところで、非同期コードについては Gunicorn ドキュメントからよく学んでください)。
全体として、次と同等のものを取得するだけです Promise.all しかし、その問題点をすべて理解するのは決して簡単ではありませんでした。
これに直面して、私は PostHog コードベース。
私は PostHog で 3 年間働いていましたが、当時は Django コードベースに非同期はありませんでしたが、彼らは大企業であり、今では AI 機能を備えているので、これを理解したに違いありません。
そして私が気づいたのは、彼らは依然として Gunicorn Gthread ワーカー (通常、処理できる最大同時リクエストは最大 4x CPU コア) で (ASGI ではなく) WSGI を実行しているため、非同期で実行してもあまりメリットが得られないということでした。コードベースには、独自の実装など、非同期を適切に機能させるためのユーティリティも多数あります。 async_to_sync。したがって、彼らが大量の負荷を処理している方法は、おそらく単なる水平スケーリングだと思います。
Django で非同期を実行する優れた方法はありません。
さて、それではどうする?
私たちは本質的に、大量の負荷がかかり始めたときだけでなく、Django がすぐに私たちに害を及ぼすだろうと結論付けました。
ユーザーが多すぎなければ、ひどいレイテンシーを避けるためにすでに複数のマシンを実行し始める必要があり、さらに保守が困難な不格好なコードを記述することになります。
もちろん、現時点では「スケールしないことを実行」し、お金 (または AWS クレジット) で問題を解決することもできますが、それは適切ではないと感じました。そして、これほど早期に実現できれば、別のフレームワークへの移行がはるかに簡単になります。
この時点で、画面に向かって「FastAPI を使え!」と叫んでいる人もいるでしょう。 –そして実際にそれを検討しました。
FastAPI は適切な非同期サポートを備えており、パフォーマンスが高いと言われている非常に人気のあるフレームワークです。 ORM が必要な場合は、非同期もサポートする SQLAlchemy を使用できます。
FastAPI に移行すれば、翻訳せずに多くのコードを再利用できるため、おそらく 1 ~ 2 日の節約ができたでしょう (移行には 3 日かかりました)。しかし、この時点では Python 非同期エコシステム全体について良い感触が得られておらず、実際にバックグラウンド ワーカー サービスをすでに Node で作成していたため、1 つのエコシステムに全力を尽くす良い機会だと考えました。
そこで、Node に移行しました。フレームワーク + ORM の組み合わせを選択するのに少し時間がかかりましたが、Express + MikroORM に落ち着きました。
確かに Express は古いですが、十分な実績があり、親しみやすいものになっています。とにかく、JS イベント ループに到達することが、このすべての主要なポイントでした。
私たちが得たもの、失ったもの
得られた効果: 効率
最初のベンチマークでは、そのままの状態で最大 3 倍のスループットが得られたことが示されています。これは、主にシーケンシャル コードであるものを非同期コンテキストで実行した場合のみです。現在は Node を使用しているので、チャンク化、埋め込み、再ランキングなどの際に多くの同時処理を行うことを計画しています。これは、この変更が時間の経過とともにさらに大きな効果をもたらすはずであることを意味します。
失われた:ジャンゴ
Django を失うのは痛手であり、私たちはすでに Express 側でさらに多くのミドルウェアとユーティリティを自分たちで構築していることに気づきました。 アドニス これは、より完全な機能を備えた Node フレームワークとして存在しますが、まったく新しいエコシステムに移行することは、単に最小限のものを使用するよりも手間がかかるように感じました。
私に最も欠けているのは ORM です。私の意見では、これは非常に人間工学に基づいたものです。可能な限り最高のパフォーマンスを引き出すには、常に ORM に注意する必要がありますが、Django ORM は、Python でクエリを作成するのに十分なパフォーマンスを実現するために、内部でいくつかの優れた機能を備えています。Django モデルを MikroORM エンティティに移行するときに、これについてさらに詳しく学びました。
獲得: MikroORM
MikroORM は、この移行全体において残念な結果となりました。私は今でも Django ORM の方が好きですが、同時に、エコシステムが異なれば、必要なツールも異なります。
私はこれまで一度も使用したことがありませんでしたが、Django のような遅延読み込み、Prisma よりもはるかに優れた移行設定、そして (基礎を手動で正しく設定すれば) 適度に人間工学に基づいた API を見つけて本当に驚きました。
全体として、私たちはこの変更の初期段階にありますが、現時点では、既存の Prisma ではなく MikroORM を選択したことに満足しています。
失われた: Python エコシステム
これは一目瞭然だと思います。 RAG およびエージェントを構築するためのほとんどのツールには Python および TypeScript SDK が含まれていますが、依然として Python が優先されており、ここでは API ラッパーについてのみ説明します。
実際に自分で ML に取り組みたいと思ったら、競合するものはありません。もっと洗練されれば最終的には Python サービスが使えるようになるのではないかと思いますが、今のところは大丈夫です。
獲得: 統一されたコードベース
Node に移行すると、Python サービスと Node サービスの代わりに 2 つの Node サービスが必要になることは常に認識していましたが、実際にコードベースをマージできること、そしてそれが非常に役立つことになるとは、ある日まで思いつきませんでした。
Node ワーカーと Django サーバーの間には多くの重複ロジックがありましたが、Express サーバーとバックグラウンド ワーカーを 1 つのコードベースに統合したので、かなり改善されました。両方とも ORM を使用できるようになり (以前はワーカーが生の SQL を実行していました)、多数のユーティリティを共有できます。
成果: テストが大幅に改善されました
これは、 pytest 対 jest 移行後にすべてが期待どおりに動作することを確認するために、さらに大量のテストを作成しただけです。これといくつかのリファクタリングは、歓迎すべき副次的利点でした。
どうやってやったのか
そろそろこの投稿を終える時期が来たと思いますが、実際の移行プロセスについて簡単にメモしておきます。
- 3日かかりました。
- 私たちは最後の部分まで AI コード生成をほとんど使用しませんでした。新しいセットアップの基礎、特に新しい ORM の内部動作をよく理解することが重要だと感じました。すべての基礎を理解したら、Claude Code は、重要性の低いエンドポイントのコードを生成するのに非常に役立ち、コードベースで問題をスキャンするのにも役立ちました。
- 何度も辞めそうになりました。新しい機能を求める顧客のリクエストがあり、Django コードにいくつかのバグがあったため、顧客にサービスを提供する代わりに移行に時間を無駄にしているように感じていました。
もう一度やろうか?
正直に言うと、私たちはこの決定に非常に満足しており、100% もう一度同じことをするつもりです。これは長期的に利益をもたらすだけでなく、現在すでにその成果を上げています。
私たちもその過程で多くのことを学びました。この記事全体の要点が、誰かが私たちに愚かだから X か Y をやるべきだったと言ってくれたり、Python の非同期がどのように機能するかを教えに来てくれたりすることであれば、それは正直に言って素晴らしいことです。私としては、Python 非同期についての経験が浅いことを喜んで認識しており、それについてさらに学ぶことができれば、それは勝利です。
実際にコードを見てみたい場合は、次の PR をチェックしてください。
ハゲ は MIT ライセンスの RAG API プラットフォームです。そのため、何かご意見や懸念がある場合は、こちらまでご連絡ください。 GitHub、または PR を開いてバックエンドを選択したフレームワークに書き換えます:D
#Python #から #Node.js #に移行した理由