Firebase 408 エラーはクライアント側からのリクエストタイムアウト時間内に完了できず、Firebase サーバーが接続を切断した状態です。ネットワーク環境またはアプリケーションの処理速度が原因となります。

よくある原因

ネットワーク接続の不安定性

WiFi や 4G 接続が途中で断絶したり、パケット損失が発生したりするとリクエストが完了されません。特に移動中のモバイル環境や、企業のファイアウォール経由でのアクセスでは接続が中断されやすくなります。Firebase サーバーはリクエスト到着を待ちますが、タイムアウト時間(デフォルト 30 秒程度)を超えると接続を切断し 408 を返します。

クライアント側の処理遅延

リクエスト送信前の処理(データベースクエリ、ファイル読み込み、画像圧縮など)が想定より長くかかると、タイムアウト発動前にエラーとなります。特に大容量データの同期や複雑な認証処理では、クライアント内での遅延が積み重なりやすいです。

Firebase クライアント SDK の設定不足

デフォルトのタイムアウト値がアプリケーション要件に合わず、処理時間が長いトランザクション(バッチ書き込みなど)で頻発します。

解決手順

ステップ 1:ネットワーク接続を確認する

まずクライアント環境のネットワーク状態を確認します。WiFi 接続が不安定でないか、通信速度が著しく低下していないか確認してください。

Android 環境での確認例:

ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
boolean isConnected = activeNetwork != null && activeNetwork.isConnectedOrConnecting();
Log.d("Network", "接続状態: " + isConnected);

Web 環境での確認例:

// ネットワーク接続状態の確認
if (navigator.onLine) {
  console.log("オンライン状態");
} else {
  console.log("オフライン状態");
}

ステップ 2:Firebase クライアント SDKタイムアウト設定を延長する

Android での設定例:

FirebaseDatabase database = FirebaseDatabase.getInstance();
// タイムアウトを 60 秒に延長(デフォルト: 30秒)
database.setLogLevel(Logger.Level.DEBUG);
DatabaseReference ref = database.getReference();

// 接続タイムアウトの明示的な設定
ref.child("your-path").addValueEventListener(new ValueEventListener() {
  @Override
  public void onDataChange(DataSnapshot snapshot) {
    // 処理
  }

  @Override
  public void onCancelled(DatabaseError error) {
    Log.e("Firebase", "エラーコード: " + error.getCode());
  }
});

Web(JavaScript)での設定例:

import { initializeApp } from "firebase/app";
import { getDatabase } from "firebase/database";

const app = initializeApp(firebaseConfig);
const database = getDatabase(app);

// リトライロジックを実装してタイムアウトに対応
const fetchDataWithRetry = async (path, maxRetries = 3) => {
  for (let i = 0; i < maxRetries; i++) {
    try {
      const response = await fetch(`https://<your-project>.firebaseio.com/${path}.json?timeout=60000ms`);
      if (response.ok) {
        return await response.json();
      }
    } catch (error) {
      console.error(`試行 ${i + 1} 失敗: ${error.message}`);
      if (i < maxRetries - 1) {
        await new Promise(resolve => setTimeout(resolve, 2000 * (i + 1))); // 指数バックオフ
      }
    }
  }
  throw new Error("最大リトライ回数に達しました");
};

fetchDataWithRetry("your-path");

ステップ 3: リトライロジックを追加する

一時的なネットワーク障害に対応するため、リトライ処理を実装します。

// Firebase Realtime Database での指数バックオフリトライ
const retryableOperation = async (operation, maxRetries = 5) => {
  let lastError;
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await operation();
    } catch (error) {
      lastError = error;
      const delayMs = Math.pow(2, attempt) * 1000; // 1秒、2秒、4秒...
      console.log(`${attempt + 1} 回目の試行失敗。${delayMs}ms 後に再試行します`);
      await new Promise(resolve => setTimeout(resolve, delayMs));
    }
  }
  throw lastError;
};

ステップ 4:大容量リクエストを分割する

バッチ書き込みや大容量データ取得の場合は、リクエストを小分けにしてタイムアウトを回避します。

// 大量のデータを分割して書き込み
const writeLargeDataset = async (path, dataArray, batchSize = 100) => {
  for (let i = 0; i < dataArray.length; i += batchSize) {
    const batch = dataArray.slice(i, i + batchSize);
    try {
      await Promise.all(batch.map(item => 
        database.ref(`${path}/${item.id}`).set(item)
      ));
      console.log(`${i + batchSize} 件のデータを書き込み完了`);
    } catch (error) {
      console.error(`バッチ書き込みエラー: ${error.message}`);
      throw error;
    }
  }
};

それでも解決しない場合

Firebase Cloud Logging で詳細なログを確認します。Cloud Console から Logging → ログエクスプローラー にアクセスし、リソースタイプを Cloud Run または App Engine に絞り込んで 408 エラーの発生パターンを分析してください。

同じネットワーク環境の複数デバイスで発生する場合はネットワークインフラの問題の可能性があるため、ISP(インターネットサービスプロバイダー)に確認を取るか、VPN 接続で別ルート経由のテストを実施します。

タイムアウト値を最大限延長しても解決しない場合は、Firebase サポートに詳細なリクエストログ(タイムスタンプ、リクエストサイズ、エンドポイント)を含めて報告してください。


免責事項:本記事の内容は、執筆時点の公開情報をもとに作成したものです。ソフトウェアの仕様は予告なく変更されることがあります。最新の情報は各ツールの公式サポートページをご確認ください。本記事の情報を利用した結果生じたいかなる損害についても、著者および運営者は責任を負いかねます。