【Googleスプレッドシート】Apps Scriptがタイムアウトする原因!6分制限の回避手法

【Googleスプレッドシート】Apps Scriptがタイムアウトする原因!6分制限の回避手法
🛡️ 超解決

Googleスプレッドシートで大量のデータを処理していると、Apps Scriptが突然「タイムアウト」で停止してしまうことに悩まされたことはありませんか。これはスクリプトの実行時間が6分を超えたために強制終了される現象です。この記事では、タイムアウトの原因と、その制限を回避してスクリプトを最後まで実行できるようにする手法を詳しく解説します。

タイムアウトは、スクリプトの処理が長引くと必ず発生するわけではなく、特定の処理パターンで起こりやすいものです。原因を理解すれば、対策を立てやすくなります。ここでは、効率的なコーディング方法から、実行時間を分割するテクニック、トリガー設定の見直しまで、実践的な解決策を紹介します。

【要点】Apps Scriptの6分タイムアウトを回避する3つの手法

  • 処理の分割(バッチ処理): 大量データを数百行ずつに区切り、複数回の実行で完結させることでタイムアウトを防ぎます。
  • スプレッドシート操作の最適化: セル1つずつ読み書きせず、配列や範囲一括操作を使って処理時間を大幅に短縮します。
  • 実行時間トリガーの活用: トリガーで定期的にスクリプトを再実行させ、継続的に処理を進める仕組みを作ります。

ADVERTISEMENT

なぜApps Scriptは6分でタイムアウトするのか?原因と仕組み

Googleのサーバーでスクリプトが実行される場合、1回の実行に対し6分(360秒)という最大実行時間が設定されています。これは他のユーザーへの負荷を抑えるための制限です。ただし、この6分のカウントはスクリプトが実際にコードを実行している時間のみを対象とします。例えば、UrlFetchAppで外部APIを待つ時間や、SpreadsheetAppのflush()で書き込みを待つ時間も含まれます。

タイムアウトが発生しやすいのは、次のようなパターンです。1つ目は、forループで数万行のシートを1セルずつ書き換える処理です。2つ目は、GoogleカレンダーやGmailなどの外部サービスと大量にやり取りする処理です。3つ目は、IMPORTRANGEの読み込み待ちを含む複雑な計算です。これらの処理は6分を超えやすく、途中で停止してデータが不完全になります。

タイムアウトを回避する具体的な手順

方法1: 処理を分割して実行するバッチ処理

最も基本的な対策は、大量データを小さな塊(バッチ)に分けて処理することです。例として、10000行のデータを1000行ずつ10回に分割します。

  1. 分割サイズを決める
    スクリプトの内容にもよりますが、目安は500〜2000行です。まずは1000行で試し、タイムアウトしない最大サイズを調整します。変数batchSizeに設定します。
  2. ループで部分範囲を処理する
    forループのステップをbatchSizeごとにし、getRange(row, col, batchSize, numCols)で範囲を取得します。書き込みも同様にsetValuesで一括に行います。
  3. 途中経過を保存する
    最後に処理した行番号をシートのセルやプロパティサービスに記録します。次回実行時に再開できるようにします。
  4. トリガーで定期的に実行する
    時間ベースのトリガーを1分間隔などで設定し、バッチ処理が完了するまで繰り返し実行させます。終了フラグを作って停止します。

以下はバッチ処理のコード例です。

function processBatch() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getActiveSheet();
  var lastRow = sheet.getLastRow();
  var batchSize = 1000;
  var startRow = PropertiesService.getScriptProperties().getProperty('startRow');
  startRow = startRow ? parseInt(startRow) : 2; // ヘッダー行をスキップ
  if (startRow > lastRow) {
    PropertiesService.getScriptProperties().deleteProperty('startRow');
    return; // 完了
  }
  var endRow = Math.min(startRow + batchSize - 1, lastRow);
  var range = sheet.getRange(startRow, 1, endRow - startRow + 1, sheet.getLastColumn());
  var values = range.getValues();
  // ここでvaluesに対して処理を行う
  // 例: 各行のA列とB列を連結してC列に書き込む
  for (var i = 0; i < values.length; i++) {
    values[i][2] = values[i][0] + ' ' + values[i][1];
  }
  range.setValues(values);
  PropertiesService.getScriptProperties().setProperty('startRow', endRow + 1);
}

方法2: スプレッドシート操作の最適化で処理時間を短縮

