外部API呼び出しのたびにページが重くなる問題を、PHPのキャッシュ実装で根本解決。Laravelでの実践的なコード例と失敗パターンを交えて解説します。
こんな悩みはありませんか?
「天気情報や在庫データを外部APIから取得しているのに、ページを開くたびに数秒かかる」「APIの無料プランの呼び出し上限にすぐ達してしまう」「アクセスが集中すると途端にサイトが重くなる」——こうした悩みを抱えている開発者・Web担当者は少なくありません。
外部APIとの連携は現代のWebアプリケーションに欠かせない機能ですが、毎回のリクエストごとにAPIを叩き続ける実装は、パフォーマンスとコスト面で大きなリスクを抱えています。弊社(Fivenine Design)でも、クライアント案件で何度もこの問題に直面してきました。答えはシンプルです——「キャッシュ」です。
この記事では、PHPおよびLaravelで外部APIのレスポンスをキャッシュする実装パターンを、実案件での経験をもとに実践的に解説します。
なぜ外部APIはそのまま叩き続けると危険なのか
外部APIを毎リクエスト直接呼び出す構成には、大きく3つの問題が潜んでいます。
1. レスポンスタイムへの依存
外部サービスのサーバー応答速度は、自社でコントロールできません。通常200ms程度でも、ピーク時や障害時には2〜5秒かかることも珍しくなく、その遅延がそのままユーザー体験に直撃します。
2. APIレート制限(Rate Limit)
ほとんどの外部APIには「1分間に100回まで」「1日1,000リクエストまで」といった呼び出し制限があります。アクセスが集中すると制限に達し、エラーが返ってくる——これは本番環境で実際に起きやすい障害パターンです。
3. コストの増大
有料APIでは呼び出し回数に応じて課金されます。キャッシュなしで1万PV/日のサイトを運用すると、同じデータを1万回取得することになり、月額コストが数十倍に膨らむケースもあります。
あるクライアントの事例では、不動産検索サイトで地図API(Google Maps Platform)を毎ページロード時に呼び出す実装になっており、月のAPI費用が想定の8倍に達していました。キャッシュ導入後、費用は約1/6に削減されています。
実装パターン:段階的に理解するキャッシュ戦略
Step 1:素朴なPHPファイルキャッシュ(フレームワーク不使用)
まず基本的な仕組みを理解するために、素のPHPでのファイルキャッシュ実装から見ていきましょう。
<?php
function fetchWithCache(string $url, int $ttl = 300): array
{
$cacheDir = __DIR__ . '/cache';
$cacheFile = $cacheDir . '/' . md5($url) . '.json';
// キャッシュディレクトリが存在しなければ作成
if (!is_dir($cacheDir)) {
mkdir($cacheDir, 0755, true);
}
// キャッシュが存在し、かつ有効期限内であれば返す
if (file_exists($cacheFile) && (time() - filemtime($cacheFile)) < $ttl) {
return json_decode(file_get_contents($cacheFile), true);
}
// APIを叩いてレスポンスを取得
$response = file_get_contents($url);
$data = json_decode($response, true);
// ファイルに保存(次回以降はキャッシュを返す)
file_put_contents($cacheFile, json_encode($data));
return $data;
}
// 使い方
$weatherData = fetchWithCache(
'https://api.example.com/weather?city=yokohama',
600 // 10分間キャッシュ
);
シンプルですが、TTL(有効期限)をURLのMD5ハッシュで管理するこの構造は、Laravelのキャッシュドライバと本質的に同じ考え方です。
Step 2:Laravelのキャッシュ機能を使った実装
本番環境では、Laravelが提供するCacheファサードを活用するのが現実的です。バックエンドをRedis・Memcached・ファイルと切り替えられる柔軟性が強みです。
<?php
namespace App\Services;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
class WeatherApiService
{
private string $baseUrl = 'https://api.example.com';
private int $cacheTtl = 600; // 秒単位(10分)
public function getWeather(string $city): array
{
$cacheKey = "weather:{$city}";
// remember() はキャッシュがあれば返し、なければAPIを叩いて保存
return Cache::remember($cacheKey, $this->cacheTtl, function () use ($city) {
$response = Http::timeout(5)->get("{$this->baseUrl}/weather", [
'city' => $city,
'apikey' => config('services.weather.key'),
]);
if ($response->failed()) {
throw new \RuntimeException('天気APIの取得に失敗しました');
}
return $response->json();
});
}
// キャッシュを手動でクリアしたい場合
public function clearWeatherCache(string $city): void
{
Cache::forget("weather:{$city}");
}
}
Step 3:キャッシュキーの設計に気を配る
キャッシュで最もよく起きるバグが「キーの設計ミス」です。たとえば、ページネーションのあるAPIでpageパラメータをキーに含め忘れると、どのページを開いても同じ結果が返ってきます。
// ❌ ページ番号が無視される(アンチパターン)
$cacheKey = "products:list";
// ✅ リクエストパラメータをすべてキーに含める
$cacheKey = 'products:' . md5(json_encode([
'category' => $category,
'page' => $page,
'sort' => $sort,
]));
よくある失敗パターンと対処法
実際の開発現場で繰り返し遭遇する失敗を4つ紹介します。
❌ 失敗1:TTLを長く設定しすぎる
「とりあえず24時間キャッシュすれば速い」と考えがちですが、在庫数・価格・天気といったリアルタイム性が求められるデータは、古い情報を長時間表示し続けることでビジネス上の問題を引き起こします。データの性質に応じてTTLを設計するのが鉄則です。
- 天気情報:10〜30分
- 商品価格:5〜15分
- ニュース記事リスト:1〜3時間
- マスターデータ(都道府県一覧など):24時間以上
❌ 失敗2:APIエラー時にエラーレスポンスをキャッシュしてしまう
APIが一時的にエラーを返したとき、そのエラーレスポンスをキャッシュしてしまうと、「APIは復旧したのにエラー画面が出続ける」という事態になります。必ずエラー判定後にキャッシュ書き込みを行いましょう。
// ✅ 正常レスポンスのみキャッシュする
return Cache::remember($cacheKey, $this->cacheTtl, function () use ($city) {
$response = Http::get(...);
// 失敗したらキャッシュせず例外を投げる
if ($response->failed()) {
throw new \RuntimeException('APIエラー');
}
return $response->json(); // ここまで来たら正常レスポンスのみ
});
❌ 失敗3:キャッシュキーが衝突する
複数のサービスや環境(本番・ステージング)が同じRedisを共有している場合、キーが重複して別サービスのデータを上書きするトラブルが起きます。キーの先頭に{env}:{service}:のようなプレフィックスをつけることで回避できます。
❌ 失敗4:キャッシュのウォームアップを忘れる
デプロイ直後やキャッシュクリア後は、全ユーザーが「キャッシュミス」を経験します。高トラフィックのサイトでは、この瞬間にAPIへのリクエストが集中し(「Thundering Herd問題」とも呼ばれます)サービスが落ちることも。Laravelのジョブキューで事前にキャッシュを生成しておく「ウォームアップ処理」が有効な対策です。
導入後にこう変わります
あるECクライアントでは、商品詳細ページで外部の在庫管理APIを毎回呼び出す実装になっており、平均ページロードタイムが2.4秒に達していました。Redisキャッシュ(TTL:5分)を導入した結果、平均35msまで短縮——約68倍の高速化を達成しています。あわせてAPI呼び出し回数が1/80以下になり、月次のAPI費用が約85%削減されました。
「速度改善」は単なる快適さの話ではありません。Googleの調査によれば、ページ速度は直接的にSEO評価と直結しており、表示速度の改善が検索順位の向上・直帰率の低下・コンバージョン率の改善につながります。
開発・運用でお困りなら
システム開発
設計から運用まで、堅牢なシステムを構築します
※ 通常1営業日以内にご返信します
まとめと次のステップ
外部APIキャッシュの実装は、パフォーマンス・コスト・可用性の3点でサイトの品質を大きく底上げします。まず自分たちのアプリケーションで「毎回外部APIを叩いている箇所」を棚卸しすることが出発点です。
Laravelを使っている場合はCache::remember()を使えば数十行で実装できますが、キャッシュキー設計・TTL設定・エラーハンドリングを丁寧に設計しないと、かえって問題を生む原因にもなります。
「どこから手をつければいいかわからない」「既存のコードに組み込むのが不安」という場合は、ぜひFivenine Designへご相談ください。20年以上の実績をもとに、現状の構成を診断した上で最適な改善プランをご提案します。