「さっきまで動いていたスクリプトが、急にエラーで止まった……」そんな経験、ありませんか?Google Apps Script(以下GAS)を使い込んでいくと、ほぼ確実にぶつかるのが実行回数の上限や同時実行の制限です。とくに2024年10月ごろからは、実際には同時アクセスしていないのに「Too many simultaneous invocations」が頻発するという報告が世界中の開発者コミュニティで相次ぎ、Google Issue Trackerにも多数のバグレポートが寄せられました。
この記事では、GASの制限にまつわるエラーの原因を7つに分類し、それぞれの具体的な対処法をコード例つきで解説します。初心者の方にも理解できるようかみ砕いて説明しつつ、上級者が現場で即使えるテクニックまで網羅しました。さらに、2026年1月末に完了したRhinoランタイム廃止の影響や、最新の公式クォータ情報も織り交ぜています。
- GASで発生する主要な制限エラー7種類の原因と、それぞれに対応した具体的な回避策の全体像
- LockServiceによる排他制御、指数バックオフ、トリガーチェーンなど実務で使える実装パターン
- 2026年最新のクォータ一覧表と、無料アカウント・Workspaceアカウントの違いの整理
- そもそもGASの「制限」とは何か?なぜ存在するのか?
- GASで遭遇する7つの主要な制限エラーと原因
- エラー1Exceeded maximum execution time
- エラー2Script invoked too many times per second
- エラー3Too many simultaneous invocations
- エラー4Service invoked too many times in a short time
- エラー5Service using too much computer time for one day
- エラー6Limit exceeded(各サービス固有の日次クォータ超過)
- エラー7Too many executions running simultaneously for this Apps Script project
- 2026年最新のGASクォータ一覧
- 実行時間6分の壁を突破する「トリガーチェーン」の実装
- 同時実行エラーを防ぐLockServiceの正しい使い方
- API呼び出し過多を回避する指数バックオフとキャッシュ戦略
- カスタム関数の呼び出し回数を減らすスプレッドシート設計
- 2026年に押さえておくべきGASの最新動向
- 情シス歴10年超の現場で培った「制限エラーを出さない」スクリプト設計術
- トリガーが「静かに死ぬ」問題とその根本的な対策
- 読み書きの「交互実行」が引き起こす隠れた速度低下
- Sheets APIとSpreadsheetAppの使い分け判断基準
- 残トリガーの自動クリーンアップとゴミ掃除の実装
- 「SpreadsheetApp.flush()」を乱用してはいけない理由
- エラー発生時のSlack・チャット通知を自動化するコード
- クォータ残量を事前にチェックして「あと何回使えるか」を把握する方法
- onEdit・onChangeトリガーの「引数が取れない」問題の正しい回避法
- 複数のスプレッドシートをまたぐ処理で起きやすい落とし穴
- 情シス視点で見た「GASに任せるべき処理」と「任せてはいけない処理」の線引き
- ぶっちゃけこうした方がいい!
- AppsScriptの実行回数や同時実行の制限に関するよくある質問
- 今すぐパソコンやスマホの悩みを解決したい!どうしたらいい?
- まとめ
そもそもGASの「制限」とは何か?なぜ存在するのか?
GASはGoogleのサーバー上で動作するスクリプト環境です。世界中のユーザーが同じインフラを共有しているため、一部のスクリプトがサーバーリソースを食い尽くさないよう、Googleはクォータ(Quotas)と呼ばれる利用上限を設けています。これはGASに限った話ではなく、Google Cloud全体で採用されているリソース保護の仕組みです。
たとえば、あなたが書いたスクリプトが1日に何十万回もAPIを叩いてしまうと、同じサーバーを使っている他のユーザーに迷惑がかかりますよね。クォータはそうした事態を防ぐための「交通ルール」のようなものだと考えるとわかりやすいでしょう。そしてこの制限は、無料のGoogleアカウント(gmail.com)とGoogle Workspaceアカウントで異なる値が設定されていることも重要なポイントです。
GASで遭遇する7つの主要な制限エラーと原因
GASを使っていると遭遇するエラーメッセージにはいくつかのパターンがあります。ここでは代表的な7つを取り上げ、それぞれの意味と発生条件を整理します。
エラー1Exceeded maximum execution time
もっとも有名な制限エラーです。GASの1回のスクリプト実行は最大6分で強制終了されます。以前はGoogle Workspace(旧G Suite)のBusinessプランやEnterpriseプランで30分まで許容されていた時期もありましたが、現在はアカウント種別を問わず一律6分が上限です。スプレッドシートの大量データを1回のループで処理しようとしたり、外部APIへのリクエストを何百回も連続で送ったりすると、この壁にぶつかります。
エラー2Script invoked too many times per second
1秒あたりのスクリプト呼び出し回数が上限を超えた場合に発生します。とくにスプレッドシートのカスタム関数を大量のセルで同時に使っているケースで頻発します。たとえば、A1からA5000まですべてのセルに
=MY_CUSTOM_FUNCTION(B1)
のような数式が入っていると、シート再計算時に一斉に呼び出しが走り、この制限に引っかかるわけです。
エラー3Too many simultaneous invocations
同時実行数の制限に達したときのエラーです。公式ドキュメントによれば、1ユーザーあたり30件、1スクリプトあたり1,000件が上限とされています。ところが2024年10月以降、実際には同時実行していない状況でもこのエラーが大量発生する事象が世界中で報告されました。これはGoogle側のインフラに起因する問題と考えられており、Issue Trackerでも多くの開発者が声を上げています。
エラー4Service invoked too many times in a short time
特定のGoogleサービス(Gmail、Calendar、Driveなど)を短時間に集中して呼び出した場合に表示されます。公式ドキュメントでは対処法として
Utilities.sleep(1000)
を挟むことが推奨されています。1日の総リクエスト数には達していなくても、バースト的なアクセスはこのエラーを引き起こします。
エラー5Service using too much computer time for one day
トリガーで自動実行しているスクリプトの1日あたりの総実行時間が上限を超えた場合のエラーです。無料アカウントでは1日90分、Workspaceアカウントでは1日6時間が上限です。手動実行は対象外ですが、時間主導型トリガーやイベントトリガーで頻繁にスクリプトを回していると、この制限に到達します。
エラー6Limit exceeded(各サービス固有の日次クォータ超過)
メール送信数、スプレッドシートの作成数、UrlFetchAppのリクエスト数など、サービスごとの日次クォータに到達した場合に表示されます。たとえばUrlFetchAppは無料アカウントで1日20,000回、Workspaceアカウントで100,000回が上限です。Gmail送信は無料アカウントで1日100通、Workspaceアカウントで1,500通が目安となっています。
エラー7Too many executions running simultaneously for this Apps Script project
これはエラー3と似ていますが、プロジェクト単位での制限を指しています。2026年2月にもAutocratなどの人気アドオンでこのエラーが大量発生した報告があり、アドオン利用者全体で同時実行枠を共有している場合にとくに影響が大きくなります。
2026年最新のGASクォータ一覧
制限値を正確に把握しておくことが、安定したスクリプト運用の第一歩です。以下の表は、公式ドキュメント(2025年12月更新分)に基づく主要な制限値をまとめたものです。なお、Googleはこれらの値を予告なく変更する可能性があると明記しているため、定期的な確認をおすすめします。
| 項目 | 無料アカウント(gmail.com) | Google Workspaceアカウント |
|---|---|---|
| 1回の実行時間上限 | 6分 | 6分 |
| カスタム関数の実行時間 | 30秒 | 30秒 |
| アドオンの実行時間 | 30秒 | 30秒 |
| トリガーによる1日の総実行時間 | 90分 | 6時間 |
| 同時実行数(ユーザーあたり) | 30件 | 30件 |
| 同時実行数(スクリプトあたり) | 1,000件 | 1,000件 |
| UrlFetchApp呼び出し回数(1日) | 20,000回 | 100,000回 |
| メール送信数(1日) | 100通 | 1,500通 |
| プロパティストア容量 | 500KB | 500KB |
| 1プロパティの最大サイズ | 9KB | 9KB |
Workspace Studio(Gemini Alphaプログラム)で動作するアドオンについては、実行タイムアウトが2分に設定されている点も押さえておきましょう。また、トライアルアカウントにはさらに厳しい制限が適用され、累計100ドル以上の支払いかつ60日以上経過するまで上限が引き上げられないという条件があります。
実行時間6分の壁を突破する「トリガーチェーン」の実装
6分の実行時間制限は、GAS開発者にとってもっとも身近な壁です。この制限を回避する王道テクニックがトリガーチェーン(処理分割+自動再実行)です。考え方はシンプルで、「6分以内に処理を中断し、次回のトリガーで続きから再開する」というものです。
基本的な仕組み
まず、処理の開始時に
new Date().getTime()
で開始時刻を記録します。ループの各イテレーションで経過時間をチェックし、たとえば5分(300,000ミリ秒)を超えそうになったら処理を中断します。中断する前に、どこまで処理が終わったかをPropertiesService(スクリプトプロパティ)に保存し、
ScriptApp.newTrigger()
で1分後に同じ関数を再実行するトリガーを作成します。
次回の実行時には、スクリプトプロパティから前回の処理位置を読み込み、そこから続行します。すべての行を処理し終えたらスクリプトプロパティをクリアし、不要なトリガーも削除して完了です。
コード例
以下は、スプレッドシートの行を順に処理し、タイムアウト前に自動で次回予約するパターンの骨格です。
function processRows() {
var startTime = new Date().getTime();
var props = PropertiesService.getScriptProperties();
var startRow = Number(props.getProperty('lastRow')) || 2;
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Data');
var lastRow = sheet.getLastRow();
var data = sheet.getRange(startRow, 1, lastRow - startRow + 1, 3).getValues();
for (var i = 0; i < data.length; i++) {
// 5分経過チェック
if (new Date().getTime() - startTime > 300000) {
props.setProperty('lastRow', String(startRow + i));
ScriptApp.newTrigger('processRows')
.timeBased()
.after(60000)
.create();
return;
}
// ここに1行ぶんの処理を書く
}
// 全行処理完了
props.deleteProperty('lastRow');
}
ポイントは、タイムアウト判定を6分ギリギリではなく余裕をもって5分程度に設定することです。処理の後始末やトリガー作成にも時間がかかるため、バッファを確保しておかないと中途半端な状態で強制終了される危険があります。
同時実行エラーを防ぐLockServiceの正しい使い方
複数のトリガーやユーザー操作が同じスクリプトを同時に実行すると、データの競合や「Too many simultaneous invocations」エラーが発生します。これを防ぐのがLockServiceによる排他制御です。
3種類のロックを使い分ける
GASのLockServiceには、用途に応じて3つのロックが用意されています。
LockService.getScriptLock()
はスクリプト全体に対するロックで、どのユーザーが実行しても同時に1つしか動かなくなります。
LockService.getDocumentLock()
はドキュメント単位のロックで、同じスプレッドシートに紐づくスクリプトの同時実行を防ぎます。
LockService.getUserLock()
はユーザー単位のロックで、同一ユーザーの重複実行のみをブロックします。
実装のポイント
ロックを取得したら、必ずfinallyブロックでロックを解放することが鉄則です。エラーが発生してロックが解放されないままだと、後続のスクリプトがすべてブロックされてしまいます。また、
tryLock()
にはタイムアウト時間をミリ秒で指定できるので、待機時間を適切に設定しましょう。
function safeProcess() {
var lock = LockService.getScriptLock();
try {
if (!lock.tryLock(30000)) {
Logger.log('ロック取得に失敗しました。別の処理が実行中です。');
return;
}
// ここにメインの処理を記述
SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Log')
.appendRow);
} catch (e) {
Logger.log('エラー発生: ' + e.message);
} finally {
lock.releaseLock();
}
}
2024年10月に多発した「Too many simultaneous invocations: Spreadsheets」エラーに対しても、この排他制御を導入することでエラー発生率を大幅に下げられたという報告が複数あがっています。根本的にはGoogle側のインフラの問題ですが、自衛策として有効です。
API呼び出し過多を回避する指数バックオフとキャッシュ戦略
「Service invoked too many times in a short time」への対策として、2つのテクニックを紹介します。
指数バックオフ(Exponential Backoff)
APIリクエストが一時的に失敗した場合、すぐにリトライするのではなく、待機時間を指数関数的に増やしながら再試行する手法です。1回目は500ミリ秒、2回目は1秒、3回目は2秒……と倍々に伸ばしていきます。さらにランダムな揺らぎ(ジッター)を加えることで、複数のスクリプトが同時にリトライして再び衝突する「サンダリングハード問題」を緩和できます。
function fetchWithBackoff(url) {
var maxRetries = 5;
var delay = 500;
for (var n = 0; n < maxRetries; n++) {
try {
return UrlFetchApp.fetch(url).getContentText();
} catch (e) {
if (n === maxRetries - 1) throw e;
var jitter = Math.random() * delay * 0.5;
Utilities.sleep(delay + jitter);
delay *= 2;
}
}
}
CacheServiceの活用
同じデータを何度もAPIから取得している場合は、CacheServiceを使って結果をキャッシュするのが効果的です。キャッシュの有効期間は最大6時間(21,600秒)まで設定でき、頻繁に変わらないデータであれば大幅にAPI呼び出し回数を削減できます。ただし、キャッシュはあくまで一時的な保存なので、データの鮮度が重要な場面では有効期限を短めに設定するか、キャッシュを使わない判断も必要です。
カスタム関数の呼び出し回数を減らすスプレッドシート設計
「Script invoked too many times per second」は、カスタム関数の設計を見直すだけで劇的に改善できるケースが多いです。
範囲ごとに一括処理する設計に切り替える
セルごとに
=MY_FUNC(A1)
と書くのではなく、範囲を渡して一括で結果を返す設計にしましょう。GASのカスタム関数は、引数として範囲を受け取ると2次元配列として処理できます。
=MY_FUNC(A1:A5000)
のように書けば、呼び出し回数は5,000回からたった1回に激減します。
カスタム関数が2次元配列を返せば、スプレッドシートは自動的に結果を複数セルに展開してくれます(スピル機能)。これはGASの公式ガイドでも推奨されている手法で、パフォーマンスと安定性の両方が大きく向上します。
中間計算の結果を活用する
複数のセルで同じ計算結果を参照しているなら、一箇所でカスタム関数を実行し、他のセルはその結果を通常のセル参照で読み取る設計にしましょう。これだけでカスタム関数の呼び出し回数を大幅に抑えられます。
2026年に押さえておくべきGASの最新動向
Rhinoランタイムの廃止が完了
2025年2月にRhinoランタイムの非推奨が発表され、2026年1月31日をもってRhino上で動作するスクリプトは実行できなくなりました。V8ランタイムへの移行がまだの方は、古いスクリプトが突然動かなくなっている可能性があります。とくにインターネット上からコピーしてきた古いコードには、
for each...in
構文やconstの再代入など、V8では動かない書き方が含まれていることがあるので注意してください。
Granular OAuthの導入
Apps Script IDEでの実行時に、きめ細かいOAuth権限の同意画面が導入されました。ユーザーが個別のスコープを選択して承認できるようになったことで、必要以上の権限を求めないスクリプト設計が求められるようになっています。今後、アドオンやトリガー実行にも順次展開される予定です。
Vertex AI Advanced Serviceの追加
GASからVertex AI APIを直接呼び出せるAdvanced Serviceが追加されました。AIモデルによるテキスト生成や画像生成をGASのワークフローに組み込めるようになりましたが、APIリクエストにはそれ自体のクォータが適用されるため、呼び出し回数の管理はこれまで以上に重要になっています。
情シス歴10年超の現場で培った「制限エラーを出さない」スクリプト設計術
ここからは、公式ドキュメントや一般的な技術ブログにはなかなか書かれていない、情報システム部門で10年以上GASの運用・保守に携わってきた視点からの実践知をお伝えします。制限エラーの対処法は世の中にいろいろ出回っていますが、「なぜか本番環境でだけ動かない」「テストでは問題なかったのに運用1ヶ月後に突然止まった」といった、記事を読むだけでは解決できないリアルな問題にこそ、現場の知見が必要です。
正直なところ、GASの制限エラーの大半は「設計段階のミス」から生まれます。コードの書き方が悪いというより、そもそもスクリプトを動かす前に考慮すべきことを飛ばしているのが原因です。私自身、何度も痛い目に遭ってきたからこそ断言できます。以下の内容は、そうした失敗から学んだ「転ばぬ先の杖」です。
本番投入前に必ずやるべき「制限シミュレーション」の手順
テスト環境で10行のデータを処理して「動いた、OK」と安心していませんか? 本番では数千行、数万行のデータが流れてくることはザラです。私が担当するプロジェクトでは、本番投入前に必ず「制限シミュレーションシート」を作ります。やり方はこうです。
まず、スクリプトが扱うデータの最大行数・最大列数・1行あたりの処理時間を見積もります。たとえば1行の処理に平均0.5秒かかるなら、6分(360秒)で処理できるのは最大720行です。ここにAPI呼び出しの待機時間やスプレッドシートへの書き込み時間を加味すると、実質的には400〜500行が安全圏になります。この数字を超える可能性があるなら、最初からトリガーチェーンを組み込んだ設計にするべきです。「今は行数が少ないから大丈夫」と思ってあとから対応しようとすると、既存コードの大規模な改修が必要になり、結果的に工数が倍以上に膨らみます。
シミュレーションを数値で残しておくと、あとから「なぜこの設計にしたのか」をチームに説明するときにも役立ちます。ドキュメントとして残しておくのは面倒に感じるかもしれませんが、半年後の自分を救うのはこのメモだと断言します。
getValue/setValueを1回でもループ内で使ったら負け
これは強い言い方ですが、ループの中で
getValue()
や
setValue()
を使っている時点で、そのスクリプトは遅かれ早かれ制限に引っかかると思ってください。Google公式のベストプラクティスでも明記されていますが、スプレッドシートへの読み書きはサーバーとの通信を伴う非常にコストの高い操作です。ベンチマークによれば、5,000セルを1つずつ
setValue()
で書き込むと約15秒かかるのに対し、
setValues()
で一括書き込みすれば2秒未満で完了します。つまり7倍以上の速度差です。
以下は、ダメなパターンと正しいパターンの比較コードです。
// ダメなパターンループ内でsetValue
function badPattern() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Data');
var data = sheet.getRange('A1:A1000').getValues();
for (var i = 0; i < data.length; i++) {
var result = data * 2;
sheet.getRange(i + 1, 2).setValue(result); // 毎回サーバーと通信!
}
}
// 正しいパターン配列にためて一括書き込み
function goodPattern() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Data');
var data = sheet.getRange('A1:A1000').getValues();
var output = ;
for (var i = 0; i < data.length; i++) {
output.push * 2]);
}
sheet.getRange(1, 2, output.length, 1).setValues(output); // 1回で完了!
}
この違いだけで、処理時間が数十秒から1秒以下に劇的に変わります。制限に引っかからないための最初の一歩は、実はこのシンプルな書き方の変更なんです。
トリガーが「静かに死ぬ」問題とその根本的な対策
GASを本番運用していて、いちばん怖いのがこれです。トリガーがエラーも出さずに、ただ動かなくなる。実行ログにも何も残っていない。でもスプレッドシートにはフォームの回答が入っている。つまりトリガーは「発火すらしなかった」ということです。
この問題は、世界中の開発者コミュニティで繰り返し報告されています。原因はGoogle側のインフラにある場合がほとんどですが、ユーザー側でも以下のような要因が絡むことがあります。
まず、認可(OAuth)の失効。スクリプトに新しいサービス(DriveAppやGmailAppなど)を追加した際、ユーザーが再認可していないと、トリガーからの実行時に認可ダイアログを表示できないため、何も起きずに終わります。次に、Granular OAuth導入後のスコープ不足。2025年から段階的に導入されたきめ細かいOAuth同意画面では、ユーザーが一部のスコープだけを許可することが可能になりました。その結果、スクリプトが必要とする権限が足りず、トリガーが沈黙するケースが増えています。
対策としてもっとも確実なのは、トリガーの死活監視スクリプトを別途用意することです。以下のコードは、指定した関数のトリガーが過去1時間以内に正常に動作したかをチェックし、動いていなければ自分にアラートメールを送る仕組みです。
function monitorTriggerHealth() {
var functionName = 'processFormSubmit'; // 監視対象の関数名
var props = PropertiesService.getScriptProperties();
var lastRun = props.getProperty('lastRun_' + functionName);
var now = new Date().getTime();
var oneHour = 60 * 60 * 1000;
if (lastRun && (now - Number(lastRun)) > oneHour) {
MailApp.sendEmail(
Session.getEffectiveUser().getEmail(),
'【警告】GASトリガー停止の可能性',
functionName + ' が過去1時間以内に実行された記録がありません。\n'
+ '最終実行: ' + new Date(Number(lastRun)).toLocaleString('ja-JP') + '\n'
+ '確認してください。'
);
}
}
// 監視対象の関数の冒頭にこれを入れておく
function processFormSubmit(e) {
PropertiesService.getScriptProperties()
.setProperty('lastRun_processFormSubmit', String(new Date().getTime()));
// 以下、メインの処理
}
monitorTriggerHealth
を15分おきの時間主導型トリガーで動かしておけば、もしメインの処理が沈黙しても気づけます。この「番犬スクリプト」を入れるだけで、月曜朝に出社して「金曜から止まってました」と言われる最悪のシナリオを防げます。
読み書きの「交互実行」が引き起こす隠れた速度低下
GASのスプレッドシートサービスには、内部的に読み取りキャッシュと書き込みキャッシュが存在します。これを知らないと、コードの見た目は正しいのにやたら遅い、という謎の現象にハマります。仕組みはこうです。書き込み(
setValue
など)を実行すると、GASは即座にサーバーに送るのではなく、一旦キャッシュに溜めます。しかし、その直後に読み取り(
getValue
など)を実行すると、最新のデータを返すためにキャッシュをフラッシュ(強制書き込み)してからデータを取得します。
つまり、「書く→読む→書く→読む」という交互の操作をすると、毎回キャッシュのフラッシュが発生し、キャッシュの恩恵がゼロになるのです。これが原因で、「コードは間違っていないのに6分制限に引っかかる」という事態が起きます。
解決策はシンプルです。まとめて読む→メモリ上で処理する→まとめて書く。この「サンドイッチ構造」を徹底するだけで、同じ処理が劇的に速くなります。
// 悪い例読み書きが交互
function interleaved() {
var sheet = SpreadsheetApp.getActiveSheet();
for (var i = 1; i <= 100; i++) {
var val = sheet.getRange(i, 1).getValue(); // 読む
sheet.getRange(i, 2).setValue(val * 2); // 書く → 次の読みでフラッシュ発生
}
}
// 良い例読みと書きを分離
function batched() {
var sheet = SpreadsheetApp.getActiveSheet();
var input = sheet.getRange(1, 1, 100, 1).getValues(); // まとめて読む
var output = input.map(function(row) { return * 2]; });
sheet.getRange(1, 2, 100, 1).setValues(output); // まとめて書く
}
Googleの公式ブログで紹介されたベンチマークでは、交互実行のパターンが約70秒かかっていた処理が、バッチ処理に書き換えただけで約1秒に短縮されたという結果が示されています。100行でこの差ですから、数千行になったときの影響は想像に難くないでしょう。
Sheets APIとSpreadsheetAppの使い分け判断基準
意外と知られていないのが、GASにはスプレッドシートを操作する方法が2系統あるという事実です。ひとつは
SpreadsheetApp
(組み込みサービス)、もうひとつは
Sheets.Spreadsheets
(Advanced Service、つまりSheets API)です。ベンチマーク結果によると、大量のデータを読み書きする場合はSheets APIのほうが高速です。とくに行数が多くなると差が顕著になります。
ただし、Sheets APIは設定が少し面倒で、Advanced Serviceの有効化やJSONの組み立てが必要になります。私の判断基準はこうです。
| 条件 | おすすめの方法 | 理由 |
|---|---|---|
| 処理行数が3,000行以下 | SpreadsheetApp | コードがシンプルで保守しやすい。速度差も体感しにくい。 |
| 処理行数が3,000行以上 | Sheets API | 読み書き速度に明確な差が出る。とくに読み込みは20倍以上速い場合もある。 |
| セルの書式設定も操作する | Sheets API(batchUpdate) | 書式、条件付き書式、セル結合などを一括で操作でき、APIリクエスト数を抑えられる。 |
| 簡単なフィルタや検索 | SpreadsheetApp | createTextFinderやgetDataRangeで十分。Sheets APIを持ち出す必要がない。 |
実務では「まずSpreadsheetAppで組んでみて、速度が問題になったらSheets APIに切り替える」という段階的なアプローチが現実的です。最初からSheets APIで書くと、チーム内でメンテナンスできる人が限られるリスクもあります。
残トリガーの自動クリーンアップとゴミ掃除の実装
トリガーチェーンを使っていると、実行済みのトリガーがどんどん溜まっていきます。Apps Scriptの管理画面に何百もの古いトリガーが並んでいる光景、見たことがある方も多いのではないでしょうか。これ自体で直接エラーが出ることは少ないのですが、トリガーの総数が20個を超えると新しいトリガーの作成に失敗するという報告があり、放置するとトリガーチェーン自体が壊れる原因になります。
以下のコードは、特定の関数名に紐づくトリガーをすべて削除するユーティリティです。トリガーチェーンの冒頭で古いトリガーを掃除してから新しいトリガーを作成するようにすると安全です。
function cleanupTriggers(functionName) {
var triggers = ScriptApp.getProjectTriggers();
for (var i = 0; i < triggers.length; i++) {
if (triggers.getHandlerFunction() === functionName) {
ScriptApp.deleteTrigger(triggers);
}
}
}
// トリガーチェーンの中で使う例
function processWithCleanup() {
cleanupTriggers('processWithCleanup'); // まず古いトリガーを掃除
var startTime = new Date().getTime();
// ... メインの処理 ...
if (/* まだ処理が残っている */) {
ScriptApp.newTrigger('processWithCleanup')
.timeBased()
.after(60000)
.create();
}
}
地味な処理ですが、トリガーが作成できずに処理が中断される事故を確実に防いでくれます。情シスとしては、この手の「転ばぬ先の杖」コードを入れるか入れないかで、深夜の緊急対応が発生するかどうかが変わるのです。
「SpreadsheetApp.flush()」を乱用してはいけない理由
ネット上の記事で「処理の途中で
SpreadsheetApp.flush()
を入れましょう」と紹介されていることがありますが、これを多用するとかえって処理速度が大幅に低下することを知っておいてください。
flush()
は、GAS内部の書き込みキャッシュを強制的にサーバーに送信する命令です。通常、GASはスクリプトの終了時に自動でフラッシュを行うため、手動で呼ぶ必要はありません。ただし、ユーザーに途中経過を見せたいとき(プログレスバーの更新など)や、エラー発生時にそこまでの書き込みを確定させたいときには有効です。
問題は、ループの中で毎回
flush()
を呼んでしまうケースです。こうすると、バッチ書き込みのメリットが完全に失われ、1行ずつサーバーに送信するのと同じ状態になります。使うなら「エラー直前のセーフポイント」としてピンポイントで、というのが鉄則です。
エラー発生時のSlack・チャット通知を自動化するコード
トリガーで動いているGASがエラーを起こしたとき、Googleからのメール通知に気づけないことは珍しくありません。とくにチームで運用している場合、エラー通知をSlackやGoogle Chatに飛ばす仕組みがあると、問題発見までの時間が劇的に短くなります。
function notifyError(functionName, errorMessage) {
var webhookUrl = PropertiesService.getScriptProperties()
.getProperty('SLACK_WEBHOOK_URL');
if (!webhookUrl) {
Logger.log('Webhook URLが設定されていません');
return;
}
var payload = {
text: ':rotating_light: *GASエラー発生*\n'
+ '関数名: `' + functionName + '`\n'
+ 'エラー: ' + errorMessage + '\n'
+ '発生時刻: ' + new Date().toLocaleString('ja-JP')
};
UrlFetchApp.fetch(webhookUrl, {
method: 'post',
contentType: 'application/json',
payload: JSON.stringify(payload)
});
}
// 使い方try-catchの中で呼ぶ
function myScheduledTask() {
try {
// メインの処理
} catch (e) {
notifyError('myScheduledTask', e.message);
throw e; // 再スローしてGoogleの通知メールも残す
}
}
このコードのポイントは、
throw e
で再スローしているところです。catchしたままだとGoogleの実行ログ上は「正常終了」扱いになり、あとから問題を調査するときに困ります。Slackに通知しつつ、Googleの記録にもエラーとして残す、この「二重通知」が運用上は非常に助かります。
Webhook URLはスクリプトプロパティに保存しておくと、コードに直接書かなくて済むのでセキュリティ的にも安心です。
クォータ残量を事前にチェックして「あと何回使えるか」を把握する方法
GASには残念ながら、クォータの残量を直接取得する公式APIはありません。しかし、自前でカウンターを実装することで、ある程度の予測は可能です。
function trackApiUsage(serviceName) {
var props = PropertiesService.getScriptProperties();
var today = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyy-MM-dd');
var key = 'apiCount_' + serviceName + '_' + today;
var count = Number(props.getProperty(key)) || 0;
count++;
props.setProperty(key, String(count));
return count;
}
function fetchWithTracking(url) {
var count = trackApiUsage('UrlFetchApp');
var limit = 20000; // 無料アカウントの上限
if (count >= limit * 0.9) {
Logger.log('警告: UrlFetchAppの利用回数が上限の90%に達しています ('
+ count + '/' + limit + ')');
// 必要に応じて通知を送る
}
return UrlFetchApp.fetch(url);
}
このやり方の注意点として、日付が変わるタイミングはGoogleのクォータリセット時刻とズレる可能性があることを覚えておいてください。Googleのクォータは「最初のリクエストから24時間後」にリセットされるため、日本時間の0時リセットとは限りません。あくまで目安として使い、「上限の90%に達したら警告を出す」くらいの余裕をもったしきい値を設定するのが安全です。
onEdit・onChangeトリガーの「引数が取れない」問題の正しい回避法
これは初心者がほぼ確実にハマるポイントです。スプレッドシートの
onEdit(e)
で受け取るイベントオブジェクト
e
の中身が、シンプルトリガーとインストーラブルトリガーで異なることを知らないと、「自分の環境では動くのに、他の人の環境では動かない」という状況に陥ります。
シンプルトリガー(関数名を
onEdit
にするだけの方式)では、
e.range
や
e.value
などの基本的な情報は取得できますが、認可が必要なサービス(Gmail、Driveなど)は呼び出せません。一方、インストーラブルトリガー(管理画面からトリガーを手動で設定する方式)であれば認可済みのサービスを使えますが、シンプルトリガーとイベントオブジェクトの構造が微妙に異なる場合があります。
もっとも安全な対処法は、イベントオブジェクトを使わずに、編集されたセルの情報をSpreadsheetAppから直接取得することです。
function onEditSafe(e) {
// eが存在しない場合や、テスト実行のことを考慮
var sheet, range, value;
try {
sheet = e.range.getSheet();
range = e.range;
value = e.value;
} catch (err) {
// eが不完全な場合のフォールバック
sheet = SpreadsheetApp.getActiveSheet();
range = sheet.getActiveRange();
value = range.getValue();
}
// ここからメインのロジック
if (sheet.getName() !== 'Input') return;
if (range.getColumn() !== 3) return;
// C列が編集されたときだけ処理する
Logger.log('編集されたセル: ' + range.getA1Notation() + ' 値: ' + value);
}
この
try-catch
によるフォールバックパターンは、デバッグ時に手動で関数を実行するときにも
e
がundefinedでエラーにならないので、開発効率も上がります。
複数のスプレッドシートをまたぐ処理で起きやすい落とし穴
GASで業務システムを組んでいると、「マスターシートからデータを読んで、別のシートに書き込む」という処理は日常茶飯事です。しかし、
SpreadsheetApp.openById()
を使うたびにサーバーへのリクエストが発生することを意識していない人は多いです。
たとえば、ループの中で毎回
SpreadsheetApp.openById()
を呼んでいるコードを見かけることがありますが、これは致命的です。同じスプレッドシートを開くのに何度もリクエストを投げているわけですから、無駄の極みです。ループの外で一度だけ開き、変数に格納して使い回すのが基本中の基本なのですが、焦っているときほどこの基本を忘れがちです。
// ダメな例
function crossSheetBad() {
var sourceData = SpreadsheetApp.openById('SOURCE_ID')
.getSheetByName('Data').getDataRange().getValues();
for (var i = 0; i < sourceData.length; i++) {
// 毎回開いている!
var target = SpreadsheetApp.openById('TARGET_ID').getSheetByName('Log');
target.appendRow, new Date(]);
}
}
// 正しい例
function crossSheetGood() {
var sourceSheet = SpreadsheetApp.openById('SOURCE_ID').getSheetByName('Data');
var targetSheet = SpreadsheetApp.openById('TARGET_ID').getSheetByName('Log');
var sourceData = sourceSheet.getDataRange().getValues();
var output = sourceData.map(function(row) {
return , new Date(];
});
targetSheet.getRange(
targetSheet.getLastRow() + 1, 1, output.length, output.length
).setValues(output);
}
加えて、複数のスプレッドシートを開くとそれだけで数秒〜十数秒かかることも頭に入れておきましょう。3つ以上のスプレッドシートをまたぐ処理がある場合は、処理時間のうちかなりの割合が「シートを開く」時間に食われている可能性があります。可能であれば、データを1つのスプレッドシートに集約する設計を検討したほうが結果的に速くなります。
情シス視点で見た「GASに任せるべき処理」と「任せてはいけない処理」の線引き
これは技術的なテクニックではなく、設計思想の話です。10年以上GASの運用を見てきた経験から言えるのは、GASは「接着剤」として使うべきであり、「構造材」にしてはいけないということです。
GASが得意なのは、Googleサービス同士をつなぐ軽量な自動化です。「フォームの回答をスプレッドシートに記録してSlackに通知する」「カレンダーの予定をもとにリマインドメールを送る」といった処理は、GASの真骨頂です。一方で、「数万行のデータを毎日集計してレポートを作成する」「外部の複数APIから大量のデータを取得して突合する」といった処理をGASで無理にやろうとすると、制限との闘いが永遠に続きます。
処理が以下のいずれかに該当するなら、GAS以外のツール(Google Cloud Functions、BigQuery、Pythonスクリプトなど)への移行を真剣に検討すべきです。
| 判断基準 | GASに適しているか | 代替候補 |
|---|---|---|
| 1回の処理が5分を超えることが常態化 | 適さない | Cloud Functions、Cloud Run |
| 1日のAPI呼び出しが15,000回を超える | 適さない | 専用サーバー、Cloud Functions |
| リアルタイム性が要求される(数秒以内の応答) | 適さない | Cloud Functions、Firebase |
| 処理するデータが10万行を超える | 適さない | BigQuery、Cloud Dataflow |
| Googleサービス間の連携で完結する軽量な処理 | 最適 | — |
| 週に数回、数百〜数千行のデータ処理 | 適している | — |
このテーブルは、新しいプロジェクトの設計レビューで私が実際に使っているチェックリストの簡略版です。「GASでできる」と「GASでやるべき」は全く違う、ということを常に意識しておくと、制限エラーに振り回される時間を根本的に減らせます。
ぶっちゃけこうした方がいい!
ここまで長々と技術的なテクニックをお伝えしてきましたが、正直に言います。GASの制限エラーに悩んでいる時間の8割は、「設計をやり直す」ことで消えるんです。
制限にぶつかってから「どうやって回避しよう」と考えるのではなく、最初から制限値を前提にした設計をする。これだけで、本当に世界が変わります。私が担当するプロジェクトでは、GASを書き始める前に必ず「このスクリプトは最大何行を処理する可能性があるか」「何回APIを叩くか」「同時に何人がアクセスするか」を洗い出します。このたった3つの質問に答えるだけで、トリガーチェーンが必要かどうか、LockServiceが必要かどうか、そもそもGASで実装すべき処理かどうかが判断できます。
それから、もうひとつ大事なのは「GASに固執しすぎない」こと。GASはすごく便利なツールですが、万能じゃないです。制限に引っかかりまくっている時点で、それはGASが「この処理には自分は向いてないよ」とサインを出しているようなものです。トリガーチェーンを3段4段と重ねて、LockServiceで排他制御して、指数バックオフでリトライして……ここまでやらないと動かないスクリプトは、ぶっちゃけGASで書くべきじゃなかった、ということです。Cloud FunctionsやBigQueryに移行したほうが、結果的に開発も運用もはるかに楽になります。
ただ、逆に言えば、GASが得意な領域(Googleサービス同士の軽い連携、数百〜数千行レベルのデータ処理、定型メールの自動送信など)では、他のどのツールよりも圧倒的に手軽で速いのも事実です。コストゼロ、環境構築ゼロ、ブラウザだけで完結する。この強みは唯一無二です。
だからこそ、「GASでやるべきことはGASで、GASに向かないことは別のツールで」という使い分けの判断力が、結局いちばん大事なスキルだと思っています。制限に振り回されるのではなく、制限をよく理解した上で「ここはGASの出番だ」「ここはGASの守備範囲外だ」と冷静に判断できる人が、真の意味でGASを使いこなしている人だと、10年やってきてつくづく思います。
AppsScriptの実行回数や同時実行の制限に関するよくある質問
Google Workspaceの有料プランにすれば制限はなくなりますか?
残念ながら、有料プランでも制限は消えません。一部のクォータ(1日のトリガー総実行時間やUrlFetchAppの呼び出し回数など)はWorkspaceアカウントのほうが緩くなっていますが、1回の実行時間6分や同時実行数30件といった制限は同じです。以前は有料プランで30分まで実行できた時期もありましたが、現在は統一されています。制限の緩和ではなく、処理設計の工夫で対応するのが現実的なアプローチです。
「Too many simultaneous invocations」が同時実行していないのに出るのはなぜですか?
2024年10月以降に急増したこの現象は、Google側のインフラに起因する問題と広く認識されています。実際に同時実行していなくても発生するため、スクリプト側の対策だけでは完全には防げません。ただし、LockServiceによる排他制御を導入することでエラー発生頻度を大幅に低減できたという報告が多数あります。また、
try-catch
でエラーをキャッチし、少し待ってからリトライするロジックを入れるのも有効な自衛策です。
トリガーチェーンで作られたトリガーが溜まり続けるのは問題ないですか?
時間指定トリガーは一度実行されると無効化されますが、履歴として残り続けます。気になる場合は、スクリプト内で不要なトリガーを削除するコードを追加しましょう。
ScriptApp.getProjectTriggers()
で現在のトリガー一覧を取得し、特定の関数名に紐づくトリガーを
ScriptApp.deleteTrigger()
で削除できます。トリガーが大量に溜まること自体で直接的な問題は起きにくいですが、管理画面が見づらくなるため、定期的なクリーンアップを習慣にすると良いでしょう。
PropertiesServiceのストレージ制限を超えそうな場合はどうすればよいですか?
PropertiesServiceは1プロパティあたり最大9KB、全体で500KBという制限があります。処理の進捗管理(行番号の記録など)であれば十分ですが、大量のデータを保持する必要がある場合は、スプレッドシート自体をデータストアとして使うか、Google Cloud StorageやFirebase Realtime Databaseなどの外部ストレージを検討してください。CacheServiceも一時保存には有効ですが、最大6時間で消える点を忘れないようにしましょう。
カスタム関数の30秒制限を回避する方法はありますか?
カスタム関数(スプレッドシートのセルから
=関数名()
で呼び出す形式)の30秒制限を直接的に伸ばす方法はありません。カスタム関数はセルの再計算のたびに呼ばれるため、ユーザー体験を損なわないよう短い制限が設けられています。処理が重い場合は、カスタム関数ではなくサイドバーやメニューから手動実行するスクリプトに切り替えるか、トリガーで定期的にデータを更新してシートに書き戻す方式に変更するのが定石です。
今すぐパソコンやスマホの悩みを解決したい!どうしたらいい?
いま、あなたを悩ませているITの問題を解決します!
「エラーメッセージ、フリーズ、接続不良…もうイライラしない!」
あなたはこんな経験はありませんか?
✅ ExcelやWordの使い方がわからない💦
✅ 仕事の締め切り直前にパソコンがフリーズ💦
✅ 家族との大切な写真が突然見られなくなった💦
✅ オンライン会議に参加できずに焦った💦
✅ スマホの重くて重要な連絡ができなかった💦
平均的な人は、こうしたパソコンやスマホ関連の問題で年間73時間(約9日分の働く時間!)を無駄にしています。あなたの大切な時間が今この悩んでいる瞬間も失われています。
LINEでメッセージを送れば即時解決!
すでに多くの方が私の公式LINEからお悩みを解決しています。
最新のAIを使った自動応答機能を活用していますので、24時間いつでも即返信いたします。
誰でも無料で使えますので、安心して使えます。
問題は先のばしにするほど深刻化します。
小さなエラーがデータ消失や重大なシステム障害につながることも。解決できずに大切な機会を逃すリスクは、あなたが思う以上に高いのです。
あなたが今困っていて、すぐにでも解決したいのであれば下のボタンをクリックして、LINEからあなたのお困りごとを送って下さい。
ぜひ、あなたの悩みを私に解決させてください。
まとめ
GASの制限は、最初は面倒な障壁に感じるかもしれません。しかし、制限を理解し、それを前提とした設計を行うことこそが堅牢で安定したスクリプトを書く近道です。トリガーチェーンで実行時間の壁を越え、LockServiceで同時実行の競合を防ぎ、指数バックオフでAPI呼び出しの波を整え、カスタム関数の設計を見直して呼び出し回数を減らす。これらのテクニックは一見するとひと手間に感じますが、一度身につければどんなGASプロジェクトにも応用できる汎用的なスキルになります。
とくに2026年はRhinoランタイムの完全廃止やGranular OAuthの導入など、GAS環境が大きく変化したタイミングです。古いスクリプトの棚卸しと合わせて、制限対策のコードも最新のベストプラクティスにアップデートしておきましょう。制限を味方につけて、GASの力を最大限に引き出してください。






コメント