Mihata
仕事効率化(DX)2026.09.13

GASの実行時間超過エラーの原因と対処5手順|6分制限を回避する

Google Apps Script(GAS)で作った自動化が、ある日突然 Exceeded maximum execution time(最大実行時間を超えました)で止まる。昨日まで動いていたのに、行が増えただけで落ちる——スプレッドシート運用を GAS で支えている現場で、いちばんよく起きるトラブルです。

このエラーは「スクリプトが壊れた」わけではありません。1回の実行に使える時間の上限を、処理が超えてしまったというだけの話です。だから直し方も、原因の系統ごとにほぼ決まっています。この記事では公式ドキュメントの数値に当たりながら、切り分けの順番と恒久対策を整理します。

結論:実行時間の超過は「上限」「呼び出し回数」「データ量」の3系統

原因は無数にあるように見えて、実際には次の3系統に束ねられます。上から順に潰していくのが最短です。

  • 系統A:上限そのものに当たっている — 1回の実行時間や、トリガーの1日あたり合計時間という公式の上限に到達している。
  • 系統B:呼び出し回数が多すぎる — スプレッドシートを1セルずつ読み書きしている、ループの中で毎回 getRange()UrlFetchApp を叩いている。実行時間超過の大半はここです。
  • 系統C:データ量が増えて線形に伸びた — ロジックは同じなのに、行数が2倍になって時間も2倍になり、上限をまたいだ。

「昨日まで動いていた」なら C、「作った直後から重い」なら B、「時間帯によって落ちたり落ちなかったりする」なら A を最初に疑ってください。

まず1分で確認する3点

コードを触る前に、データを壊さないための確認を先にします。

  1. 書き込み系の処理が途中で止まっていないか。タイムアウトは処理の途中で強制終了するため、「半分だけ書き込まれたシート」「重複して送られたメール」が残ることがあります。実行ログ(Apps Script エディタの「実行数」)で、どこまで進んだかを先に確認します。
  2. どの関数が落ちたか。実行数の画面で、落ちた関数名と実行の種類(手動 / トリガー)、所要時間を控えます。所要時間がきっかり上限に張り付いていればタイムアウト、それより短ければ別のエラーです。
  3. 同じ関数が多重に走っていないか。1分おきトリガーなどで、前の実行が終わる前に次が始まっていることがあります。この状態では1本あたりの処理も遅くなります。

ここまでで「落ちた関数」と「所要時間」が分かれば、あとは原因の特定に進めます。

公式の上限はいくつか(アカウント種別で違うのは合計時間のほう)

Google の公式ドキュメントには、Apps Script の上限が表で公開されています。実行時間に関わる主な数値は次のとおりです(Quotas for Google Services|Google Apps Script 公式ドキュメント)。

  • Script runtime(スクリプトの実行時間):6 min / execution — 無料の個人アカウントでも Google Workspace アカウントでも同じ6分です。「Workspace なら30分使える」という話をときどき見かけますが、公式の表は両方とも 6 min / execution と記載されています。
  • Custom function runtime(カスタム関数の実行時間):30 sec / execution — シートのセルに =MYFUNC() と書いて呼ぶ自作関数は、6分ではなく30秒です。ここを混同していると「同じコードなのにセルからだと落ちる」が起きます。
  • Triggers total runtime(トリガーの合計実行時間):個人アカウント 90 min / day、Google Workspace 6 hr / dayアカウント種別で差が出るのはこちらです。1本ずつは6分以内でも、1日の合計がこの枠を超えると以降のトリガーが動かなくなります。
  • Simultaneous executions per user(同時実行数):30 / user
  • Triggers(トリガー数):20 / user / script
  • URL Fetch calls:個人 20,000 / day、Workspace 100,000 / day — 外部APIを叩く自動化はこちらの枠も見ておきます。

なお、この表の数値は Google 側で改定されることがあります。数字を根拠に設計を決めるときは、記事ではなく上記の公式ページを都度開いて確認してください。

原因別の対処5手順

手順1:まず「上限に当たっているのか」を確定させる

落ちた関数の先頭で開始時刻を取り、要所で経過時間をログに出します。

const start = Date.now(); …処理… console.log((Date.now() - start) / 1000 + '秒');

これで「5分50秒あたりで毎回落ちる」なら、実行時間の上限に当たっているのが確定します。あわせて、トリガー実行だけが落ちるのか、手動実行でも落ちるのかを分けて見ます。手動では通るのにトリガーだけ止まる場合は、1回の6分ではなく1日の合計実行時間(90分 / 6時間)を使い切っている可能性が高いです。

ここで直らない(=所要時間が上限よりずっと短いところで落ちている)なら、原因は実行時間ではありません。権限エラーやAPIのエラーを疑ってください。

手順2:1セルずつの read / write を一括読み書きに置き換える

