🚦

レート制限の仕組み - APIを保護するアクセス制御

14分 で読める | 2025.12.13

レート制限とは

レート制限(Rate Limiting)は、一定時間内のAPIリクエスト数を制限する仕組みです。サービスの安定性を保ち、悪意のある利用やバグによる過剰アクセスからシステムを保護します。

なぜ必要か: 無制限にリクエストを受け付けると、1人のユーザーがシステム全体のリソースを使い果たしたり、DDoS攻撃によってサービスが停止する可能性があります。

レート制限の目的

目的説明
サービス保護過負荷によるダウンを防止
公平性確保すべてのユーザーにリソースを公平に分配
悪用防止スクレイピング、ブルートフォース攻撃の抑止
コスト管理インフラコストの予測可能性を確保

主要なアルゴリズム

1. 固定ウィンドウ(Fixed Window)

一定時間のウィンドウごとにカウントをリセットします。

設定: ウィンドウ1分(00:00〜00:59)、制限100リクエスト/分

時間リクエスト結果
00:00-00:3090
00:30-00:5910✓(合計100)
01:00カウンタリセット
01:00-01:30100

問題点: ウィンドウ境界で瞬間的に2倍のリクエストが可能

時間リクエスト問題
00:59100
01:00100
→ 2秒間で200リクエスト!

2. スライディングウィンドウログ(Sliding Window Log)

各リクエストのタイムスタンプを記録し、過去N秒間のリクエスト数をカウントします。

現在時刻: 01:00:30、ウィンドウ: 過去60秒(00:00:30〜01:00:30)

タイムスタンプ状態
00:00:25ウィンドウ外(削除)
00:00:35✓ 有効
00:00:50✓ 有効
01:00:10✓ 有効

メリット: 正確なレート制限 デメリット: メモリ使用量が多い

3. スライディングウィンドウカウンタ(Sliding Window Counter)

固定ウィンドウの改良版。前後のウィンドウのカウントを重み付けして計算します。

ウィンドウリクエスト数
前(00:00-00:59)80
現在(01:00-01:59)30
現在時刻01:00:20(33%経過)

計算: 推定リクエスト数 = 80 × 0.67 + 30 = 83.6

4. トークンバケット(Token Bucket)

バケットにトークンが一定レートで追加され、リクエストごとにトークンを消費します。

設定: バケット容量10トークン、補充レート1トークン/秒

状態トークン備考
初期状態10/10満タン
5リクエスト消費後5/105トークン消費
3秒後8/103トークン補充

バースト: 現在8リクエスト可能

メリット: バースト対応可能、メモリ効率が良い

5. リーキーバケット(Leaky Bucket)

バケットから一定レートでリクエストが処理されます。

flowchart LR
    In["流入<br/>(可変)"] --> Bucket["バケット<br/>(キュー)"] --> Out["流出<br/>(固定レート)"]

メリット: 出力レートが安定 デメリット: バーストに対応しにくい

実装パターン

レスポンスヘッダー

HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1640000000

制限超過時のレスポンス

HTTP/1.1 429 Too Many Requests
Retry-After: 30
Content-Type: application/json

{
  "error": "rate_limit_exceeded",
  "message": "レート制限を超えました。30秒後に再試行してください。",
  "retry_after": 30
}

Redisを使った実装例

async function checkRateLimit(userId, limit, windowSec) {
  const key = `ratelimit:${userId}`;
  const current = await redis.incr(key);

  if (current === 1) {
    await redis.expire(key, windowSec);
  }

  if (current > limit) {
    const ttl = await redis.ttl(key);
    return { allowed: false, retryAfter: ttl };
  }

  return { allowed: true, remaining: limit - current };
}

制限の粒度

ユーザーベース

ユーザー制限
ユーザーA100リクエスト/分
ユーザーB100リクエスト/分

IPアドレスベース

IPアドレス制限
192.168.1.1100リクエスト/分
192.168.1.2100リクエスト/分

エンドポイントベース

エンドポイント制限備考
GET /api/users100リクエスト/分
POST /api/users10リクエスト/分作成は厳しく

階層型

プラン制限
Free tier100リクエスト/日
Pro tier10,000リクエスト/日
Enterprise無制限

分散システムでの考慮点

中央集権型

flowchart LR
    S1["サーバー1"] --> Redis["Redis<br/>(共有カウンタ)"]
    S2["サーバー2"] --> Redis
    S3["サーバー3"] --> Redis

メリット: 正確 デメリット: Redisへのレイテンシ

ローカルキャッシュ + 同期

flowchart LR
    S1["サーバー1<br/>ローカルカウンタ"] <-->|定期同期| S2["サーバー2<br/>ローカルカウンタ"]

メリット: 低レイテンシ デメリット: 若干の超過を許容

クライアント側の対応

指数バックオフ

async function fetchWithRetry(url, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    const response = await fetch(url);

    if (response.status === 429) {
      const retryAfter = response.headers.get('Retry-After') || Math.pow(2, i);
      await sleep(retryAfter * 1000);
      continue;
    }

    return response;
  }
  throw new Error('Rate limit exceeded after retries');
}

まとめ

レート制限は、APIの安定性と公平性を確保するための重要な仕組みです。トークンバケットやスライディングウィンドウなど、ユースケースに応じたアルゴリズムを選択し、適切な粒度で制限を設定することで、サービスを保護しながら良好なユーザー体験を提供できます。

← 一覧に戻る