【GAS】「起動時間の最大値を超えました」エラー時の対策 処理の継続編

GAS(Google Apps Script)の実行時に「起動時間の最大値を超えました」というエラーが出て対応に悩まされた経験はありませんでしょうか。

今回は、この起動時間の制限をうまく迂回して処理を継続する方法をご紹介します。

参考:処理継続以外の他の対処方法

このエラーが出た場合は、処理に無駄があり高速化の余地がないか、まずは確認しましょう。

プログラム高速化のコツを「プログラム高速化編」の記事で解説しています。

もし処理が並列で実行しても問題ないならば、並列処理を活用すると全体の処理時間を節約できて効率が良くなります。

並行処理を検討したい場合はこちらの記事をご確認ください。

起動時間の最大値とは?

エラーへの対処方法を解説する前に、このエラーメッセージがどんな意味なのか見ていきましょう。

表示された「起動時間の最大値を超えました」のメッセージの通り、GASで処理を実行できる時間には上限が設定されています。上限時間はエラーが発生したときの実行時間を確認するとわかります。

期間(実行時間)の欄を確認すると360.14秒と記載されています。

3601.4秒 ≒ 6分なので、上限時間は6分であることがわかります。

そのため、 「時間の最大値を超えました」 エラーを解消するためにはGASの一回の実行を6分以内に収める必要があります。

今回は実行時間の上限を確認しましたが、ほかにもGASには様々な制限が設定されています。詳しくは以下のURLを参照してください。

https://developers.google.com/apps-script/guides/services/quotas

処理の継続

具体的な例をもとに解説をしていきます。

スプレッドシート上のA列とB列二つの数字を足し算して、答えを隣のセルに格納する・・・をすべての行に対して行うGASプログラムです。

プログラム

function main() {
  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet()
  const sheet = spreadsheet.getActiveSheet()

  // シート全体の値を取得
  const sheetValueArray = sheet.getRange(2, 1, sheet.getLastRow() - 1, sheet.getLastColumn()).getValues()
  
  // 各行の計算処理をする
  for(let i = 0; i < sheetValueArray.length; i++) {
    const number1 = sheetValueArray[i][0] // 数字1
    const number2 = sheetValueArray[i][1] // 数字2

    // 計算結果を格納する
    const answer = number1 + number2
    sheet.getRange(i + 2, 3).setValue(answer)
 
    // 説明の都合上のスリープ(30秒)
    Utilities.sleep(30000)
  }
}

forループの一周にスリープが入っているので30秒かかるようになっています。

そのため6分の制限のなかでは、12行程度の処理でタイムアウトとなります。

処理の継続とは

タイムアウトとなり途中でおわってしまった処理を再開して処理が継続できれば、今回のケースでは問題は解決しそうです。

ですが、いまのプログラムのままでは単純に再実行した場合にまた2行目から処理が開始されてしまいます。これでは、また13行目付近で処理が中断されてしまいます。

そのためまずは、前回の処理でどこまで足し算が終わったのかを検出できるようにプログラムを改修しましょう。改修例はプログラムは以下の通りです。

プログラム

function main() {
  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet()
  const sheet = spreadsheet.getActiveSheet()

  // シート全体の値を取得
  const sheetValueArray = sheet.getRange(2, 1, sheet.getLastRow() - 1, sheet.getLastColumn()).getValues()
  
  // 各行の計算処理をする
  for(let i = 0; i < sheetValueArray.length; i++) {
    const number1 = sheetValueArray[i][0] // 数字1
    const number2 = sheetValueArray[i][1] // 数字2
    const done = sheetValueArray[i][2] // 足し算の答え

    if(done === "") {
      // 計算結果を格納する
      const answer = number1 + number2
      sheet.getRange(i + 2, 3).setValue(answer)

      // 説明の都合上のスリープ(30秒)
      Utilities.sleep(30000)
    }
  }
}

このプログラムを実行すると、続きである14行目から処理が再開されます。

その行の足し算の答えがはいっている場合はその行は計算が済んでいると判断して、足し算(時間がかかる処理)をスキップしています。そうすることで処理を途中から再開できています。

const done = sheetValueArray[i][2] // 足し算の答え

// 空白の場合スキップ
if(done === "") {
  // 時間がかかる処理をここに入れる
}

これが起動時間の制限を迂回する「処理の継続」の方法です。

プログラムの実行を自動化するテクニック

処理の継続によって、実質6分を超えて処理をさせることができました。

ただし、この迂回方法にも欠点があり、それは処理の継続は「今実行しているプログラムがタイムアウトを待たないといけない」という点です。

また今回のプログラムの場合、同時に並列して処理をするとうまく動作しないことがあるため、常に動いているプログラムは1つにする必要があります。

その問題は、GASの機能である「トリガー実行」で解決します。

GASの左側のトリガーのメニューから「トリガーを追加」を選び次のようにトリガーを設定します。

このように設定することで処理を10分ごとに定期的に実行することができます。

処理が完了するまで、トリガーをスケジュールしておけば、タイムアウトしたか処理をつねに監視する必要がありません。

処理の継続を使うと良いケース

  • 処理の高速化がもう見込めない場合
  • 処理の並列化ができず、つねに順番に処理をする必要がある場合

処理の継続は、全体で6分以上かかるような大きな処理を6分で終わる量で分割して個別に処理するというアプローチです。

そのため、大きな処理でかかるはずだった作業時間自体はかわりません。処理の高速化や並列化は作業時間も節約できるため、ほかの二つの方法も検討できるのであればまずはそちらを検討ください。

まとめ

今回は、起動時間の制限をうまく迂回して処理を継続する方法をご紹介しました。

タイマーと処理の継続を組み合わせると、本来ではできないような大量のデータも処理できるようになります。ぜひ活用してみてください。