11月4日、Yakko Majuri氏が「Why we migrated from Python to Node.js」と題したブログ記事を公開し、話題を呼んでいる。この記事では、ローンチからわずか1週間でバックエンドをPython(Django)からNode.js(Express+MikroORM)へ全面移行した理由と、その得失について詳しく紹介されている。以下に、その内容を紹介する。
概要
この記事の背景としては、Pythonで書いたプロダクトを、ローンチから間もなくNode.jsに移行したという著者の経験がある。初期コードベースが小さい段階であっても、将来的なスケールを見据え、非同期処理を中心に据えた運用が不可欠と判断しての移行である。
一般論としては「スケールはPMF後に考えればよい」と言われており、多くの場合「あり得ない」決断であるが、Djangoにおける非同期まわりの複雑さと実行モデル上の制約が、早い段階でレイテンシと保守性のボトルネックになると見積もったことが、この意思決定を後押ししたという。
Python非同期処理の難しさ
筆者はDjangoに親しみがあり、起点としてDjangoを選んだ。しかし、LLM呼び出しや埋め込み生成などI/O中心のワークロードを安全かつ直感的に非同期化するのが難しかったと述べる。背景として、Pythonの非同期機構が言語後付けであり、エコシステムが多層化していることを挙げる。記事では以下の学びが列挙される。
- Pythonはネイティブな非同期ファイルI/Oを持たない。
- Djangoは依然として完全な非同期対応ではない。特にORMのasync対応は未完で、いわゆる「色付き関数」問題が表面化する。公式ドキュメントにも多くの但し書きが並ぶ。
sync_to_asyncやasync_to_syncの呼び出しが至る所に出現しがちである。- 非同期対応を補うライブラリは存在するが、標準ではないがゆえの注意点が多い(例:
aiofilesは内部的にスレッドプール、Geventはstdlibをモンキーパッチ)。 - アプリの非同期挙動は、Gunicornのワーカ種別など「言語より上の層」の選択に左右されやすい。
また、PostHogのコードベース調査ではWSGI+gthreadで水平スケールしている状況が見られ、Djangoでの本格的なasync運用の難度を再確認したという。結論として「Djangoでasyncをうまく回す決定打がない」という評価である。
FastAPIではなくNode.jsを選んだ理由
選択肢としてFastAPI(+SQLAlchemy)も検討したが、Python asyncエコシステム全体への不信感と、すでにNodeでワーカーサービスを持っていた事情から、イベントループが言語の核にあるJavaScriptへ集約する方針を採った。フレームワークは実績が豊富で馴染み深いExpressを選択し、ORMはMikroORMを採用した。
得たもの/失ったもの
得たもの: パフォーマンス
初期ベンチマークでは、ほぼ逐次的なコードをasync文脈で動かしただけでもスループットが約3倍になったという。今後はチャンク分割、埋め込み、リランキングなどで積極的に並行処理を取り入れ、さらなる効果を見込む。
失ったもの: Django
Djangoを失う痛みは大きい。Express側ではミドルウェアやユーティリティを自作する場面が増えた。特にDjango ORMの使い勝手は恋しく、移行時にMikroORMへモデルを写経する中で、Django ORMが内部で行う最適化の妙を再認識したと述べる。
得たもの: MikroORM
MikroORMは「慰め賞」以上の価値があったという評価である。Djangoに似た遅延ロード、Prismaより好感の持てるマイグレーション体験、土台を手で整えれば扱いやすいAPIなどを利点に挙げる。現時点ではPrismaよりもMikroORMを選んで満足している。
失ったもの: Pythonエコシステム
RAGやエージェント系SDKはTypeScript対応も進んだが、Pythonが優先される現実は変わらない。将来的にMLを自前実装する段になれば、専用のPythonサービスを併設する可能性は高いと見ている。ただし現段階ではNodeに一本化して問題ないという判断である。
得たもの: コードベースの統合
サーバ(Express)とバックグラウンドワーカーを単一のリポジトリに統合できた効果は大きい。重複ロジックが解消され、両者で同じORMとユーティリティ群を共有できるようになった。以前はワーカーが生SQLを発行していたが、それも統一された。
得たもの: テストの充実
pytest対jestの優劣ではなく、移行の完全性を担保するためにテスト量を大幅増強したことが副次的な成果となった。
どのように移行したか
実際の移行作業は非常にシンプルなものだった。
- 作業期間は3日。
- 基盤の理解を優先し、最後の細部以外はAI生成に頼らなかった。土台が固まってからはClaude Codeで末端エンドポイントの実装や全体スキャンを補助。
- 顧客要望や既存不具合への対応に追われ、何度も断念しかけた。
再び同じ選択をするか
筆者は「100%同じ決断を繰り返す」と述べる。長期的な投資であると同時に、現時点でも効果が出ている。Python asyncに対する自身の知識不足も認めつつ、学びの機会が得られたことをポジティブに捉えている。コードはGitHubで公開されており、関心があればPRで別フレームワーク版に書き換えても構わないというオープンな姿勢である。
詳細はWhy we migrated from Python to Node.jsを参照していただきたい。