なぜ私はExpressをやめてFastifyに移行したのか?Node.jsのスループットを3倍にする秘訣

Development tutorial - IT technology blog
Development tutorial - IT technology blog

Express – 安全な選択肢か、それともパフォーマンスの壁か?

ExpressでREST APIを構築するのは非常に簡単です。巨大なコミュニティとインターネット上に溢れるドキュメントのおかげで、Node.jsエンジニアが真っ先に思い浮かべる名前でしょう。しかし、トラフィックが秒間数千リクエストに達すると、それまで気づかなかったパフォーマンスの「亀裂」がExpressに見え始めます。

Expressの致命的な弱点はオーバーヘッドにあります。正規表現(Regex)ベースのルーティング機構は動作が遅く、さらにExpressはJSONシリアライズの最適化をサポートしていません。サーバーがデータを返すたびに、Node.jsはオブジェクト全体に対して手動でJSON.stringify()を実行する必要があります。これはCPUリソースを激しく消費し、オブジェクトが巨大な場合にはイベントループ(Event Loop)を停滞させる原因となります。

何晩もミドルウェアの最適化に費やしても結果が振るわなかったため、私はFastifyを試すことにしました。その結果は驚くべきものでした。スループット(Throughput)が急上昇し、レイテンシ(Latency)は最小限まで抑えられたのです。

なぜFastifyはこれほどまでに速いのか?

Fastifyは単なるWebフレームワークではありません。最初の1行から「パフォーマンス第一」の思想で設計されています。以下に、Expressを凌駕する2つの秘密兵器を紹介します。

1. Radix Treeによるルーティング機構

Expressのように正規表現でルートリストを走査する代わりに、Fastifyは**Radix Tree**(find-my-wayライブラリ経由)というデータ構造を使用します。ルートの検索時間はパスの長さにのみ依存(O(L))し、エンドポイントが10個でも1000個でも検索速度は変わりません。複雑なマイクロサービスにおいて、これは速度面での大きな飛躍となります。

2. シリアライズ:Schemaによる高速化

これは私が最も気に入っている機能です。Fastifyはfast-json-stringifyライブラリを使用します。レスポンス送信時になってデータを変換するのではなく、事前に**JSON Schema**を定義しておきます。このスキーマから、オブジェクトを文字列に「鋳造」するための専用関数が生成されます。このアプローチは、通常のJSON.stringify()よりも2〜3倍高速です。

Fastifyによる実践的なREST APIの実装

おなじみのコマンド数行ですぐに始められます:

mkdir fastify-api-demo
cd fastify-api-demo
npm init -y
npm i fastify

エンドポイントの定義方法を見てみましょう。コード構造の中にスキーマが組み込まれているのがわかります:

const fastify = require('fastify')({ logger: true });

// レスポンスを最適化するためのスキーマ定義
const userSchema = {
  response: {
    200: {
      type: 'object',
      properties: {
        id: { type: 'integer' },
        name: { type: 'string' },
        email: { type: 'string' }
      }
    }
  }
};

fastify.get('/user/:id', { schema: userSchema }, async (request, reply) => {
  // ここにDBからデータを取得するロジックを記述
  return { id: request.params.id, name: 'ItFromZero', email: '[email protected]' };
});

const start = async () => {
  try {
    await fastify.listen({ port: 3000 });
    console.log('サーバーがポート3000で起動中');
  } catch (err) {
    fastify.log.error(err);
    process.exit(1);
  }
};
start();

レスポンスのJSON構造を確認したり、スキーマを綺麗に整形したりするには、toolcraft.appをよく使います。肥大化しがちなVS Codeに拡張機能を追加するよりも手軽で便利です。

プラグインシステム:分割して統治せよ

Expressを使う際によくある間違いは、すべてをapp.jsファイルに詰め込んでしまうことです。Fastifyは**プラグインシステム(Plugin System)**によってこの問題を根本的に解決します。Fastifyでは、ルート、データベース接続、エラーハンドリングに至るまで、すべてがプラグインとして扱われます。

fastify-pluginの仕組みにより、ロジックを完璧にカプセル化できます。特定のスコープ(Scope)でプラグインを登録すれば、その変数やデコレータがアプリケーションの他の部分を「汚染」することはありません。コードは非常にクリーンで保守しやすくなります。

バリデーション:入り口で不正データを遮断

バリデーションはシステムのボトルネックになりがちですが、Fastifyは**Ajv**を標準搭載して入力データをチェックします。これはセキュリティだけでなく、データ構造が明確に定義されることでNode.jsのメモリ最適化にも寄与します。

例えば、POSTリクエストのボディを検証する場合:

const postSchema = {
  body: {
    type: 'object',
    required: ['title', 'content'],
    properties: {
      title: { type: 'string', minLength: 5 },
      content: { type: 'string' }
    }
  }
};

fastify.post('/posts', { schema: postSchema }, async (request, reply) => {
  return { status: '記事が正常に作成されました!' };
});

クライアントがtitleを忘れた場合、Fastifyは詳細なメッセージと共に自動的に400エラーを返します。追加の検証ロジックを1行も書く必要はありません。

実際の数値:Fastify vs Express

MacBook M1 (16GB RAM) 上で autocannon を使用してベンチマークを実行しました。テストシナリオは、単純なJSONを返すエンドポイントです。

  • Express: 約12,000 req/s、平均レイテンシ 15ms。
  • Fastify: 約32,000 req/s、平均レイテンシ わずか4ms。

結果、FastifyはExpressの約3倍のリクエストを処理できました。本番環境(Production)では、これはサーバー費用(CPU/RAM)の大幅な削減を意味します。

結論:いつFastifyへ「飛び込む」べきか?

Fastifyは非常に強力ですが、何が何でも移行すべきというわけではありません。

Expressを選ぶべきケース:

  • 小規模プロジェクトでプロトタイプを極めて迅速に作成する必要がある場合.
  • チームがExpressに習熟しており、新しく学習する時間がない場合。
  • Expressにしかない特定のミドルウェアに依存している場合。

Fastifyを選ぶべきケース:

  • 最高のパフォーマンスが求められるマイクロサービスを構築する場合。
  • 限られたハードウェアリソースで大量の負荷を処理する必要がある場合。
  • 厳格な構造を保ち、バリデーションを強制することでバグを最小限に抑えたい場合。

ミドルウェアの考え方が似ているため、移行自体はそれほど難しくありません。しかし、Fastifyがもたらすスピードと安定性のメリットは、エンジニアが体験してみる価値が十分にあります。

Share: