Node.js 24とFastify v6 — なぜfast-json-stringifyを捨てたのか

Maru

@maru

Node.js 24와 Fastify v6 — 왜 fast-json-stringify를 버렸을까

Node.js 24とFastify v6 — なぜfast-json-stringifyを捨てたのか

Fastify v6は、長年アイデンティティであった独自のシリアライズエンジンであるfast-json-stringifyを果敢に削除しました。Node.js 25に搭載されたV8エンジンのネイティブJSON.stringifyの性能が劇的に向上し、複雑なスキーマコンパイルを介さずとも圧倒的な速度を実現できるようになったためです。これによりFastifyは3,000行を超えるコードを削減してフレームワークを劇的に軽量化しつつ、最新ランタイムの最適化の恩恵をそのまま享受できるアーキテクチャへの転換を成し遂げました。

Node.js 24と25で起きたV8 JSON.stringifyの最適化

WebサーバーやAPIサーバーの開発において、CPU演算時間を最も消費する主犯は常にJSONシリアライズでした。私たちが愛用していたFastifyも、このシリアライズのボトルネックを解消するために、開発者が定義したスキーマを事前にコンパイルして文字列として組み立てる「fast-json-stringify」という強力なサードパーティ製エンジンを独自に組み込んで活用していました。

しかし、Node.js 25に搭載されたV8 13.8エンジンをはじめとする最新ランタイムの最適化により、このパラダイムが完全に覆されました。複雑なコンパイルライブラリを駆使せずとも、ネイティブのJSON.stringifyだけで従来のカスタムコンパイルエンジンと同等、あるいはそれ以上の性能を出せるようになったためです。この驚異的なアーキテクチャの改善は、いくつかの巧妙な最適化メカニズムを統合した結果です。

  • 副作用のない高速パス: ゲッターやプロキシ、あるいはカスタムtoJSONメソッドのように、シリアライズ過程でエンジン動作の流れを妨げる副作用のない一般的なオブジェクトを検出し、複雑なバリデーションループを回避する専用の超高速パスを実行します。
  • SIMDベースの文字列エスケープ: 文字列内の特殊文字やエスケープが必要な文字を処理する際、CPUレジスタレベルで複数のバイトを一度にスキャンし、並列でエスケープ処理を行います。
  • ドラゴンボックス(Dragonbox)アルゴリズム: 数値をテキストに変換するのに最適化された超高速浮動小数点変換アルゴリズム「ドラゴンボックス」を全面導入し、数値のシリアライズ速度を飛躍的に向上させました。
  • セグメントバッファシステム: 大きな文字列を作成するためにメモリの再割り当てと解放を繰り返す代わりに、小さなセグメント形式の事前確保された一時バッファに結果を順次書き込み、最後に一括で組み立てることでガーベジコレクションの負荷を完全に軽減しました。
  • 隠しクラス(Hidden Class)メタデータの連携: V8エンジン固有の隠しクラス情報をシリアライズパイプラインに連携し、オブジェクトの属性や形状が変化しない構造を事前に把握することで、実行時にプロパティ構造を検索する不要なコストを排除しました。

これにより、開発者は複雑な最適化ライブラリを追いかけ、メンテナンスリソースを浪費する必要がなくなりました。Node.jsのバージョンを上げるという単純なインフラ作業だけで、バックエンドの最も深刻な演算負荷をランタイム標準のエンジンによって綺麗に解消できるようになったのです。

Fastify v6の大胆な決断:3,000行のカスタムエンジンを削除

Fastify v6が3,000行を超えるカスタムシリアライズエンジンを削除した理由は、最新のNode.js環境において複雑な実行時コンパイル方式を維持するメリットがなくなったからです。Fastify開発チームはGitHubのプルリクエスト#6507を通じて、従来のfast-json-stringifyライブラリへの依存関係を完全に排除しました。これにより、性能向上のために甘受しなければならなかったアプリケーション起動時のコンパイルオーバーヘッドと、巨大なカスタムエンジンのメンテナンス負荷が一度に解消されました。