実行時間超過でいちばん多いのがこれです。公式のベストプラクティスは、読み書きを交互に繰り返すことを明確に「遅い」としており、「すべてのデータを1回の命令で配列に読み込み、配列上で処理し、1回の命令で書き出す」ことを推奨しています。同ページの例では、セルごとに処理した場合の約70秒が、一括処理では約1秒になったと示されています(Best Practices|Google Apps Script 公式ドキュメント)。

具体的には、ループの中の getRange(i, 1).getValue() / setValue() を消します。

  • 読み込みは const values = sheet.getDataRange().getValues(); で二次元配列に一括取得する。
  • 加工はすべて JavaScript の配列操作で行い、その間はシートに触らない。
  • 書き戻しは sheet.getRange(2, 1, values.length, values[0].length).setValues(values); で一括。
  • 背景色や書式も同じで、setBackgroundColor() の繰り返しではなく setBackgrounds(colors) にまとめる。
  • ループの中で SpreadsheetApp.flush() を呼んでいる場合は外す(毎回サーバーと往復するため極端に遅くなります)。

1,000行を1セルずつ触っていた処理が、この置き換えだけで数十秒から1〜2秒になるのは珍しくありません。ここで所要時間が桁で縮まらないなら、ボトルネックはシートの読み書きではなく、外部サービスの呼び出しかデータ量です。

手順3:ループの中の「毎回やらなくていい処理」を外へ出す

公式も「Apps Script の中で完結する処理は、Google のサーバーや外部サーバーからデータを取ってくるより速い」と明記しています。ループの中に紛れている次のような処理を、ループの外へ移します。

  • SpreadsheetApp.getActiveSpreadsheet() / getSheetByName() の再取得。1回だけ取って変数に持つ。
  • 行ごとの UrlFetchApp.fetch()。まとめて取れるAPIなら fetchAll() を使うか、取得結果を Cache Service に入れて使い回す。公式のベストプラクティスでも、繰り返し取得するデータのキャッシュが推奨されています。
  • 行ごとの GmailApp / DriveApp 呼び出し。検索条件をまとめ、取得は1回にする。
  • ループ内の Utilities.sleep()。100行に1秒入れれば、それだけで100秒消えます。

ここまでで6分を切らないなら、処理そのものが1回の実行に収まらない量になっています。手順4へ進みます。

手順4:バッチ処理+継続トリガーで分割実行にする

データ量が増えて線形に伸びたケースは、速くするのではなく「途中で中断して、続きから再開する」形に作り替えるのが正攻法です。手順は次のとおりです。

  1. 進捗を記録する場所を決めるPropertiesService.getScriptProperties() に「次に処理を始める行番号」を保存します(シートの管理用セルでも可)。
  2. 関数の冒頭で開始時刻を取るconst start = Date.now();
  3. ループの中で残り時間を見るif (Date.now() - start > 4.5 * 60 * 1000) { ... } のように、6分ではなく4〜5分で自主的に打ち切る閾値を置きます。上限ぎりぎりを狙うと、書き込みの途中で強制終了されます。
  4. 打ち切るときに、次の開始位置を保存して継続トリガーを作るScriptApp.newTrigger('main').timeBased().after(60 * 1000).create(); で「1分後に自分をもう一度呼ぶ」トリガーを立てて return します。after(durationMilliseconds) は「現在時刻から最短でどれだけ後に実行するか」を指定するメソッドとして公式リファレンスに定義されています(ClockTriggerBuilder|Google Apps Script リファレンス)。
  5. 最後まで終わったら、進捗をリセットし、自分が作った継続トリガーを削除するScriptApp.getProjectTriggers() で走査して deleteTrigger() します。ここを忘れるとトリガーが溜まり、上限(20 / user / script)に当たって新しいトリガーが作れなくなります。

この形にすると、1回の実行は必ず5分以内で終わり、処理量が増えても実行回数が増えるだけになります。ただしトリガーの1日合計(個人90分 / Workspace 6時間)は消費し続けるので、無限に伸ばせるわけではありません。分割したのに翌日から動かなくなった、という場合はこの枠を見てください。

手順5:重い集計はシート側の関数に寄せる

GAS で行を回して集計している処理は、そもそもスプレッドシートの関数にやらせたほうが速いことが多いです。

  • 合計・件数・平均 → QUERY / SUMIFS / COUNTIFS を集計用シートに置き、GAS は結果の範囲を1回読むだけにする。
  • 突き合わせ → 行ごとの検索ループをやめ、VLOOKUP / XLOOKUP の列を作って一括で getValues()
  • 絞り込み → GAS 側の filter の前に FILTER 関数で対象行を減らしておく。

ただしこの寄せ方は、シート側が重くなるとかえって遅くなります。関数が増えすぎてシート自体が開かない状態なら、先にスプレッドシートが重い・開かない原因と軽くする対処を片付けてください。シートの再計算が遅いままだと、GAS を分割しても改善しません。

