Claude Media
ClaudeでGASの6分制限を分割実行に直す依頼の出し方

ClaudeでGASの6分制限を分割実行に直す依頼の出し方

GASは1回の実行が6分で止まります。再開位置の保存と時間主導トリガーの連鎖で分割実行に直すよう、Claudeへ頼むときの依頼文と確認点をまとめました。

GAS(Google Apps Script)の1回の実行は6分で打ち切られます。数千行のシートを1本の関数で回すスクリプトは、データが増えるといつか途中で止まります。直し方は決まっていて、処理をまとまりに分け、どこまで終えたかを保存し、残りを次の実行に引き継ぎます。この設計をClaudeに任せるとき、依頼文に何を書けば手戻りが減るかを順に見ていきます。

6分の壁は何を止めるのか

Apps Scriptのクォータ表は、アカウントの種類ごとに数値を載せています。「Script runtime」はgmail.comなどの個人アカウントでもGoogle Workspaceアカウントでも「6 min / execution」で、アカウントを変えても伸びません。

項目個人アカウントGoogle Workspace
1回の実行時間個人アカウント6分Google Workspace6分
カスタム関数の実行時間個人アカウント30秒Google Workspace30秒
トリガーの1日の合計実行時間個人アカウント90分Google Workspace6時間
トリガー数個人アカウント20個 / ユーザー / スクリプトGoogle Workspace20個 / ユーザー / スクリプト
プロパティの値サイズ個人アカウント9KB / 値Google Workspace9KB / 値
プロパティの総容量個人アカウント500KB / ストアGoogle Workspace500KB / ストア

分割実行で変わるのは「1回あたり」の長さだけです。処理に要する合計時間は変わらないので、トリガーで動かす限り、1日の合計実行時間の枠(個人は90分、Workspaceは6時間)に収まる量かどうかは別に見積もる必要があります。枠を超えると、例外一覧にある「Service using too much computer time for one day」で止まります。この例外は、手動実行よりも低い日次上限を持つトリガー実行で起きやすいと説明されています。

分割実行を作る4つの部品

GASで長い処理を継続させる定石は、4つの部品の組み合わせです。

部品

分割実行を支える部品

  • 時間予算

    関数の冒頭で開始時刻を控え、ループの各周回で経過時間を測ります。6分に達する前に、自分で処理を切り上げます。

  • 再開位置の保存

    打ち切る直前に、次に処理する行番号などをPropertiesServiceへ書きます。次の実行はそこから読み出して続けます。

  • 次回トリガーの予約

    ScriptApp.newTrigger(...).timeBased().after(ms)で、続きを走らせる時間主導トリガーを作ります。

  • 排他ロック

    LockServiceで同じ区間の同時実行を防ぎます。連鎖の途中で手動実行が重なる事故を止めます。

after(durationMilliseconds)は「現在時刻から最低この時間後」にトリガーを動かす指定で、実際の待ち時間は指定より長くなることがあります。ちょうどその時刻に再開される前提でコードを書かないでください。定期実行を作りたいだけならeveryMinutes(n)もありますが、nに使える値は1、5、10、15、30のどれかです。

再開位置の置き場所にPropertiesServiceが向くのは、実行をまたいで値が残るからです。値は1件9KBまで、ストア全体で500KBまでなので、保存するのは「次の行番号」や「最後に処理したID」のような小さなカーソルに限ります。処理済みデータそのものは、シートの列やログシートに持たせます。

悪い依頼と良い依頼の違い

「6分で止まるので直して」だけでは、Claudeは時間予算の値も再開位置の持ち方も自分で決めます。決め方が運用と合わないと、後から差し戻すことになります。

前と後

依頼文を具体化する

BEFORE

曖昧な依頼

processAll関数が6分で止まるので、途中から再開できるようにして。

AFTER

条件を添えた依頼

processAllを分割実行に直して。対象はdataシートの2行目以降で、1行の処理に約1秒かかる。1回の実行は4分で切り上げ、次の行番号をスクリプトプロパティに保存し、1分後に続きを走らせる。全行が終わったら保存値と連鎖用トリガーを消す。同じ行を2回処理しても結果が変わらない書き方にする。

