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

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

今回は、処理を並列化してGASの起動時間の最大値を超えないようにする方法をご紹介します。

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

処理の並列化にはいくつか制約があります。 まずはシンプルにプログラムの実行が短くできないか検討してみるのもいいかもしれません。

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

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

起動時間の最大値とは?

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

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

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

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

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

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

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

処理の並列化

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

このプログラムは、1回約350秒かかるのmain関数を10回実行しています。

プログラム

function main() {
  for(let i = 0; i< 10; i++) {
    console.log(i + ": 処理中です。")
    Utilities.sleep(350000)
  }
}

実行結果

22:23:48	お知らせ	実行開始
22:23:48	情報	0: 処理中です。
22:29:38	情報	1: 処理中です。
22:29:48	エラー	
Exceeded maximum execution time

通常この処理をfor文などで図のように直列に実行した場合は、処理2回目の途中で制限時間6分を超えてエラーになってしまいます。

処理の並列化とは

もし、この350秒の処理を順番に10回ではなく、すべて並列に実行することができたら起動時間の上限である6分以内に処理を収めることができそうです。

また、上限を考えなければ本来は350秒*10回 = 3500秒(約60分)必要な処理が並列化によって6分以内に収めることができます。このように処理間に前後関係がなく、並列化できる場合は処理の効率化の観点で非常に強力な手段となります。

並列処理の最小構成

並列化するためには、ファイルをHTMLとスクリプトの2つに分ける必要があります。

仮に「main.gs」、「dialog.html」というファイル名で以下のプログラムを作成します。

スクリプト

function onOpen() {
  SpreadsheetApp.getUi()
      .createMenu("追加メニュー")
      .addItem("並行実行", "openDialog")
      .addToUi()
}

function openDialog() {
  const html = HtmlService.createTemplateFromFile("dialog.html").evaluate()
  SpreadsheetApp.getUi().showModalDialog(html, "並列処理実行中!")
}

function main(count) {
  console.log(count + ": 処理中です。")
  Utilities.sleep(350000)
}

HTML

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <script>
      for(let i = 0; i < 10; i++) {
        google.script.run
          .withSuccessHandler(onSuccess)
          .main(i + 1)
      }

      function onSuccess() {
        google.script.host.close()
      }
    </script>
  </head>
</html>

実行中の表示

こちらが実行のログです。10回の350秒の処理が同時刻に開始し、正常にどれも終了していることがわかると思います。

プログラムの解説

並列化を実装するためには、HTMLとスクリプトの2ファイルに処理を分けて書く必要があります。

  • 並列実行に関する処理はHTML側
  • 実処理部分はスクリプト側

に書きます。

処理は次のような順番に実行されます。

  1. 【スプレッドシート】onOpenで追加されたメニューからダイアログ表示
  2. 【main.gs】openDialog関数でhtmlファイルを開く
  3. 【dialog.html】HTMLに書かれたスクリプト(mainの繰り返し処理)を実行する
  4. 【main.gs】並列処理したい関数main関数を実行する(10回分)
  5. 【dialog.html】すべての並列処理が完了したらonSuccess関数を実行する

HTMLからGASの処理を呼び出す方法

google.script.run
  .withSuccessHandler(onSuccess)
  .main(i + 1)

google.script.runに続けて、スクリプト側の関数名を指定することでHTML側から処理を実行することができます。

この呼び出し方はmain関数の終了を待たないので、forループなどの繰り返しと組み合わせると、平行していくつもの処理を実行することができます

最後にwithSuccessHandlerで処理が正常終了したときの処理を実行しています。処理の終了を待って次の処理を始めたいときに使います。

今回は、最初に開いたダイアログを閉じています。

処理の結果を次に渡すには?

main関数の戻り値を次の処理に渡したいときがあります。

その場合はwithSuccessHandlerに渡す関数の引数で受け取ることができます。

サンプルプログラムでmain関数で値を返すようにして、onSuccess関数で受け取るようにしてみて動作を確認してみてください。

並列処理の使いどころ

処理が効率的に実行できる並列処理ですが、並列処理に向いているケースと、向いていないケースがあります。

諸々と気にしなければいけない要素はあるのですが、それは使いながら覚えていただくとして、まずは一点だけ気をつけるようにしてください。

  • 実行する関数の間に前後関係がある処理は並列処理にできない

以下の直列と並列の図を見比べるとわかりやすいですが、処理1~処理10の間に前後関係がある場合は並列化には向きません。

前後関係とは、前の処理の戻り値を次の関数が受け取るだったり、前の処理でセルに書き込んだ値を次の処理で使うなどのことを指します。

また、HTMLとスクリプトで処理が分かれるため、動作が追いにくくなります。

プログラムの動作を確認するときは、HTML側の処理はログをダイアログとして埋め込むなど、ログの出力の仕方を工夫してみてください。

ログの出力の方法は以下の記事に詳しくまとめています。

まとめ

今回は、処理を並列化してGASの起動時間の最大値を超えないようにする方法をご紹介しました。

プログラムの高速化でも処理を6分以内に収められない場合は、ぜひ並列化ができないか検討してみてください。

参考

Class HtmlService

https://developers.google.com/apps-script/reference/html/html-service

Class google.script.run (Client-side API)

https://developers.google.com/apps-script/guides/html/reference/run