深夜2時の本番クラッシュ
クライアント向けにGoogle Sheetsツールを納品し、自分一人でのテストでは完璧に動作していました。しかし、20人が同時にファイルを開いたり、誰かが5,000行のデータセットを貼り付けた途端、スクリプトは壁にぶつかります。実行ログに、苛立たしいメッセージが一行だけ表示されます:
Exception: Too many simultaneous invocations: Spreadsheet
Googleはスクリプトインスタンスが同時に実行できる数に上限を設けています。ほとんどのGoogle Workspaceアカウントでは、この上限は同時実行数30件前後です。カスタム関数や高頻度トリガーなどを使用してこの上限を超えると、Googleはサーバーリソースを守るためにプロセスを強制終了します。
まとめ:クラッシュを止める方法
- LockServiceを実装する: スクリプトを一度に実行させず、キューで待機させます。
- カスタム関数をバッチ処理する: 100行に対して関数を100回呼び出さないでください。範囲全体
(A2:A101)を渡して一度に処理します。 - CacheServiceを使用する: 結果を10分間保存することで、同一の計算でスクリプトが再実行されるのを防ぎます。
このエラーが発生する原因
=GET_VAT(A1) のようなカスタム関数があるとします。この数式を500行にドラッグしてコピーすると、Google Sheetsは1つずつ実行するのではなく、500件すべてを同時に計算しようとします。500件は許容されている約30スロットをはるかに超えるため、スプレッドシートはパニックを起こし、例外をスローします。
方法1:LockServiceによるキューイング
LockService はデリカテッセンの「番号札発行機」のようなものです。複数の実行が同時に同じシートに書き込もうとするのを防ぎます。混沌とした渋滞を整然とした行列に変える効果があります。
function safeWriteToSheet(data) {
const lock = LockService.getScriptLock();
try {
// ロックを要求し、他の処理が終わるまで最大30秒待機する
lock.waitLock(30000);
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Logs');
sheet.appendRow([new Date(), data]);
// flushでロック解放前に変更が書き込まれることを保証する
SpreadsheetApp.flush();
} catch (e) {
console.error('Lock timeout: ' + e.message);
throw new Error('サーバーが混雑しています。数秒待ってから再試行してください。');
} finally {
// コードが失敗した場合でも、必ずロックを解放する
lock.releaseLock();
}
}
方法2:範囲バッチ処理(最善の解決策)
エラーの原因がカスタム数式にある場合、LockService はすべてのセルが順番を待つことになるため、シートの動作が重く感じられる可能性があります。根本的な解決策は、単一の値ではなく範囲全体(配列)を処理するように関数を書き直すことです。
非効率な方法:
1,000個のセルで使用される数式:=USD_TO_EUR(A2)
// 非推奨:これはスクリプトを1,000回個別に実行させる
function USD_TO_EUR(value) {
return value * 0.92;
}
プロの方法:
たった1つのセルで使用される数式:=USD_TO_EUR(A2:A1001)
// 推奨:これは1,000行を処理するのにスクリプトを1回だけ実行する
function USD_TO_EUR(input) {
if (Array.isArray(input)) {
// 範囲から返される2次元配列を処理する
return input.map(row => row.map(cell => cell * 0.92));
}
return input * 0.92;
}
範囲全体を渡すことで、実行回数を1,000回からわずか1回に削減できます。これがGoogleの同時実行数制限を下回り続けるための最も効果的な方法です。
方法3:結果のキャッシュ
スクリプトが外部API(通貨換算サービスや天気サービスなど)からデータを取得している場合、同じデータを二度取得しないでください。CacheService を使って数分間結果を保存しましょう。これにより、すでに取得済みのデータに対して新しいユーザーが新たなスクリプト実行をトリガーするのを防げます。
function getCachedRate(currencyPair) {
const cache = CacheService.getScriptCache();
const cachedValue = cache.get(currencyPair);
if (cachedValue) return cachedValue;
// キャッシュにない場合、コストのかかるAPI呼び出しを実行する
const rate = fetchRateFromAPI(currencyPair);
// 15分間(900秒)キャッシュに保存する
cache.put(currencyPair, rate, 900);
return rate;
}
検証:修正後の負荷テスト
自分の環境で動作したからといって、修正できたと思い込まないでください。以下の3つのテストを試してみましょう:
- 元に戻すテスト: カスタム関数が含まれる200個のセルを選択して削除し、
Cmd+Z(またはCtrl+Z)を押します。これにより、大量の同時再計算が強制的に発生します。 - 実行ログの確認: Apps Scriptエディタを開き、実行数をクリックします。開始時刻が重複していないか確認してください。すべてが「失敗」ではなく「完了」と表示されていれば、
waitLockが正常に機能しています。 - マルチユーザー負荷テスト: 同僚2人に「実行」ボタンを連打してもらうか、同じ範囲を同時に編集してもらいます。クラッシュせずにスクリプトが正しくキューに入れば、本番環境への準備は完了です。
まとめ
「Too many simultaneous invocations」エラーは計算ロジックのバグではなく、交通渋滞です。LockService でキューを管理し、範囲ベースのバッチ処理で道路上の車の数を減らしましょう。この2つの変更だけで、Google Apps Scriptにおける同時実行の問題の95%を解決できます。