後者に入れた条件は、そのまま依頼文のチェックリストになります。

  • 何を単位に分けるか(行、ファイル、メールのスレッドなど)
  • 1単位あたりの所要時間(実測値があれば添える)
  • 1回の実行を何分で切り上げるか
  • 再開位置をどこに保存するか(スクリプトプロパティか、シートのセルか)
  • 全件完了したときに消すもの(保存値、連鎖用トリガー)
  • 同じ単位を2回処理してもよいか(冪等性)

最後の冪等性は見落とされやすい項目です。保存より前に処理が落ちると、同じ単位がもう一度処理される場合があります。メール送信のような取り消せない処理なら、処理済みの印を先に書く、あるいは1件ごとに保存するなどの順序を、依頼文で指定しておきます。

生成されるコードの形

次のコードは、公式リファレンスの部品(PropertiesService、ScriptApp、LockService)に沿って組んだ例です。Claudeの出力をそのまま再現したものではなく、依頼文を満たすコードがとる典型的な形として読んでください。

const TIME_BUDGET_MS = 4 * 60 * 1000; // 6分の上限に余裕を残す
const CURSOR_KEY = 'nextRow';
 
function processChunk() {
  const lock = LockService.getScriptLock();
  if (!lock.tryLock(10000)) return; // 別の実行が走行中
  const startedAt = Date.now();
  try {
    const props = PropertiesService.getScriptProperties();
    const sheet = SpreadsheetApp.openById('SHEET_ID').getSheetByName('data');
    const lastRow = sheet.getLastRow();
    let row = Number(props.getProperty(CURSOR_KEY) || 2);
 
    while (row <= lastRow) {
      if (Date.now() - startedAt > TIME_BUDGET_MS) {
        props.setProperty(CURSOR_KEY, String(row));
        scheduleNext_();
        return;
      }
      handleRow_(sheet, row); // 処理済み列を見て二重処理を避ける
      row++;
    }
    props.deleteProperty(CURSOR_KEY);
    deleteChainTriggers_();
  } finally {
    lock.releaseLock();
  }
}
 
function scheduleNext_() {
  deleteChainTriggers_();
  ScriptApp.newTrigger('processChunk')
    .timeBased()
    .after(60 * 1000)
    .create();
}
 
function deleteChainTriggers_() {
  ScriptApp.getProjectTriggers()
    .filter(t => t.getHandlerFunction() === 'processChunk')
    .forEach(t => ScriptApp.deleteTrigger(t));
}

読みどころは3つあります。1つ目は、時間の判定が各周回の先頭にあることです。1行の処理が長い場合、判定の位置が悪いと、予算内で打ち切るつもりが6分を超えます。2つ目は、tryLockがBooleanを返す点です。ロックを取れなかった実行は何もせずに戻るので、先行する実行が続きを担当します。3つ目は、連鎖用トリガーを消す対象を関数名で絞っている点です。同じ関数に常設の定期トリガーも付けていると、それまで消してしまいます。常設トリガーを持たせたい場合は、起動用の関数と連鎖用の関数を分けるよう、依頼文に書き添えます。

ロックの種類は3つあります。スクリプト全体で同時実行を防ぐgetScriptLock()、ドキュメント単位のgetDocumentLock()、ユーザー単位のgetUserLock()です。分割実行で守りたいのは「同じスクリプトの二重走行」なので、通常はgetScriptLock()が合います。スタンドアロンスクリプトやウェブアプリではgetDocumentLock()がnullを返す点にも注意が必要です。

生成コードをレビューする観点

分割実行のコードは、動いて見えても壊れ方が静かです。レビューでは次の項目を順に見ます。

落とし穴

連鎖用トリガーが消えずに溜まる