なお、カスタム関数(セルに書く自作関数)で重い処理をしている場合は、30秒の壁は手順4の分割では回避できません。カスタム関数をやめ、メニューやトリガーから実行する通常の関数に作り替えるのが唯一の道です。

毎週のように落ちるなら、GASの延命ではなく置き換えの時期

ここまでの手順は、ほとんどの「実行時間を超過しました」を実際に止められます。ただし、注意しておきたい傾向があります。

一括読み書きに直し、分割実行を入れ、シート側に寄せて——それでも半年後にまた落ちる。落ちるたびに閾値を下げ、トリガーを足し、進捗管理のコードが増えていく。この状態になっているとき、起きているのは不具合ではなく「データ量と業務が、スプレッドシート+GAS の器を超えた」という設計の話です。

判断の目安は次のあたりです。

  • タイムアウト対応が月1回以上発生している。
  • 分割実行のための進捗管理コードが、本来やりたい処理より長くなっている。
  • トリガーの1日合計時間が枠に迫っていて、処理を増やす余地がない。
  • 作った本人しか直せず、その人が対応するたびに他の仕事が止まっている。

GAS の改修を外注している場合は、延命1回あたりの費用も並べてみてください。目安はGAS開発を外注する場合の費用相場にまとめています。年に数回の改修費が積み上がっているなら、作り替えたほうが安く済む領域に入っていることがあります。

正直なところ、GAS で十分な業務のほうが多いです。メールの自動送信のような定型処理は、GASでスプレッドシートからメールを自動送信する方法の範囲で長く安定して動きます。作り替えを考えるべきなのは、データ量が右肩上がりで、しかも止まると業務が止まる処理だけ。どちらなのか切り分けたい段階でも、現在のスクリプトと処理量を見れば判断はできます。

スプレッドシートでの管理そのものをどこまで続けるかについては、スプレッドシート管理の限界と、アプリ化を考える基準もあわせてご覧ください。

まとめ

  • 1回の実行時間の上限は公式表で 6 min / execution。個人アカウントと Google Workspace で差はない。
  • 差が出るのはトリガーの1日合計(個人 90 min / day、Workspace 6 hr / day)。トリガーだけ止まるならこちらを疑う。
  • セルに書くカスタム関数は30秒。分割実行では回避できない。
  • まず getValues() / setValues() の一括読み書きに直す。ここで大半が解決する。
  • それでも収まらなければ、4〜5分で自主的に打ち切り、after() の継続トリガーで続きから再開する形に作り替える。終了時のトリガー削除を忘れない。
  • タイムアウト対応が月1回以上続くなら、直す対象はコードではなく仕組みのほう。

よくある質問

GASの実行時間の上限は6分ですか?Google Workspaceなら伸びますか?

公式の割り当て表では、スクリプトの実行時間は個人アカウント・Google Workspace のいずれも 6 min / execution と記載されており、アカウント種別による差はありません。種別で差があるのはトリガーの合計実行時間で、個人アカウントが 90 min / day、Google Workspace が 6 hr / day です。

手動実行では通るのに、トリガーからだけタイムアウトするのはなぜですか?

1回あたりの6分ではなく、トリガーの1日あたり合計実行時間を使い切っている可能性があります。個人アカウントは1日90分、Google Workspace は1日6時間が上限です。1本ずつが6分以内でも、実行回数が多ければこの枠に先に当たります。

セルに書いた自作関数が30秒で止まります。分割すれば直りますか?

直りません。カスタム関数の実行時間の上限は 30 sec / execution で、通常の関数の6分とは別枠です。継続トリガーによる分割実行はカスタム関数では使えないため、カスタム関数をやめてメニューや時間主導トリガーから呼ぶ通常の関数に作り替える必要があります。

一括読み書きに直すと、どれくらい速くなりますか?

公式のベストプラクティスでは、セルごとに読み書きを繰り返す処理に約70秒かかったものが、配列への一括読み込みと一括書き出しに変えて約1秒になった例が示されています。実際の倍率は処理内容によりますが、ループ内の getValue / setValue を getValues / setValues に置き換えるのが最初に効く対処です。

継続トリガーを使った分割実行で気をつけることは?

打ち切りの閾値を6分ぎりぎりではなく4〜5分に置くこと、次の開始位置を PropertiesService などに保存すること、処理が全部終わったら自分で作ったトリガーを deleteTrigger で消すことの3点です。削除を忘れるとトリガーが溜まり、上限の 20 / user / script に当たって新規作成ができなくなります。

まずはお気軽にご相談ください

AI・IT・デザインに関するお悩みやご相談、お見積りのご依頼など、
どんなことでもお気軽にお問い合わせください。

お問い合わせ