Webアプリケーションでは、APIを通じてユーザー認証、コンテンツ取得、データ更新など、さまざまな処理が行われています。
しかし、APIへのリクエスト数を適切に管理しない場合、サーバー負荷の増加やサービス品質の低下につながる可能性があります。
例えば、短時間に大量のリクエストが送信されると、データベースやアプリケーションサーバーの処理能力を超えてしまうことがあります。
こうした問題への対策として利用されるのが、Rate Limiting(レート制限) です。
本記事では、Node.js、Express、Redisを利用してAPIレート制限を実装する方法と、実際のWebサービスで考慮すべきポイントを解説します。
1. APIレート制限とは?
APIレート制限とは、一定時間内に許可するリクエスト数を制限する仕組みです。
例えば、次のようなルールを設定できます。
1ユーザー
↓
60秒間
↓
最大100リクエスト
設定した上限を超えた場合は、HTTPステータスコード429 Too Many Requestsを返します。
レート制限は、主に次の目的で利用されます。
- APIリソースの公平な利用
- 過剰なリクエストによる負荷の軽減
- 自動化された大量アクセスへの対策
- バックエンドサービスの安定性向上
ただし、レート制限だけですべての攻撃を防げるわけではありません。
認証、アクセス制御、監視などと組み合わせることが重要です。
2. レート制限の代表的なアルゴリズム
レート制限には、いくつかの実装方法があります。
Fixed Window
一定の時間枠内でリクエスト数をカウントする方式です。
00:00 - 00:59
最大100リクエスト
01:00 - 01:59
最大100リクエスト
実装が比較的簡単ですが、時間枠の境界付近にリクエストが集中する可能性があります。
Sliding Window
直近の一定時間を基準にリクエスト数を計算する方式です。
Fixed Windowよりも境界付近の集中を抑えやすくなります。
Token Bucket
一定速度でトークンを補充し、リクエストごとにトークンを消費する方式です。
一時的なアクセス増加を許容しながら、長期的なリクエスト量を制御できます。
用途によって適切なアルゴリズムを選択することが重要です。
3. Expressで基本的なレート制限を実装する
まず、Node.jsプロジェクトを準備します。
mkdir api-rate-limit-demo
cd api-rate-limit-demo
npm init -y
npm install express express-rate-limit
次に、Expressアプリケーションを作成します。
server.js
const express = require("express");
const { rateLimit } = require("express-rate-limit");
const app = express();
const limiter = rateLimit({
windowMs: 60 * 1000,
limit: 100,
standardHeaders: "draft-8",
legacyHeaders: false,
message: {
error: "Too many requests"
}
});
app.use("/api", limiter);
app.get("/api/content", (req, res) => {
res.json({
message: "API response"
});
});
app.listen(3000, () => {
console.log("Server running on port 3000");
});
この例では、同じクライアントからのリクエストを60秒間に最大100回まで許可します。
上限を超えると、APIは429レスポンスを返します。
この設定は説明用です。実際の制限値は、APIの用途や利用状況に応じて調整する必要があります。
4. APIごとに異なる制限を設定する
すべてのAPIに同じ制限を設定する必要はありません。
例えば、コンテンツ取得APIとログインAPIでは、求められるセキュリティ対策が異なります。
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
limit: 10,
standardHeaders: "draft-8",
legacyHeaders: false,
message: {
error: "Please try again later"
}
});
app.post(
"/api/login",
loginLimiter,
(req, res) => {
res.json({
message: "Login request received"
});
}
);
ただし、このコードは認証処理そのものを実装したものではありません。
実際のログイン機能では、パスワード検証、アカウント保護、ログ記録なども必要です。
また、IPアドレスだけで制限すると、同じネットワークを利用する複数のユーザーが影響を受ける場合があります。
5. 複数サーバー環境ではRedisを利用する
単一のNode.jsプロセスでは、メモリ上でリクエスト数を管理する方法も利用できます。
しかし、複数のアプリケーションサーバーを利用する場合は問題が発生します。
Load Balancer
↓
┌───┼───┐
↓ ↓ ↓
App1 App2 App3
各サーバーが独立してカウントすると、全体としてのリクエスト数を正確に制御できません。
そこでRedisのような共有データストアを利用します。
Load Balancer
↓
┌───┼───┐
↓ ↓ ↓
App1 App2 App3
└───┼───┘
↓
Redis
これにより、複数サーバー間で制限状態を共有できます。
6. Redisを利用したレート制限
Redisを利用するために、必要なパッケージをインストールします。
npm install redis rate-limit-redis
次に、Redisクライアントを作成します。
const { createClient } = require("redis");
const { RedisStore } = require("rate-limit-redis");
const redisClient = createClient({
url: process.env.REDIS_URL
});
redisClient.on("error", (error) => {
console.error("Redis error:", error);
});
async function startServer() {
await redisClient.connect();
const redisLimiter = rateLimit({
windowMs: 60 * 1000,
limit: 100,
store: new RedisStore({
sendCommand: (...args) =>
redisClient.sendCommand(args)
}),
standardHeaders: "draft-8",
legacyHeaders: false
});
app.use("/api/shared", redisLimiter);
app.get("/api/shared/content", (req, res) => {
res.json({
message: "Shared rate limit enabled"
});
});
app.listen(3000);
}
startServer().catch(console.error);
この例では、Redisにレート制限の状態を保存します。
実際の環境では、Redisへの接続認証、TLS、ネットワーク制限、障害時の動作なども設計する必要があります。
なお、前のExpressサンプルとは別の起動例として示しています。実装時にはサーバー起動処理を一つに統合してください。
7. クライアント識別方法を考える
レート制限では、どの単位でリクエストを数えるかが重要です。
代表的な方法には、以下があります。
| 識別方法 | 特徴 |
|---|---|
| IP Address | 未認証ユーザーにも適用しやすい |
| User ID | 認証済みユーザー単位で管理できる |
| API Key | 外部API利用者の制御に適している |
| Session | セッション単位で制御できる |
ただし、IPアドレスは必ずしも一人のユーザーに対応しません。
企業ネットワークやモバイル回線では、複数のユーザーが同じIPアドレスを共有することがあります。
そのため、認証済みAPIではユーザーIDを利用するなど、サービスの特性に合わせて設計します。
8. Reverse Proxy環境での注意点
本番環境では、Nginxやロードバランサーの背後でExpressを実行することがあります。
この場合、クライアントIPの取得方法に注意が必要です。
Expressではtrust proxyを設定できますが、無条件に有効化するのは危険です。
app.set("trust proxy", 1);
この設定は、信頼できるプロキシが正確に1ホップ存在する構成などを想定した例です。
実際にはネットワーク構成を確認し、信頼するプロキシを適切に指定してください。
誤った設定では、クライアントがIPアドレス情報を偽装し、レート制限を回避できる可能性があります。
9. HTTP 429とRetry-After
リクエスト制限に達した場合は、HTTP 429を返すことが一般的です。
HTTP/1.1 429 Too Many Requests
Retry-After: 60
Content-Type: application/json
レスポンス本文の例:
{
"error": "Rate limit exceeded",
"message": "Please try again later"
}
Retry-Afterは、再試行までの待機時間をクライアントへ伝えるために利用できます。
ただし、すべてのレート制限実装で自動的に付与されるわけではありません。
利用するライブラリの仕様を確認する必要があります。
10. クライアント側での再試行処理
APIが429を返した場合、クライアント側で適切に処理することも重要です。
例えば、短時間に何度も再試行すると、かえってサーバー負荷を増やしてしまいます。
基本的には、次のような流れが考えられます。
API Request
↓
HTTP 429?
↓ Yes
Retry-After確認
↓
一定時間待機
↓
再試行
再試行には上限回数を設け、必要に応じてExponential Backoffを利用します。
また、データ更新APIでは、再試行によって同じ処理が重複実行されないよう、冪等性にも注意する必要があります。
11. デジタルプラットフォームでの活用例
ユーザーアカウント、コンテンツ検索、リアルタイム情報取得などを提供するWebサービスでは、APIの安定性が重要です。
例えば、JLPH のようなデジタルプラットフォームを技術的な設計例として考える場合、以下のようなAPIごとに異なるレート制限を検討できます。
- ログインAPI:認証試行回数を制御する
- コンテンツ取得API:過剰なリクエストを抑える
- 検索API:バックエンドへの負荷を管理する
- 管理者API:認証・認可と組み合わせてアクセスを制御する
ただし、適切な制限値はサービスの規模や実際のトラフィックによって異なります。
運用データを確認しながら調整することが重要です。
12. レート制限の監視
レート制限を導入した後は、効果を確認する必要があります。
主な監視指標は以下です。
- APIリクエスト数
- HTTP 429発生率
- APIレスポンス時間
- Redisの応答時間
- エラー率
- サーバーCPU使用率
例えば、429レスポンスが急増した場合、単純に不正アクセスが増えたとは限りません。
正常なユーザーに対して制限が厳しすぎる可能性もあります。
そのため、リクエスト数だけでなく、ユーザー体験やシステム負荷も含めて判断する必要があります。
13. 本番環境で確認したいポイント
レート制限を導入する際には、次の項目を確認すると便利です。
□ APIごとに適切な制限値を設定している
□ クライアント識別方法を検討している
□ 複数サーバーで制限状態を共有している
□ Reverse Proxyの設定を確認している
□ HTTP 429を適切に返している
□ クライアントの再試行処理を考慮している
□ Redis障害時の動作を設計している
□ 正常ユーザーへの影響を監視している
□ 認証・認可など他の対策と組み合わせている
まとめ
APIレート制限は、Webサービスの安定性とセキュリティを向上させるための重要な仕組みです。
特に重要なのは、以下のポイントです。
- Fixed WindowやToken Bucketなどの方式を理解する
- Expressで基本的なレート制限を実装する
- 複数サーバー環境ではRedisなどの共有ストアを利用する
- IPアドレスやユーザーIDなどの識別方法を適切に選ぶ
- HTTP 429と再試行処理を正しく設計する
- 導入後も監視と改善を継続する
レート制限は、単にリクエストをブロックするための仕組みではありません。
正常なユーザーの利用体験を維持しながら、システム全体のリソースを適切に管理することが重要です。
認証、アクセス制御、監視と組み合わせることで、より安定したWebサービスの構築につながります。