このような構造的変化が、単なるコード整理を超えて高性能を維持できる秘訣は、Node.js 25環境でのベンチマーク結果に如実に表れています。以下は、ネイティブシリアライズと従来のカスタムコンパイラの処理量を直接比較した数値です。

데이터 유형 (초당 처리 횟수)네이티브 JSON.stringifyfast-json-stringify결과 분석
一般的な配列15,8398,637ネイティブが約1.83倍高速
巨大な配列585354ネイティブが約1.65倍高速
標準オブジェクト7,930,6407,585,403ネイティブが約4.5%高速
長い文字列23,29122,348同等の性能
短い文字列9,823,44713,496,065既存エンジンの優勢
日付データ661,0031,244,898既存エンジンの優勢

実際のプロダクションAPI環境で頻繁にやり取りされる大容量の配列や標準オブジェクトフォーマットでは、むしろネイティブAPIの性能が既存のエンジンを上回りました。スキーマのプリフォーマットにより日付データや非常に短い文字列構造では従来のエンジンが優位な領域も存在しますが、フレームワークが抱える複雑なコードのメンテナンスコストに比べれば、そのメリットは大幅に低下しました。結局のところ、ランタイム自体の高度化がサードパーティ製ライブラリの人工的なチューニングを凌駕し始めたのです。

シリアライズの分離とAjvベースのレスポンススキーマ検証パターン

Fastify v6では、レスポンススキーマはもはやシリアライズを加速させたり、出力データをフィルタリングしたりする役割を担いません。シリアライズはV8エンジンの超高速ネイティブJSON.stringifyが担当し、開発者が宣言したレスポンススキーマは、Ajvエンジンを通じた選択的なデータ検証の目的でのみ機能します。二つの役割が完全に分離されたことでアーキテクチャはより明確になりましたが、既存の利用方法を変更する際には必ず注意すべき点があります。

以前のバージョンでは、レスポンススキーマに定義されていないプロパティは、シリアライズ過程で自動的に除外されていました。しかしv6からはネイティブシリアライズを使用するため、スキーマに存在しないフィールドもフィルタリングされずにそのままクライアントに露出します。機密データの漏洩を防ぐには、レスポンスオブジェクトをビジネスロジック層で直接クリーニングするか、明示的にレスポンス検証段階を有効にする必要があります。

以下は、Fastify v6でレスポンススキーマを使用する簡単な例です。

javascript
fastify.get('/user', {
  schema: {
    response: {
      200: {
        type: 'object',
        required: ['id', 'name'],
        properties: {
          id: { type: 'integer' },
          name: { type: 'string' }
        }
      }
    }
  }
}, async (request, reply) => {
  // v6 주의: 스키마에 없는 'role' 필드가 필터링되지 않고 그대로 출력됩니다.
  return { id: 1, name: '마루', role: 'admin' };
});

このようにレスポンス検証とシリアライズを切り離すことで、開発者は必要な場所にのみ選択的に検証コストを支払うことができるようになりました。性能最適化を必要としない内部APIであれば検証を完全に省略し、レスポンス速度を最大限に引き上げることが可能です。

今すぐ準備すべきマイグレーション戦略

Node.jsのバージョンを最新のLTS以上に引き上げるだけでも、複雑なアーキテクチャ変更なしにシリアライズ過程のCPUリソースを大幅に削減できます。これからはカスタム最適化ライブラリに依存するよりも、ランタイム標準仕様のエンジンレベルの最適化を積極的に活用すべき時期です。Fastify v6の導入を控えているのであれば、従来のスキーマフィルタリング動作に依存していたコードを特定し、ネイティブシリアライズの仕様に合わせてオブジェクト構造を明確に整理しておく先制的なリファクタリングを推奨します。


参考リンク