0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

外部APIを loop 内で呼ぶのは危険です──性能低下・障害連鎖・高コストを防ぐ設計パターン

Posted at

はじめに

業務アプリやバッチ処理のコードを読んでいると、こんな書き方を見かけることがあります。

foreach ($items as $item) {
    $response = $externalService->fetch($item->id); // ← loop内でAPI叩き
    ...
}

一見すると普通ですが、実はこれ
パフォーマンス劣化・レートリミット・障害耐性低下
を同時に招く危険なアンチパターンです。

この記事では、

  • なぜ loop 内で外部 API/Service を呼んではいけないのか
  • 実際どんな問題が起きるのか
  • どう直すべきか
  • 良い例 / 悪い例をコード付きで解説

をまとめます。

結論

外部API・サービス呼び出しは 必ず loop の外に出すべき です。

☑️ 1. パフォーマンスが最悪になる(ネットワーク × N 回)

APIのレスポンスが 100ms の場合、100件処理しただけで 10秒 かかります。
内部処理は高速でも、ネットワークIOは常に最も遅い。

☑️ 2. レートリミットに簡単に到達する

外部APIはたいてい何らかの回数制限があります。
loop 内で呼ぶと、意図せず制限を踏み抜き、急にエラー多発 → 障害化。

☑️ 3. 外部障害の影響を全件が受けて止まる

loop 内で毎回 API を呼ぶと、

  • APIが一時的に落ちる
  • タイムアウトが発生する

だけで 全件処理が即停止 します。
外部依存処理は、障害時の影響範囲を最小化すべきです。

☑️ 4. 再実行が困難(どこまで進んだ?問題)

途中で落ちると「どこまで処理したか?」が不明になり、リトライがしにくい。

☑️ 5. 従量課金APIの場合はコストが爆増

ループ件数 × APIコスト = 請求額
開発者にとっては見えづらいが、運用コストを押し上げます。

❌ 悪い例:loop 内で API を叩く

foreach ($hotels as $hotel) {
    $planId = $externalApi->getPlanId($hotel->plan_code); // ❌ 毎回API
    $this->savePlan($hotel, $planId);
}
  • 毎回API
  • 障害が起きたら即終了
  • 遅い
  • コスト高い

典型的なアンチパターンです。

🟢 良い例:loop 外で一括取得する

☑️ API側がまとめ取得に対応している場合(最も理想)

$planCodes = $hotels->pluck('plan_code')->all();

// 一括で API 呼び出し(まとめて取得)
$planMap = $externalApi->getPlanIds($planCodes);

foreach ($hotels as $hotel) {
    $planId = $planMap[$hotel->plan_code] ?? null;
    $this->savePlan($hotel, $planId);
}

効果

  • API コール回数 100 → 1 回
  • パフォーマンス上昇
  • 障害影響最小化
  • コスト削減

「APIのあるべき姿」は bulk API を用意することです。

API側が対応していない場合:キャッシュで回避

APIがまとめ取得に対応していないケースでも、loop 内呼び出しを避ける工夫 ができます。

$cache = [];

foreach ($hotels as $hotel) {

    // すでに取得したコードはAPI呼ばない
    if (!isset($cache[$hotel->plan_code])) {
        $cache[$hotel->plan_code] = $externalApi->getPlanId($hotel->plan_code);
    }

    $planId = $cache[$hotel->plan_code];
    $this->savePlan($hotel, $planId);
}

重複コードに対する API 呼び出しが 1回に削減 されます。

🔧 どうしても loop 内で API が必要な場合の対策

① キャッシュする(最重要)

$cache = [];

foreach ($items as $item) {

    if (!isset($cache[$item->code])) {
        $cache[$item->code] = $api->fetch($item->code);
    }

    $value = $cache[$item->code];
}

② リトライ・サーキットブレーカーを設定する

障害中でも無限リトライしないようにする。

③ キューに逃がす

外部API依存処理はできるだけ非同期化。

④ まとめて投げられる API を作る(サーバ側改善)

自身でAPIを持っている場合はbulk API を用意するのが最強です。

「Service呼び出し」も同じ

外部APIだけでなく、以下も同じ原則が当てはまります。

  • DBクエリ(N+1問題)
  • 別マイクロサービス
  • 外部ライブラリでのIO処理
  • ファイルアクセス

loop 内の I/O は すべてアンチパターン になり得ます。

🎯 まとめ

外部APIやサービス呼び出しは、必ず loop の外に出すことを最優先で考えるべきです。

  • 性能改善
  • レートリミット回避
  • 障害耐性アップ
  • コスト削減

とメリットが非常に大きいです。

もし loop 内で呼ばれているコードを見つけたら、
「これ、外に出せない?」
と必ず疑って改善していきましょう。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?