スプレッドシートの読み書きは時間がかかるため、回数を減らすことが重要です。getValues / setValues を使って範囲全体を配列で処理し、API呼び出しやインタラクションを最小限に抑えます。

  1. 配列で一括取得・一括書き込み
    getRange().getValues()で全データを二次元配列として取得し、加工後はsetValues()で一度に書き戻します。セル1つずつのgetValue/setValueは禁止です。
  2. 不要な flush() を避ける
    flush()は強制的に変更を反映するため時間がかかります。必要な場合以外は使わず、自動反映に任せます。
  3. 複数のシート操作をまとめる
    シート間のコピーや移動も、可能な限り一括で行います。例えばcopyToメソッドで範囲をまとめてコピーします。
  4. 条件付き書式やデータ検証は最小限に
    これらのルールを大量に設定すると処理が重くなります。どうしても必要な場合以外は削除しておきます。

方法3: 時間制限を回避するトリガー設計

どうしても1回の実行で完了しない処理は、トリガーを使って複数回に分けて実行します。この方法はバッチ処理と組み合わせて使うことが多いです。

  1. 時間ベースのトリガーを設定する
    スクリプトエディタのメニューから「編集」→「現在のプロジェクトのトリガー」を開き、新しいトリガーを追加します。実行する関数を選び、時刻指定の間隔(1分おき、5分おきなど)を設定します。
  2. トリガーの最大実行時間に注意
    トリガーで起動されたスクリプトも6分制限があります。また、同時に複数のトリガーが起動しないよう、実行中フラグをプロパティサービスで管理します。
  3. 再開可能な状態を保存する
    処理の途中経過をシートのセルやプロパティサービスに保存します。トリガーのたびにその位置から再開できるようにします。
  4. 完了後にトリガーを削除する
    すべての処理が終わったら、ScriptApp.deleteTrigger(triggerId)でトリガーを解除します。忘れると不要な実行が続きます。

以下のコードでトリガーを管理できます。

function startBatchTriggers() {
  // 1分おきにトリガーを設定
  ScriptApp.newTrigger('processBatchWithTrigger')
      .timeBased()
      .everyMinutes(1)
      .create();
}
function processBatchWithTrigger() {
  var lock = LockService.getScriptLock();
  if (!lock.tryLock(10000)) return; // 前回の実行中ならスキップ
  try {
    processBatch(); // バッチ処理関数
  } finally {
    lock.releaseLock();
  }
}

タイムアウトに関連する注意点と失敗例

無限ループに陥ってタイムアウトが頻発する

バッチ処理で再開用の値が正しく更新されないと、同じ範囲を繰り返し処理してタイムアウトすることがあります。必ずログを出力して進行状況を確認し、終了条件を明確にします。

トリガーが重複して実行される

トリガーを複数設定してしまうと、同時に複数のインスタンスが起動して競合が発生します。LockServiceを使って排他制御を行い、同時実行を防止します。また、トリガー作成時に既存のトリガーを削除する処理を入れます。

プロパティサービスの保存容量を超える

プロパティサービスにはサイズ制限(キー・値合わせて500KB)があるため、大きなデータを保存できません。処理の進捗は行番号や日時などの小さい値のみに限定します。大量の状態が必要な場合はシートの別領域を使います。

外部APIの応答待ちでタイムアウト

UrlFetchApp.fetch()のタイムアウトはスクリプト全体の6分に含まれます。APIの応答が遅い場合は、setTimeoutや再試行ロジックを入れずに、タイムアウト時間を短く設定(例: 30秒)して速やかに諦めるか、別の手法を検討します。

ADVERTISEMENT

各回避手法の比較表

手法 効果 実装難易度 注意点
バッチ処理 タイムアウトを確実に回避 途中再開の仕組みが必要
最適化(一括読み書き) 処理時間を大幅短縮 コードの書き換えが必要
トリガー分割 実行時間制限を回避 トリガー管理と排他制御が必須

まとめとして、Apps Scriptの6分タイムアウトは、処理のバッチ分割とスプレッドシート操作の最適化、トリガーの活用によって確実に回避できます。まずは、配列による一括処理を導入して処理時間を短縮し、それでも足りなければバッチ処理とトリガーで分割実行する流れがおすすめです。次に実際のプロジェクトでこれらの手法を試し、処理時間を計測しながら最適なバッチサイズを見つけてみてください。


ADVERTISEMENT

この記事の監修者
✍️

超解決 第一編集部

疑問解決ポータル「超解決」の編集チーム。正確な検証と、現場視点での伝わりやすい解説を心がけています。