APIの タイムアウトを キャッシュで 抑制した 話|重い 集計処理を 速くする 設計
システムを運用していると、「最初は速かったのに、データが増えたら急に遅くなった」という問題に出会います。この記事では、重い集計処理が原因でAPIがタイムアウトしていた問題を、キャッシュで解消したときの考え方と設計を紹介します。
何が 起きていたのか?
ある管理画面で、一覧を開くたびに集計APIを呼び出していました。データが少ないうちは問題ありませんでしたが、件数が増えるにつれて応答が遅くなり、ついにはAPI Gatewayのタイムアウトでエラーが返るようになりました。
API GatewayとLambdaを組み合わせる構成では、Lambda自体の実行時間に余裕があっても、API Gatewayの統合タイムアウト(REST APIの既定値は29秒)を超えるとエラーになります。
原因は 何だったのか?
調べてみると、原因はシンプルでした。
- 画面を開くたびに、同じ集計を最初からやり直していた
- 集計の元データは1日に数回しか変わらない
- 複数のスタッフが同じ画面を開くため、同じ計算が何度も走っていた
つまり、ほとんどのリクエストで前回と同じ結果を、毎回時間をかけて作り直していたのです。
どう 解決したのか?
タイムアウトを 延ばすのではなく、計算を 減らす
タイムアウトの時間を延ばす方法もありますが、データが増えればまた同じ問題が起きます。また、何十秒も待たされる画面は使われません。そこで、集計結果をキャッシュし、データが変わったときだけ作り直す設計に変えました。
キャッシュの 設計で 決めた こと
| 決めること | 今回の判断 |
|---|---|
| 何をキャッシュするか | 集計結果(画面に出す形に整えたもの) |
| どこに置くか | 複数のLambdaから共有できる外部ストア |
| いつまで有効か | 有効期限を設定(保険として) |
| いつ消すか | 元データが更新されたとき |
Lambdaは実行環境が使い回されるとは限らないため、メモリ上の変数ではなく、外部のストア(DynamoDBやElastiCacheなど)に置くのがポイントです。
実装の 流れ
const CACHE_KEY = 'summary:v1';
const TTL_SECONDS = 60 * 60; // 保険としての有効期限(1時間)
export async function getSummary() {
// 1. まずキャッシュを見る
const cached = await cache.get(CACHE_KEY);
if (cached) return cached;
// 2. なければ集計して保存する
const summary = await buildSummary(); // 重い集計
await cache.set(CACHE_KEY, summary, { ttl: TTL_SECONDS });
return summary;
}
// 元データが更新されたら、キャッシュを消す
export async function onDataUpdated() {
await cache.delete(CACHE_KEY);
}この形にすることで、元データが更新された直後の1回だけ集計が走り、それ以外のリクエストはキャッシュから即座に返るようになりました。
さらに 改善できる ことは?
古い データを 返しながら裏で 更新する
キャッシュが切れた直後のリクエストは、集計を待つ必要があります。これを避けるには、有効期限が切れても古い結果をいったん返し、裏で新しい結果を作る方法(stale-while-revalidate)が有効です。
集計を 事前に 作って おく
さらに進めると、データ更新のイベントをきっかけに集計を作り直しておく方法もあります。利用者がAPIを呼ぶときには、常に計算済みの結果が用意されている状態になります。
キャッシュの キーに バージョンを 入れる
集計の内容や形式を変えたときに古いキャッシュが残らないよう、キーに v1 のようなバージョンを入れておくと安全です。
キャッシュを 入れる ときの 注意点は?
- 常に正確な値が必要な処理には慎重に使う:在庫・残高・定員の残りなどは、古い値が表示されると問題になる
- 消し忘れを防ぐ:データを更新するすべての経路でキャッシュを消す。経路が多い場合は、有効期限を短めにして保険にする
- 同時アクセスに備える:キャッシュが消えた瞬間に大量のリクエストが来ると、全員が同時に集計を始める。ロックや事前作成で防ぐ
データベースやサーバーの選び方で運用費を抑える考え方は、コスパ最強の開発構成|DB・サーバー・LLMまで運用費を抑える選び方でまとめています。
まとめ
- タイムアウトは「時間を延ばす」より「同じ計算を減らす」で解決する
- キャッシュは「何を・どこに・いつまで・いつ消すか」を先に決める
- 正確さが必要なデータには慎重に使い、同時アクセスにも備える
システムの遅さやタイムアウトでお困りの方は、既存システムの調査からご相談いただけます。
よくある質問
タイムアウトの時間を延ばすだけではだめですか?
一時的にはしのげますが、データが増えればまた同じ問題が起きます。また、利用者は長い待ち時間に耐えられません。処理そのものを速くするか、結果を使い回す設計にする方が根本的な解決になります。
キャッシュを入れると古いデータが表示されませんか?
有効期限だけに頼るとその間は古いデータが返ります。データの更新時にキャッシュを消す(無効化する)仕組みを組み合わせることで、ほぼ最新の状態を保てます。
どんな処理がキャッシュに向いていますか?
読み込みが多く書き込みが少ない処理、計算に時間がかかる処理、多少の遅れが許される処理です。逆に、残高や在庫のように常に正確な値が必要な処理には慎重に使います。
システム開発の
要件が固まっていなくても大丈夫です。まずはお気軽にご相談ください。