大量のデータを処理するバッチ処理をApps Scriptで実装したものの、6分の実行時間制限に阻まれて全体を完了できずに困ったことはありませんか。GoogleスプレッドシートのApps Scriptには1回の実行につき6分という制限があり、長時間の処理が中断されてしまいます。この記事では、処理を複数の小さなバッチに分割して6分以内に収め、自動的に再開する仕組みを実装する方法を解説します。具体的なコード例とともに、分割の戦略や注意点も紹介します。
【要点】Apps Scriptの実行制限を回避するバッチ分割の実装ポイント
- トリガーによる分割実行: 時間ベースまたはスプレッドシートの変更トリガーを利用して、1回の実行を細切れにし、全体の処理を完了します。
- プロパティサービスで状態管理: 処理の進捗状況をScriptPropertiesまたはUserPropertiesに保存し、次回実行時に続きから開始できるようにします。
- タイマー関数で実行時間を監視: スクリプト内で経過時間をチェックし、6分に達する前に処理を中断してトリガーを再設定します。
ADVERTISEMENT
目次
Apps Scriptの6分制限とバッチ分割の仕組み
Google Apps Scriptには、1回の関数実行につき最大6分(360秒)の実行時間制限があります。これはGoogleのサーバーリソースを公平に利用するための制限で、長時間実行されるスクリプトは自動的に強制終了されます。この制限を回避するには、大量のデータ処理を小さなチャンクに分割し、それぞれのチャンクを別々の関数実行で処理する方法が有効です。分割方法には主に以下の3つのアプローチがあります。
- 時間ベーストリガーによる定期的な分割実行
- スプレッドシートの編集トリガーを利用した手動再開
- スクリプト内で経過時間を監視して自分自身を再帰呼び出しする方法
それぞれの方法には長所と短所がありますが、この記事では実装が比較的容易で確実性の高い「時間ベーストリガーを用いた分割実行」に焦点を当てます。
時間ベーストリガーを使ったバッチ分割の実装手順
1. 分割処理の設計
- 処理対象を確認する
まず、実行したいバッチ処理の全体像を把握します。例えば、スプレッドシートの全行をループして各セルを加工する処理であれば、1回の実行で処理する行数を決めます。目安として、1回のループに0.1秒かかるとすると、6分で処理できる行数は約3,600行です。余裕を見て1回あたりの処理行数を2,000〜3,000行に設定します。 - 状態管理の仕組みを決める
次回の実行時にどこまで処理したかを記録するために、PropertiesServiceを使用します。ScriptPropertiesまたはUserPropertiesに、最後に処理した行番号やインデックスを保存します。プロパティは文字列として保存するため、数値は文字列に変換して保存します。 - トリガーの設定方法を決める
分割実行を自動化するには、時間ベーストリガーを使用します。トリガーはスクリプトエディタの「トリガー」メニューから手動で設定するか、コード内でScriptApp.newTrigger()を使ってプログラム的に設定します。後者のほうが柔軟で、分割回数に応じて動的にトリガーを再設定できます。
2. 分割実行関数のサンプルコード
以下のコードは、スプレッドシートのA列のデータを加工し、B列に結果を出力するバッチ処理を分割実行する例です。メイン関数は processBatch() で、1回の実行で処理する行数を BATCH_SIZE で指定します。
function processBatch() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var data = sheet.getDataRange().getValues();
var lastRow = data.length;
var startRow = PropertiesService.getScriptProperties().getProperty('START_ROW');
if (!startRow) startRow = 1;
else startRow = parseInt(startRow);
var BATCH_SIZE = 2000; // 1回あたりの処理行数
var endRow = Math.min(startRow + BATCH_SIZE - 1, lastRow);
// 処理の実行
for (var i = startRow; i <= endRow; i++) {
var cellValue = sheet.getRange(i, 1).getValue();
var processed = process(cellValue); // 実際の処理
sheet.getRange(i, 2).setValue(processed);
}
// 次の開始行を保存
if (endRow < lastRow) {
PropertiesService.getScriptProperties().setProperty('START_ROW', endRow + 1);
} else {
// 全行完了
PropertiesService.getScriptProperties().deleteProperty('START_ROW');
// トリガーを削除(最後の処理)
deleteTrigger();
}
}
この関数では、プロパティから開始行を取得し、BATCH_SIZE分だけ処理します。最後の行まで到達したらプロパティを削除し、トリガーも削除して終了します。
3. トリガーを設定する関数
function setTrigger() {
// 既存のトリガーを削除してから設定
deleteTrigger();
// 1分ごとにトリガーを作成(必要に応じて調整)
ScriptApp.newTrigger('processBatch')
.timeBased()
.everyMinutes(1)
.create();
}
function deleteTrigger() {
var triggers = ScriptApp.getProjectTriggers();
for (var i = 0; i < triggers.length; i++) {
if (triggers[i].getHandlerFunction() === 'processBatch') {
ScriptApp.deleteTrigger(triggers[i]);
}
}
}
setTrigger() を最初の1回だけ実行すれば、1分ごとに processBatch が起動し、分割処理が進行します。ただし、1分間隔では6分以内に処理が完了しない可能性があるため、トリガーの間隔は実際の処理時間に合わせて調整してください。
4. 初期化と完了処理
最初に setTrigger() を実行し、プロパティを設定します。処理が完了したら自動的に trigger が削除されるので、手動で止める必要はありません。また、エラーが発生した場合のリカバリも考慮して、try-catch でエラーをハンドリングし、エラー時にもトリガーを削除するなどの対策が必要です。
バッチ分割実装時の注意点とよくあるトラブル
トリガーが重複して設定されてしまう
トリガーをプログラム的に設定する場合、既存のトリガーを削除せずに新しく追加すると、複数のトリガーが並行して動作してしまいます。これにより、同じ処理が同時に実行されてデータが重複したり競合が発生します。トリガーを設定する前に必ず deleteTrigger() を呼び出して既存のトリガーを削除してください。
プロパティの競合
複数のプロジェクトで同じプロパティキーを使用していると、意図しない値が読み書きされる可能性があります。プロパティキーには十分にユニークな名前を使用しましょう。また、ScriptProperties はプロジェクト全体で共有されるため、他のスクリプトから影響を受けないように注意してください。
処理の途中でエラーが発生した場合
バッチ処理中にエラーが発生すると、その行の処理が中断され、次のトリガーで再開したときに同じ行からやり直すことになります。エラーが発生した行をスキップして次に進むようにロジックを実装するか、エラー内容をログに記録するなどの対策を検討してください。
実行時間の見積もりが不正確
1回の処理にかかる時間はデータ量や処理内容によって変動するため、BATCH_SIZE の設定値が適切でないと6分を超えてしまう可能性があります。実際の処理時間を測定し、余裕を持った値に設定してください。また、トリガーの間隔も処理時間より長めに設定することで、重複実行を防止できます。
ADVERTISEMENT
分割方法の比較:時間ベース・編集トリガー・再帰呼び出し
| 分割方法 | メリット | デメリット | 適したケース |
|---|---|---|---|
| 時間ベーストリガー | 定期的に自動実行されるためユーザー操作不要 | トリガーの間隔が短いと重複実行のリスク、長いと全体完了に時間がかかる | 定期的なバッチ処理(毎日など)で、完了までに時間がかかってもよい場合 |
| 編集トリガー | ユーザーが編集したタイミングで進捗するため無駄な実行がない | 編集がないと処理が進まない、大量の編集でトリガーが多発する | ユーザーが手動でデータを追加しながら処理したい場合 |
| 再帰呼び出し(時間監視) | トリガー不要でコード内で完結、細かい制御が可能 | スクリプトの複雑さが増す、再帰の深さに制限がある | 1回の実行内で完結させたいが、6分以内に収まらない場合 |
この記事では、Apps Scriptの6分実行制限を回避するために、バッチ処理を細切れにして時間ベーストリガーで自動実行する方法を解説しました。プロパティサービスによる状態管理とトリガーの動的制御を組み合わせることで、長時間の処理を確実に完了できます。さらに応用として、並列実行やCloud Tasksとの連携なども検討できますが、まずは今回紹介した分割実行の基本形を試してみてください。適切なBATCH_SIZEとトリガー間隔を設定することで、制限を気にせず大規模なデータ処理が可能になります。
ADVERTISEMENT
超解決 第一編集部
疑問解決ポータル「超解決」の編集チーム。正確な検証と、現場視点での伝わりやすい解説を心がけています。
Googleスプレッドシートの人気記事ランキング
- 【Googleスプレッドシート】GOOGLEFINANCE関数で株価・為替を取得!リアルタイムデータの呼び出し
- 【Googleスプレッドシート】印刷範囲を指定して印刷!特定範囲だけPDFや紙に出す手順
- 【Googleスプレッドシート】新しいスプレッドシートを作成する3つの方法!ドライブ・URL・テンプレート
- 【Googleスプレッドシート】数値の連続データを自動入力!オートフィルの活用
- 【Googleスプレッドシート】ダークモードを有効にする!目に優しい配色への切替
- 【Googleスプレッドシート】株価APIで株式データを自動取得!GOOGLEFINANCE超え活用
- 【Googleスプレッドシート】共有相手が編集できない時のチェック!権限と許可状態の確認
- 【Googleスプレッドシート】ページ設定で用紙サイズと向きを調整!印刷レイアウトの基本
- 【Googleスプレッドシート】Excelファイルxlsxをインポートする手順!ドラッグ&ドロップで取り込み
- 【Googleスプレッドシート】条件付き書式をコピーする!書式のみペーストの活用
