rate-limiting について
開発者向けのレート制限の実装ガイドです。Express と Spring Boot の例、Redis のスライディングウィンドウ、アルゴリズム比較を通じて方式やレスポンスヘッダーの設定を扱います。
主な機能
- 01Express のレート制限例
- 02Redis スライディングウィンドウ実装
- 03レート制限アルゴリズムの比較
- 04Spring Boot と Bucket4j の例
- 05レスポンスヘッダーと本番チェックリスト
活用シーン
- 01API のリクエスト数を制限
- 02複数サーバーで Redis 制限を利用
- 03レート制限アルゴリズムを比較
- 04429 レスポンスと再試行案内を設定
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ claude-dev-suite/ claude-dev-suite --skill rate-limitingインストール後、Claude Code で/rate-limitingを入力して読み込まれたか確認します。
詳細
レート制限
Express (express-rate-limit)
import rateLimit from 'express-rate-limit';
import RedisStore from 'rate-limit-redis';
import { createClient } from 'redis';
const redis = createClient({ url: process.env.REDIS_URL });
// Global rate limit
const globalLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
limit: 100,
standardHeaders: 'draft-7',
legacyHeaders: false,
store: new RedisStore({ sendCommand: (...args) => redis.sendCommand(args) }),
message: { error: 'Too many requests, try again later' },
});
app.use(globalLimiter);
// Stricter limit on auth endpoints
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
limit: 5,
keyGenerator: (req) => req.body.email || req.ip, // By email, not just IP
store: new RedisStore({ sendCommand: (...args) => redis.sendCommand(args) }),
});
app.use('/api/auth/login', authLimiter);
Redis スライディングウィンドウ(カスタム)
async function isRateLimited(key: string, limit: number, windowSec: number): Promise<boolean> {
const now = Date.now();
const windowStart = now - windowSec * 1000;
const multi = redis.multi();
multi.zRemRangeByScore(key, 0, windowStart); // Remove old entries
multi.zAdd(key, { score: now, value: `${now}` });
multi.zCard(key); // Count in window
multi.expire(key, windowSec);
const results = await multi.exec();
const count = results![2] as number;
return count > limit;
}
// Usage
if (await isRateLimited(`rl:${userId}`, 100, 60)) {
return res.status(429).json({ error: 'Rate limit exceeded' });
}
アルゴリズムの比較
| アルゴリズム | 精度 | メモリ使用量 | 最適な用途 |
|---|---|---|---|
| 固定ウィンドウ | 低(境界でバーストが発生) | 非常に少ない | シンプルな API |
| スライディングウィンドウログ | 高 | 中程度 | 正確な制限 |
| スライディングウィンドウカウンター | 中 | 少ない | 精度とメモリ使用量のバランス |
| トークンバケット | 中 | 少ない | バーストに対応する API |
Spring Boot (Bucket4j)
@Bean
public FilterRegistrationBean<RateLimitFilter> rateLimitFilter() {
var filter = new RateLimitFilter(
Bandwidth.classic(100, Refill.intervally(100, Duration.ofMinutes(1)))
);
var registration = new FilterRegistrationBean<>(filter);
registration.addUrlPatterns("/api/*");
return registration;
}
レスポンスヘッダー
// Standard rate limit headers (RFC draft-7)
res.set({
'RateLimit-Limit': '100',
'RateLimit-Remaining': `${remaining}`,
'RateLimit-Reset': `${resetTimestamp}`,
'Retry-After': `${retryAfterSeconds}`, // On 429 responses
});
アンチパターン
| アンチパターン | 修正方法 |
|---|---|
| 複数サーバー構成でのメモリ内レート制限 | Redis ベースのストアを使用する |
| IP のみでレート制限する | ユーザー ID または API キーでも制限する |
| レスポンスにレート制限ヘッダーがない | クライアントが状況を把握できるよう RateLimit-* ヘッダーを送信する |
| すべてのエンドポイントで同じ制限を適用する | 認証、Webhook、負荷の高い処理にはより厳しい制限を設定する |
| 429 レスポンスに Retry-After がない | Retry-After ヘッダーを含める |
本番環境チェックリスト
- 分散デプロイメント向けに Redis ベースのレート制限を使用する
- エンドポイントごとにレート制限を設定する
- レスポンスに標準のレート制限ヘッダーを含める
- 429 レスポンスに Retry-After ヘッダーを含める
- ユーザー ID(IP だけでなく)でレート制限する
- 監視:レート制限に達した頻度
コメント
Markdown 対応