完了時や例外時にdeleteTriggerが呼ばれないと、トリガーが残り続けます。1ユーザーあたり1スクリプトのトリガーは20個までなので、上限に達すると以後の予約が失敗します。例外が起きる経路でも後片付けが通るか、finallyの位置を確認します。

  • 再開位置を保存する順序: 処理の前に保存すると、落ちた単位が飛ばされます。処理の後に保存すると、二重処理が起きます。どちらを許容するかを決め、コードのコメントに残すようClaudeに頼みます。
  • カーソルの大きさ: 配列や処理済みIDの一覧をそのままプロパティへ書く実装は、9KBの上限に触れます。保存するのは番号か、最後のIDだけです。
  • 時間予算の余裕: 4分に切り上げても、最後の1単位が長引けば6分を超えます。1単位の最長時間を見積もって、予算を決めます。
  • ロックの解放: releaseLock()をfinallyに置きます。例外で抜けたとき解放されないと、次の実行がtryLockで取れなくなります。
  • トリガーの実行主体: インストール可能トリガーは、作成した人のアカウントで動きます。共有スクリプトを別の人が起動した場合でも、連鎖は作成者の権限と枠を使います。

分割実行を手元で確かめる

本番データで6分待って検証するのは現実的ではありません。時間予算を定数にしておけば、短い値に差し替えて分割の動きを再現できます。

手順

分割動作を短時間で確かめる手順

  1. 1

    予算を縮める

    TIME_BUDGET_MSを10秒程度にして、数十行のテストデータを用意します。

  2. 2

    手動で1回実行する

    エディタからprocessChunkを実行し、保存されたnextRowとトリガー一覧を確認します。

  3. 3

    連鎖を見届ける

    Apps Scriptのダッシュボードの「My Executions」ページで、実行の状態(Completed、Failed、Running)と回数を見ます。想定した回数で止まれば成功です。

  4. 4

    後片付けを確かめる

    全行が終わったあと、nextRowとトリガーが残っていないことを見ます。

Claude Codeにローカルのコードを直させている場合は、この確認手順をCLAUDE.mdに書いておくと、毎回の依頼で繰り返さずに済みます。例えば次のような断片です。

## GAS 長時間処理のルール
- 1回の実行は6分で止まる。ループは時間予算(定数 TIME_BUDGET_MS)で切り上げる
- 再開位置は PropertiesService のスクリプトプロパティに小さな値で保存する
- 連鎖トリガーを作る関数は、完了時と例外時に必ず自分のトリガーを削除する
- 排他は LockService.getScriptLock() と tryLock、解放は finally
- 変更後は TIME_BUDGET_MS を 10 秒にした確認手順を回答に添える

clasp経由でローカル編集とpushを進める手順はClaude CodeでGAS開発を進める手順にあります。複数の実行が同じ値を書き換える競合の考え方は、別の言語での例になりますがClaude CodeでRedisのレート制限を実装する手順の原子化の節と共通します。

分割しても足りないときの選択肢

分割実行が効くのは、1回の実行が長いだけで、合計の処理時間は日次の枠に収まる場合です。次の場合は、分割以外の手を考えます。

  • 合計時間が枠を超える: 個人アカウントのトリガー合計90分、Workspaceの6時間に収まらないなら、処理量を減らすか、処理を別の基盤へ移す検討に入ります。
  • カスタム関数が遅い: カスタム関数は1回30秒で、シートの数式として何度も呼ばれると「Script invoked too many times per second」のような例外も出ます。範囲を一度に渡して配列で返す形に直します。
  • 外部APIの呼び出しが多い: URL Fetchは個人で1日2万回、Workspaceで10万回の枠があります。呼び出し回数を数えてから、分割か、まとめ取りかを選びます。

移行先の候補を相談する段階なら、影響範囲を数えてから進める方法としてClaude Codeでモノリスを分割する順番をPlan modeで決める手順の進め方が使えます。

6分の壁そのものは、実行の長さではなく、設計の前提が「1回で終わる」になっていることから来ます。Claudeに頼むときは、再開位置・時間予算・後片付け・冪等性の4点を言葉にして渡すだけで、分割の質が変わります。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. developers.google.com/apps-script/guides/services/quotas
  2. developers.google.com/apps-script/reference/script/clock-trigger-builder
  3. developers.google.com/apps-script/reference/lock/lock-service
  4. developers.google.com/apps-script/reference/properties/properties
この記事を共有:XはてブLinkedIn