API秒間1万リクエスト対応:面接問題と解答アプローチ
問題¶
Interviewer: Your API is getting 10,000 requests per second. Server is choking. Users are complaining. You have 15 minutes to fix it. What do you do?
解答¶
即座に取れる対応(15分以内):
- レートリミットを設ける — Redis や API Gateway のレートリミット機能を有効化し、1クライアントあたりの RPS を制限する
- キャッシュを挟む — よく読まれるレスポンスを Redis/Memcached に乗せ、DB・アプリへのヒットを減らす
- 水平スケールアウト — ロードバランサー配下にアプリサーバーを追加してインスタンスを増やす
- 重い処理を非同期化 — キューに積んで即座に 202 Accepted を返し、ワーカーで後処理する
解説¶
なぜこの順番か¶
| 優先度 | アクション | 理由 |
|---|---|---|
| 1 | レートリミット | 悪意あるクライアントや暴走クライアントを即座に遮断 |
| 2 | キャッシュ | 読み取り系クエリが多い場合に劇的に負荷が落ちる |
| 3 | スケールアウト | AWS/GCPなら数分で追加可能、CPU ボトルネック解消 |
| 4 | 非同期化 | 書き込み・重い処理を切り離す根本対策 |
キャッシュ戦略の選択¶
# Cache-Aside パターン(最も一般的)
def get_user(user_id):
cached = redis.get(f"user:{user_id}")
if cached:
return json.loads(cached)
user = db.query("SELECT * FROM users WHERE id = ?", user_id)
redis.setex(f"user:{user_id}", 300, json.dumps(user)) # TTL 5分
return user
レートリミットの実装例(Token Bucket)¶
# Redis を使った Token Bucket
def is_allowed(client_id, limit=100, window=60):
key = f"rate:{client_id}"
current = redis.incr(key)
if current == 1:
redis.expire(key, window)
return current <= limit
トレードオフ¶
- キャッシュ: 読み取り負荷には効くが、データ整合性の問題(Cache Invalidation)が発生しうる
- スケールアウト: コストが増大する。ステートレスな設計(セッションを外出し)になっていることが前提
- 非同期化: UX が変わる(即座の結果を返せない)。冪等性の担保が必要
面接でのポイント¶
- 問題を分解して話す: まずボトルネックを特定(CPU? DB? ネットワーク?)してから対策を提案する姿勢を見せる
- 即時対応と恒久対応を区別する: 「今すぐ15分でやること」と「中長期でやること」を分けて話す
- 数字を使う: 「キャッシュヒット率を80%にすれば DB の負荷は1/5になる」など定量的な根拠を示す
- トレードオフを述べる: 完璧な解法はないので、なぜこの選択をしたかの理由を添える