「Node.jsは非同期イベント駆動だから、そのままWebサーバーとして外に公開すれば最速ですよ」
数年前、ある現場のアーキテクチャレビューでジュニアエンジニアが放ったその一言を、今でも苦々しく思い出す。画面共有された構成図には、インターネットからのトラフィックをポート80/443でダイレクトに受けるnode app.jsの文字。背筋に冷たいものが走った。
現場叩き上げのインフラ屋やバックエンドエンジニアなら、この構成がいかに自殺行為であるかが即座に理解できるはずだ。しかし、チュートリアルをなぞっただけの人間は「Node.jsでWebサーバーが書ける=Node.jsはWebサーバーである」という致命的な勘違いをやらかす。
Node.jsはサーバーサイドJavaScriptの実行環境としては極めて優秀だ。非同期I/Oとシングルスレッドのイベントループにより、I/O待ちの多いAPI処理では無類の強さを発揮する。
だが、言っておく。Node.jsをそのままWebサーバーとして最前線に晒すのは愚行中の愚行だ。
そして、その脆弱な思想のまま「リアルタイム通信がしたい」とWebSocketの数万コネクションを自前で抱え込もうとした瞬間、インフラと運用の現場には本当の地獄の釜が開くことになる。
Apache or Nginxを挟まないという狂気:なぜWebサーバーが必要なのか
なぜ、Node.jsの手前にApacheやNginxを置かなければならないのか。その理由は「Webサーバー」と「アプリケーションランタイム」の責務の違いにすべて集約される。
インターネットの最前線に立つということは、悪意あるスローなHTTPリクエスト(Slowloris攻撃など)、巨大なペイロード、途切れ途切れのパケット、SSL/TLSハンドシェイクの暗号化オーバーヘッド、そして突発的な大量アクセスといった「泥水」を一身に浴びることを意味する。
C言語で極限まで最適化され、数十年の実戦で鍛え上げられたApacheやNginxは、この泥水を高速にバッファリングし、無害化し、効率的に振り分ける専用の防波堤だ。
もしNode.jsを最前線に直置きした場合どうなるか。シングルスレッドで動くNode.jsのイベントループが、巨大リクエストの受信待ちやSSL計算といった低レベルな処理にCPUリソースを奪われ、本来実行すべきビジネスロジックの処理が数珠つなぎにブロックされる。1つの重いリクエストや例外エラーの未ハンドリングによってプロセス全体がクラッシュすれば、その瞬間にWebサイト全体が沈黙する。
「構成はApache or Nginx → Node.jsと知れ」
これは単なるお作法ではない。最前線で何万もの生のリクエストを捌くWebサーバーの壁を挟み、Node.jsにはバックエンドで純粋な処理だけに専念させる。この最低限の境界線すら引けない設計は、本番環境に投下した瞬間に大量アクセスの波に呑まれて即死する。
ステートフルの罠:数万のWebSocketコネクションが引き起こす物理的破滅
Webサーバーを正しく前段に置いたとしても、WebSocketの運用にはもう一段深い「ステートフルの壁」が立ちはだかる。
HTTPの世界は「ステートレス」だ。リクエストを受けてレスポンスを返せば、コネクションは即座に解放される。サーバーを増やしたければロードバランサーの後ろにインスタンスを並べるだけで水平分散(スケールアウト)が成立する。
しかし、WebSocketはクライアントとサーバーの間に物理的なTCPコネクションを張り続ける「ステートフル」な世界だ。これが数万コネクション規模になった瞬間、運用は物理限界との戦いに変わる。
第一に、メモリとファイルディスクリプタ(FD)の枯渇だ。OSにとってソケット接続は1つのファイルであり、コネクションを維持するだけでカーネルバッファとプロセスメモリが確実に消費される。デフォルト設定のLinuxなど一瞬で「Too many open files」を吐いて死ぬ。
第二に、スケールアウトの破綻だ。サーバーAに繋がっているユーザーと、サーバーBに繋がっているユーザーの間でメッセージを中継するためには、背後にRedisのPub/SubやRabbitMQといったメッセージブローカーを配置しなければならない。結局、分散させたはずの負荷がバックプレーンに集中し、そこが新たな単一障害点(SPOF)となってシステム全体を巻き込んでボトルネック化する。
そして最も現場を凍りつかせるのが、「デプロイと再起動」の瞬間だ。コードを1行修正してサーバーをローリングアップデートしようとした瞬間、そのサーバーが抱えていた数万のWebSocket接続が一斉に切断される。切断されたクライアントは何をするか。一斉に再接続を試みるのだ(Reconnection Storm)。数万の再接続リクエストが瞬時に残りのサーバーへ雪崩れ込み、連鎖的に過負荷でノックダウンしていく。この阿鼻叫喚を一度でも経験した人間は、自前でWebSocketサーバーを運用することに恐怖を覚えるようになる。
自前で抱えて死ぬな:AWS Lambdaとマネージドへ逃げろ
現場で泥をすすってきたエンジニアとして、はっきり言おう。
自前でEC2やVPSを借り、NginxとNode.jsを組み合わせ、RedisでPub/Subを繋ぎ、数万のコネクション維持とスケールアウトに怯えながら24時間の監視当番に追われるくらいなら、最初から自前で作るのをやめろ。
AWSを使うなら、WebSocketのコネクション維持はAPI Gatewayに丸投げし、メッセージの処理はバックエンドのAWS Lambdaに任せる構成を取るべきだ。
あるいは、GraphQLサブスクリプションを扱えるAppSyncや、マネージドなメッセージングサービスを組み込む。クライアント数万件の物理的なTCPコネクション維持、SSL終端、接続の切断・再接続の嵐を捌く責務は、すべてクラウド側のインフラに押し付ければいい。
Lambdaを使えば、イベントが発生した瞬間だけマイクロ秒単位でコンピュートが走り、用が済めば消える。ステートフルの呪縛から解放され、再びステートレスな世界でスケールさせることができるのだ。
もちろん、マネージドサービスにはコストの問題やコールドスタートの課題がある。だが、深夜にOOM KillerでNode.jsが叩き落され、全ユーザーの接続が吹き飛んで障害対応の電話で叩き起こされるコストと天秤にかければ、答えは明白だ。
技術の選定とは、「自分たちがどの地獄なら引き受けられるか」の選択である。Node.jsの特性を見誤り、構成をケチり、自前運用の泥沼に沈む前に、インフラの境界線をどこに引くかを冷静に見極めなければならない。

