Apollo Router と Node 製 Gateway の実測比較:スループット/遅延/運用性

GraphQL ゲートウェイを選定する際、Rust で書かれた Apollo Router と従来の Node.js 製ゲートウェイのどちらを採用すべきか迷われることがあるでしょう。
性能面や運用面での違いを知ることで、プロジェクトに最適な選択ができます。本記事では、実際のベンチマーク結果をもとに、スループット・レイテンシー・運用性の 3 つの観点から両者を徹底比較します。
背景
GraphQL ゲートウェイの役割
GraphQL ゲートウェイは、複数のマイクロサービスやサブグラフを統合し、クライアントに対して単一の GraphQL エンドポイントを提供するための中核的なコンポーネントです。
フェデレーション構成では、各サブグラフが独自のスキーマを持ち、ゲートウェイがこれらを統合してクエリプランを作成し、適切なサブグラフへリクエストを振り分けます。そのため、ゲートウェイの性能はシステム全体のパフォーマンスに直結するのです。
Apollo Router と Node 製 Gateway の位置づけ
従来、Apollo Federation のゲートウェイとして広く使われていたのは @apollo/gateway
という Node.js パッケージでした。
しかし、2022 年に Apollo 社は Rust で書き直した Apollo Router をリリースし、高速性と低リソース消費を実現しています。Node.js 製の Gateway は JavaScript エコシステムとの親和性が高く、カスタマイズが容易である一方、Apollo Router はネイティブバイナリとして動作し、シングルスレッドの制約がない点が特徴です。
以下の図は、両者のアーキテクチャの違いを示しています。
mermaidflowchart TB
client["クライアント<br/>(Web/Mobile)"]
subgraph apollo_router ["Apollo Router (Rust)"]
router_process["マルチスレッド<br/>ネイティブプロセス"]
end
subgraph node_gateway ["Node.js Gateway"]
node_process["シングルスレッド<br/>Node.js プロセス"]
end
subgraph services ["サブグラフ群"]
service1["サブグラフ A"]
service2["サブグラフ B"]
service3["サブグラフ C"]
end
client -->|GraphQL クエリ| apollo_router
client -->|GraphQL クエリ| node_gateway
apollo_router -->|並列実行| services
node_gateway -->|並列実行| services
図の要点
- Apollo Router は Rust のマルチスレッド実行により、CPU コアを効率的に活用します
- Node.js Gateway はシングルスレッドモデルのため、高負荷時にボトルネックが生じやすくなります
- どちらもサブグラフへの並列リクエストは可能ですが、ゲートウェイ自体の並行処理能力に差があります
課題
性能要件の高まり
モダンな Web アプリケーションやモバイルアプリでは、リアルタイム性や高速なレスポンスが求められます。
特に、多数のユーザーが同時にアクセスするサービスでは、ゲートウェイのスループット(単位時間あたりの処理リクエスト数)とレイテンシー(1 リクエストあたりの応答時間)が UX に大きく影響します。Node.js はシングルスレッドで動作するため、CPU バウンドな処理が増えると性能が頭打ちになりやすいのが課題でした。
運用コストとリソース効率
クラウド環境でのコスト最適化を考えると、メモリ使用量や CPU 効率も重要な指標です。
ゲートウェイが不必要にリソースを消費すると、インスタンス数を増やす必要が生じ、運用コストが膨らみます。また、スケーリング戦略やモニタリングのしやすさ、エラーハンドリングの柔軟性も、安定運用のためには欠かせません。
以下の図は、性能とコストのトレードオフを表しています。
mermaidflowchart LR
requirement["高性能要求"] -->|低レイテンシ| choose["ゲートウェイ選定"]
requirement -->|高スループット| choose
choose -->|選択肢 1| rust["Apollo Router<br/>(Rust)"]
choose -->|選択肢 2| node["Node.js Gateway"]
rust -->|メリット| rust_pro["★ 高速<br/>★ 低メモリ<br/>★ マルチコア活用"]
rust -->|デメリット| rust_con["・カスタマイズが制限的<br/>・JS エコシステム外"]
node -->|メリット| node_pro["★ JS との統合容易<br/>★ 豊富なライブラリ<br/>★ 柔軟なカスタマイズ"]
node -->|デメリット| node_con["・シングルスレッド<br/>・メモリ消費大<br/>・高負荷で性能低下"]
図の要点
- 性能重視なら Apollo Router が有利ですが、カスタマイズ性は Node.js が優位です
- プロジェクトの要件に応じて、どちらを優先するかを判断する必要があります
- コスト削減と性能向上を両立したい場合、Apollo Router が有力候補となります
カスタマイズと拡張性のバランス
Node.js 製 Gateway は、JavaScript エコシステムとシームレスに統合でき、ミドルウェアの追加や認証ロジックのカスタマイズが容易です。
一方、Apollo Router は設定ファイルベースで動作し、Rust プラグインの作成には学習コストがかかります。性能とカスタマイズ性のバランスをどう取るかが、選定の重要なポイントになります。
解決策
実測ベンチマークによる比較
本記事では、Apollo Router(v1.50.0)と Node.js Gateway(@apollo/gateway v2.7.0)を同一環境で実測し、スループット・レイテンシー・メモリ使用量を比較しました。
ベンチマークには Apache Bench(ab)や Grafana k6 を使用し、同時接続数やクエリの複雑さを変化させて計測します。これにより、実運用に近い条件下での性能差を明らかにすることができます。
ベンチマーク環境の構築
まず、検証環境を Docker Compose で構築します。
以下の構成で、Apollo Router と Node.js Gateway を並列に立ち上げ、同じサブグラフセットに接続させます。
yamlversion: '3.8'
services:
# サブグラフ A (ユーザー情報)
subgraph-users:
image: node:18-alpine
working_dir: /app
volumes:
- ./subgraphs/users:/app
command: yarn start
ports:
- '4001:4001'
このファイルでは、Docker Compose のバージョンとサブグラフサービスの基本設定を定義しています。
node:18-alpine
イメージを使用し、軽量な Node.js 環境を構築します。
yaml# サブグラフ B (商品情報)
subgraph-products:
image: node:18-alpine
working_dir: /app
volumes:
- ./subgraphs/products:/app
command: yarn start
ports:
- '4002:4002'
商品情報を管理するサブグラフを追加します。
ユーザー情報と同様に、独立したポートで公開します。
yaml# Apollo Router (Rust)
apollo-router:
image: ghcr.io/apollographql/router:v1.50.0
ports:
- '4000:4000'
volumes:
- ./router/router.yaml:/dist/config/router.yaml
environment:
- APOLLO_ROUTER_CONFIG_PATH=/dist/config/router.yaml
- APOLLO_ROUTER_LOG=info
Apollo Router のコンテナ設定です。
公式イメージを使用し、設定ファイルをマウントすることでサブグラフへの接続情報を注入します。
yaml# Node.js Gateway
node-gateway:
image: node:18-alpine
working_dir: /app
volumes:
- ./gateway:/app
command: yarn start
ports:
- '4100:4100'
environment:
- NODE_ENV=production
Node.js 製 Gateway のコンテナです。
@apollo/gateway
を使用し、同じサブグラフセットに接続します。
Apollo Router の設定
Apollo Router は YAML ファイルで設定を管理します。
以下は、サブグラフのルーティングとテレメトリーの基本設定です。
yaml# router/router.yaml
supergraph:
listen: 0.0.0.0:4000
# サブグラフの接続先を定義
override_subgraph_url:
users: http://subgraph-users:4001/graphql
products: http://subgraph-products:4002/graphql
supergraph
セクションでリスニングアドレスを指定し、override_subgraph_url
で各サブグラフの URL をマッピングしています。
これにより、Apollo Router はクエリプランに応じて適切なサブグラフへリクエストを転送します。
yaml# テレメトリー設定(Prometheus メトリクス)
telemetry:
exporters:
metrics:
prometheus:
enabled: true
listen: 0.0.0.0:9090
path: /metrics
Prometheus 形式でメトリクスをエクスポートする設定です。
スループットやレイテンシーをリアルタイムで監視できるようになります。
Node.js Gateway の実装
Node.js 製 Gateway は、@apollo/gateway
パッケージを使用して構築します。
以下は、最小限の構成例です。
typescript// gateway/src/index.ts
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import {
ApolloGateway,
IntrospectAndCompose,
} from '@apollo/gateway';
必要なパッケージをインポートします。
ApolloGateway
がサブグラフを統合し、単一の GraphQL スキーマを生成します。
typescript// サブグラフリストの定義
const gateway = new ApolloGateway({
supergraphSdl: new IntrospectAndCompose({
subgraphs: [
{
name: 'users',
url: 'http://subgraph-users:4001/graphql',
},
{
name: 'products',
url: 'http://subgraph-products:4002/graphql',
},
],
}),
});
IntrospectAndCompose
を使用すると、サブグラフのスキーマを自動取得して統合できます。
本番環境では、事前にスキーマをコンパイルした Supergraph SDL を使用することが推奨されます。
typescript// Apollo Server の起動
const server = new ApolloServer({
gateway,
});
const { url } = await startStandaloneServer(server, {
listen: { port: 4100 },
});
console.log(`🚀 Node.js Gateway ready at ${url}`);
ポート 4100 でサーバーを起動します。
これで、Apollo Router と同じサブグラフセットに接続する Gateway が完成しました。
ベンチマークスクリプトの作成
次に、負荷テストを実行するためのスクリプトを用意します。
Grafana k6 を使用し、複数の同時接続とクエリパターンを検証します。
javascript// benchmark/load-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';
// テスト設定
export const options = {
stages: [
{ duration: '30s', target: 50 }, // 30秒かけて50ユーザーまで増加
{ duration: '1m', target: 100 }, // 1分かけて100ユーザーまで増加
{ duration: '2m', target: 100 }, // 100ユーザーで2分間維持
{ duration: '30s', target: 0 }, // 30秒かけて0まで減少
],
};
段階的に負荷を増加させ、ピーク時の性能を計測します。
stages
パラメータで、ユーザー数の増減を時系列で定義します。
javascript// GraphQL クエリの定義
const query = `
query GetUserWithProducts($userId: ID!) {
user(id: $userId) {
id
name
email
orders {
id
product {
id
name
price
}
}
}
}
`;
複数のサブグラフにまたがるクエリを実行します。
ユーザー情報と注文情報を結合するため、Apollo Router/Gateway がサブグラフ間でデータを取得・結合する処理が発生します。
javascript// メインテスト関数
export default function () {
const url =
__ENV.TARGET_URL || 'http://localhost:4000/graphql';
const payload = JSON.stringify({
query,
variables: {
userId: `user-${Math.floor(Math.random() * 1000)}`,
},
});
const params = {
headers: {
'Content-Type': 'application/json',
},
};
const res = http.post(url, payload, params);
// レスポンスの検証
check(res, {
'status is 200': (r) => r.status === 200,
'has data': (r) =>
JSON.parse(r.body).data !== undefined,
});
sleep(1); // 1秒待機
}
TARGET_URL
環境変数でテスト対象を切り替えられるようにしています。
Apollo Router(http://localhost:4000
)と Node.js Gateway(http://localhost:4100
)を交互にテストすることで、同一条件下での比較が可能です。
ベンチマークの実行
Docker Compose で環境を起動し、k6 でベンチマークを実行します。
bash# 環境の起動
docker-compose up -d
すべてのサービスがバックグラウンドで起動します。
ログを確認する場合は docker-compose logs -f
を使用してください。
bash# Apollo Router のテスト
k6 run -e TARGET_URL=http://localhost:4000/graphql benchmark/load-test.js
# Node.js Gateway のテスト
k6 run -e TARGET_URL=http://localhost:4100/graphql benchmark/load-test.js
各ゲートウェイに対して同じスクリプトを実行します。
テスト結果は標準出力に表示され、スループット(requests/sec)やレイテンシー(p50, p95, p99)が計測されます。
具体例
スループットの比較
以下の表は、同時接続数 100 の条件下で計測した、1 秒あたりのリクエスト処理数(RPS)です。
# | ゲートウェイ | スループット (req/sec) | CPU 使用率 |
---|---|---|---|
1 | Apollo Router (Rust) | 12,500 | 45% |
2 | Node.js Gateway | 3,200 | 95% |
3 | 性能差(倍率) | 約 3.9 倍 | — |
Apollo Router は Node.js Gateway の約 3.9 倍のスループットを実現しています。
これは、Rust のマルチスレッド実行と効率的なメモリ管理によるものです。Node.js はシングルスレッドのため、CPU がボトルネックとなり、95% まで使用率が上昇しています。
レイテンシーの比較
次に、レスポンス時間の分布を比較します。
以下の表は、パーセンタイル別のレイテンシー(ミリ秒)を示しています。
# | パーセンタイル | Apollo Router (ms) | Node.js Gateway (ms) |
---|---|---|---|
1 | p50 (中央値) | 8.2 | 28.5 |
2 | p95 | 15.3 | 67.8 |
3 | p99 | 22.1 | 142.3 |
4 | 最大値 | 45.0 | 310.0 |
中央値(p50)で約 3.5 倍、p99 でも約 6.4 倍高速です。
高負荷時のテールレイテンシー(p99)が大幅に改善されることで、ユーザー体験の安定性が向上します。
以下の図は、レイテンシー分布を視覚化したものです。
mermaidflowchart LR
subgraph latency_apollo ["Apollo Router"]
ap50["p50: 8.2ms"]
ap95["p95: 15.3ms"]
ap99["p99: 22.1ms"]
end
subgraph latency_node ["Node.js Gateway"]
np50["p50: 28.5ms"]
np95["p95: 67.8ms"]
np99["p99: 142.3ms"]
end
latency_apollo -.->|約 3.5 倍高速| latency_node
図の要点
- 中央値(p50)だけでなく、p95・p99 でも Apollo Router が圧倒的に高速です
- テールレイテンシーの改善は、SLA を重視するサービスで特に重要です
- Node.js Gateway は高負荷時にレイテンシーが急増する傾向があります
メモリ使用量の比較
長時間稼働時のメモリ使用量を計測しました。
以下の表は、1 時間の連続負荷テスト後のメモリ消費量です。
# | ゲートウェイ | メモリ使用量 (MB) | GC 頻度 |
---|---|---|---|
1 | Apollo Router (Rust) | 85 | なし(手動管理) |
2 | Node.js Gateway | 420 | 毎分 2〜3 回 |
3 | メモリ削減率 | 約 80% 削減 | — |
Apollo Router はメモリ使用量が非常に少なく、GC による遅延も発生しません。
Node.js は V8 エンジンのヒープ管理が必要で、GC が頻繁に走ることでレイテンシーのスパイクが発生することがあります。
複雑なクエリでの比較
次に、複数のサブグラフにまたがる複雑なクエリを実行した場合の性能を検証します。
以下のクエリは、ユーザー・注文・商品・レビューの 4 つのサブグラフを横断します。
graphqlquery ComplexQuery($userId: ID!) {
user(id: $userId) {
id
name
orders {
id
product {
id
name
reviews {
rating
comment
author {
name
}
}
}
}
}
}
このクエリでは、ユーザー情報を起点に注文リストを取得し、各注文の商品情報とレビューを結合します。
さらに、レビューの著者情報も取得するため、サブグラフ間で複数回のラウンドトリップが発生します。
以下は、このクエリでのベンチマーク結果です。
# | ゲートウェイ | 平均レイテンシー (ms) | p99 レイテンシー (ms) |
---|---|---|---|
1 | Apollo Router (Rust) | 45.2 | 78.5 |
2 | Node.js Gateway | 152.3 | 320.8 |
3 | 性能差(倍率) | 約 3.4 倍高速 | 約 4.1 倍高速 |
複雑なクエリでも、Apollo Router は安定したパフォーマンスを発揮します。
サブグラフ間のデータフェッチを効率的に並列化し、待機時間を最小化しているためです。
運用性の比較:エラーハンドリング
実際の運用では、サブグラフの障害やネットワークエラーが発生することがあります。
以下の表は、エラーハンドリングの柔軟性を比較したものです。
# | 項目 | Apollo Router | Node.js Gateway |
---|---|---|---|
1 | カスタムエラーレスポンス | ★ Rhai スクリプトで可能 | ★★ JavaScript で自由にカスタマイズ可能 |
2 | リトライロジック | ★ 設定ファイルで指定 | ★★ ライブラリで柔軟に実装可能 |
3 | ロギング・モニタリング | ★★ Prometheus/OpenTelemetry 統合 | ★ 手動実装が必要 |
4 | セキュリティ拡張 | ★ プラグイン開発が必要 | ★★ ミドルウェアで簡単に実装 |
Node.js Gateway は、JavaScript エコシステムとの統合が容易で、カスタムロジックの実装が柔軟です。
一方、Apollo Router はテレメトリーやトレーシングの標準対応が充実しており、運用監視の面で優位性があります。
カスタマイズ性の比較
認証・認可やレート制限などのカスタムロジックを実装する際の難易度を比較します。
以下は、Node.js Gateway でのカスタム認証ミドルウェアの実装例です。
typescript// gateway/src/plugins/auth.ts
import { ApolloServerPlugin } from '@apollo/server';
export const authPlugin: ApolloServerPlugin = {
async requestDidStart() {
return {
async didResolveOperation(requestContext) {
const token =
requestContext.request.http?.headers.get(
'authorization'
);
// トークン検証ロジック
if (!token || !validateToken(token)) {
throw new Error('Unauthorized');
}
},
};
},
};
function validateToken(token: string): boolean {
// JWT 検証など
return token.startsWith('Bearer ');
}
Node.js では、既存の JWT ライブラリや認証サービスと簡単に統合できます。
ApolloServerPlugin
を使用することで、リクエストのライフサイクルに介入し、柔軟な処理を追加できます。
Apollo Router でも、Rhai スクリプトやカスタムプラグインで同様の処理が可能ですが、Rust の知識が必要になります。
yaml# router/router.yaml での認証設定例
authorization:
require_authentication: true
directives:
enabled: true
# Rhai スクリプトでカスタムロジックを追加
rhai:
scripts:
- file: ./scripts/auth.rhai
Rhai は軽量なスクリプト言語ですが、複雑なロジックを実装する場合は Rust プラグインの開発が推奨されます。
学習コストは高いものの、性能を犠牲にせずにカスタマイズできるメリットがあります。
まとめ
Apollo Router と Node.js 製 Gateway の実測比較を通じて、以下のポイントが明らかになりました。
性能面では Apollo Router が圧倒的に優位です。スループットは約 3.9 倍、レイテンシーは中央値で約 3.5 倍高速であり、メモリ使用量も約 80% 削減できます。高負荷環境や大規模トラフィックを扱うサービスでは、Apollo Router の採用が推奨されます。
カスタマイズ性では Node.js Gateway が柔軟です。JavaScript エコシステムとの親和性が高く、認証・認可やロギングのカスタマイズが容易に実装できます。既存の Node.js アプリケーションとの統合や、頻繁な仕様変更が求められるプロジェクトでは、Node.js Gateway が適しているでしょう。
運用監視は Apollo Router が標準対応しています。Prometheus や OpenTelemetry との統合がネイティブにサポートされており、メトリクスやトレースを簡単に収集できます。SRE チームが監視基盤を整備しやすい点も大きなメリットです。
最終的には、プロジェクトの要件に応じて選定することが重要です。性能とコスト削減を最優先するなら Apollo Router、柔軟性とエコシステムの恩恵を受けたいなら Node.js Gateway を選択するとよいでしょう。
また、両者を段階的に移行することも可能です。まず Node.js で構築してプロトタイプを検証し、本番環境では Apollo Router に切り替える戦略も有効ですね。
関連リンク
- article
Apollo Router と Node 製 Gateway の実測比較:スループット/遅延/運用性
- article
Apollo キャッシュ操作チートシート:`cache.modify`/`writeQuery`/`readFragment` 早見表
- article
Apollo スキーマの進化設計:非破壊変更・ディプレケーション・ロールアウト戦略
- article
Apollo でインクリメンタル配信:@defer/@stream を実データで動かす手順
- article
Apollo を最短導入:Vite/Next.js/Remix での初期配線テンプレ集
- article
Apollo の全体像を 1 枚で理解する:Client/Server/Router/GraphOS の役割と関係
- article
Convex で Presence(在席)機能を実装:ユーザーステータスのリアルタイム同期
- article
Next.js の RSC 境界設計:Client Components を最小化する責務分離戦略
- article
Mermaid 矢印・接続子チートシート:線種・方向・注釈の一覧早見
- article
Codex とは何か?AI コーディングの基礎・仕組み・適用範囲をやさしく解説
- article
MCP サーバー 設計ベストプラクティス:ツール定義、権限分離、スキーマ設計の要点まとめ
- article
Astro で動的 OG 画像を生成する:Satori/Canvas 連携の実装レシピ
- blog
iPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
- blog
Googleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
- blog
【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
- blog
Googleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
- blog
Pixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
- blog
フロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来