Google Apps Scriptの6分制限の簡単な対処法

インクルキャット JP
10 min readJul 20, 2021

--

Photo by Tsvetoslav Hristov on Unsplash

※この記事は「An easy way to deal with Google Apps Script’s 6-minute limit」を翻訳したものです。

6分制限について

Google Apps Scriptは便利ですし、仕事でも大いに役立つでしょう。しかし、使っていくうちに、大きな壁にぶつかることがあります。それは、実行時間の6分の制限です。公式ドキュメントによると、Google Apps Scriptの1回の実行時間は最大6分となっています(公式ドキュメントを見る限り、有料ユーザーと無料ユーザーの違いはないようです)

スクリプトの実行時間が6分に達した場合、スクリプトは突然停止し、「Exceeded maximum execution time」というエラーメッセージが表示されます。

このようなエラーメッセージが表示されます。😥

Apps Scriptの処理速度はそれほど速くないので、Google SheetsやGoogle Driveからデータを読み込んでいると、あっという間に制限時間に達してしまいます。では、どうすればいいのでしょうか?これを解決する方法をお伝えします。

これを解決するには?

この問題は、スクリプトのプロパティ時間駆動のトリガーで解決できます。これらのメカニズムを使って、必要なだけスクリプトを再実行することができます。

PropertiesServiceクラスでは、1つのスクリプトにスコープされたキーと値のペアで簡単なデータを保存することができます。そのため、関数の処理回数をスクリプトのプロパティとして保存することができます。

時間駆動のトリガーでは、スクリプトを特定の時間に実行させることができます。つまり、6分の制限に達する前に次回の関数を実行するトリガーを設定することができます。

6分間の制限を乗り越えるためのメカニズム

しかし、実際にやるとなると、簡単ではないことがわかります😓。この仕組みを利用したソリューションは、インターネット上でたくさん見つけることができます。不要になったトリガーやプロパティを削除したり、引数の渡し方を決めたり、いろいろなことをしなければならないことが、すぐにわかるでしょう。

そこで、6分間の時間制限を効率的に処理するために、LongRunというJavascriptのクラスを作りました。これは私のオープンソースプロジェクト「GAS-Terminal」の拡張機能として作られたものです。

LongRunクラス

このクラスは、PropertiesServiceやTime-based Triggersなどの操作を背後に隠し、長時間のスクリプトを簡単に実行することができます。以下にコード例を示します。

function LongRunTest(
// there must be no arguments, because the parameters must be retrieved from LongRun class.
/* times: number, funcExecutionSeconds: number, maxExecutionSeconds: number, triggerDelayMinutes: number */
) {
let longRun = LongRun.instance;// funcName must equal this function's name.
const funcName = 'LongRunTest';// you can get the parameters from LongRun class.
// the order of the array is the same as the order of the command definition.
const params = longRun.getParameters(funcName);
const times = parseInt(params[0]);
const funcExecutionSeconds = parseInt(params[1]);
const maxExecutionSeconds = parseInt(params[2]);
const triggerDelayMinutes = parseInt(params[3]);// you can set the long-running configurations. of course you can use the default values.
longRun.setMaxExecutionSeconds(maxExecutionSeconds); // default is 240 seconds
longRun.setTriggerDelayMinutes(triggerDelayMinutes); // default is 1 minute// you should get the index to resume(zero for the first time)
let startIndex = longRun.startOrResume(funcName);
if( startIndex === 0 ){
LogUtils.i('--- LongRunTest command started. ---');
}try {
// Execute the iterative process.
for (let i = startIndex; i < times; i++) {
LogUtils.i('Processing: ' + i);// Each time before executing a process, you need to check if it should be stopped or not.
if (longRun.checkShouldSuspend(funcName, i)) {
// if checkShouldSuspend() returns true, the next trigger has been set
// and you should get out of the loop.
LogUtils.i('*** The process has been suspended. ***');
break;
}// *** code your main process here! ***
Utilities.sleep(funcExecutionSeconds * 1000); // demonstrate the processLogUtils.i('Processing Done!: ' + i);
}
}
catch (e) {
LogUtils.ex(e);
}
finally {
// you must always call end() to reset the long-running variables if there is no next trigger.
const finished = longRun.end(funcName);
if( finished ){
LogUtils.i('--- LongRunTest command finished. ---');
}
}
}

LongRunクラスのソースコードは、私のGitHubリポジトリから入手できます。ぜひ、チェックしてみてください。もちろん、このクラスを単独で使うこともできるが、GAS-Terminalフレームワークと組み合わせて使うとより効果的です。

LongRunクラスをGAS-Terminalなしで使いたい方へ(2021年10月24日追加)

LongRunクラスはGAS-Terminalを強化するために作られたクラスですが、GAS-Terminalがなくても問題なく使えます。LongRun.tsファイルだけを取り出して、自分のプロジェクトに入れて使えばいいのです。
私はまた、このクラスを単独で使用する方法を示すサンプルプロジェクトも作成しました。興味のある方は、このリポジトリをご覧ください。

LongRunクラスをもっと手軽に使いたい方へ。(2021/12/14追記)

LongRunクラスによって、面倒な長時間処理の制御がかなり楽になったと思うのですが、それでも複雑なのは変わりません。そんなに細かい制御を必要としない長時間の処理なら、LongRunクラスに関数を渡すだけの方が楽なのでは?
ということで、LongRun.tsにexecuteLongRun()というグローバル関数を用意し、関数名を渡すだけで1つ1つ処理し、処理する回数も指定できるようにしました。(提案してくれたFabriceに感謝!)
この関数には、オプションで、パラメータ、初期化する関数、終了時に実行される関数を渡すことができます。
以下はそのサンプルです。

import {executeLongRun, LongRun} from "../LongRun";function LongRunTest() {
const params = [];
params.push("param1");
params.push("param2");
const loopCount = 100;
// execute the long-run task
executeLongRun("main", loopCount, params, "initializer", "finalizer" );
}
// This function will be executed on first or restart. (optional)
function initializer(startIndex: number, params: string[]){
// Do some initial processing.
}
// This function will be executed on each time.
function main(index: number, params: string[]){
// Do some main processing.
}
// This function will be called on interruption or when all processing is complete. (optional)
function finalizer(isFinished: boolean, params: string[]){
// Do some final processing.
}

動作サンプルは、リポジトリのTest2.tsをご覧ください。
executeLongRun()を使用して、異なる長時間処理を同時に実行することはできないことに注意してください

結論

6分制限の問題は苦痛ですが、用意された仕組みを使えば解決できます。また、LongRunクラスのような便利なクラスもあります。Google Apps Scriptを活用して、長時間の処理を実現しましょう!👍

--

--

インクルキャット JP

情報格差の壁に挑む(挑みたい)個人アプリ開発者。