0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GASトリガーの6分制限と並行実行の罠 — LockService + 実行ログでの対処法

0
Posted at

GASトリガーの6分制限と並行実行への対処

Google Apps Scriptのトリガーには6分のタイムアウトがあり、長い処理や並行実行時の重複書き込みで予期しない動作が起きる。これを検知・制御する手段がある。

トリガーの6分制限

GASのトリガーは実行時間が6分を超えるとタイムアウトで強制終了される。その時点で途中結果だけが残る場合がある。

例えば、毎分実行する everyMinutes(1) で以下のような処理があると問題になる。

function collectDataAndWrite() {
  const data = fetchExternalAPI(); // 2秒
  processRecords(data);              // 4秒
  appendToSheet(data);               // 完了
}

この処理が3分以上かかる場合、複数のトリガーが並行実行される。トリガー1が書き込み中にトリガー2が同じシートに書き込むと、データが上書きされる。

並行実行による重複書き込み

トリガーの実行時間が不安定な場合、複数インスタンスが同時に走る。

function unsafeWrite() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const lastRow = sheet.getLastRow();
  sheet.getRange(lastRow + 1, 1).setValue(new Date());
  // トリガー1と2が同じ行番号を計算する可能性
}

everyMinutes(1) で毎分実行され、処理が1.5分かかる場合:

  • 10:00 トリガー1開始
  • 10:01 トリガー2開始(トリガー1がまだ実行中)
  • 10:01:30 トリガー1完了
  • 10:02:30 トリガー2完了

結果として、トリガー1と2が同じ行に書き込む。

LockServiceで排他制御

GASの LockService は複数のトリガー実行を序列化する。

function safeWrite() {
  const lock = LockService.getScriptLock();
  if (!lock.tryLock(15000)) {
    // ロック獲得失敗時は実行を中止
    logError('Previous execution still running');
    return;
  }
  
  try {
    const sheet = SpreadsheetApp.getActiveSheet();
    const lastRow = sheet.getLastRow();
    sheet.getRange(lastRow + 1, 1).setValue(new Date());
  } finally {
    lock.releaseLock();
  }
}

tryLock(15000) は15秒以内にロック獲得を試みる。獲得できない場合は false を返す。この場合、後続の処理をスキップして実行ログに記録する。

実行ログでの障害検知

GASの実行ログには各トリガー実行の時間と結果が記録される。長時間実行や重複実行はここで検知できる。

function monitorExecutionLog() {
  const logger = ScriptApp.getScriptName();
  const executions = ScriptApp.getProjectTriggers();
  
  const now = new Date();
  const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000);
  
  // 実行ログをキャッシュから取得
  const cache = CacheService.getScriptCache();
  const lastCheck = cache.get('last_check');
  
  if (!lastCheck || new Date(lastCheck) < oneHourAgo) {
    logExecutionStats();
    cache.put('last_check', now.toISOString(), 3600);
  }
}

function logExecutionStats() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const now = new Date();
  
  sheet.appendRow([
    now,
    'execution_check',
    'completed',
    Utilities.computeDigest(
      Utilities.DigestAlgorithm.SHA_256,
      JSON.stringify({ timestamp: now.getTime() })
    ).slice(0, 8)
  ]);
}

実行ログシートに記録することで、トリガーが予定時刻に実行されているか、実行時間がどれほどかを監視できる。

everyMinutes vs everyHours の判断

トリガー実行時間が1分以内なら everyMinutes(1) で安全。それ以上かかる場合は everyHours(1) にして実行頻度を下げる。あるいは処理をバッチ分割する。

function createTrigger() {
  // 毎分実行:処理時間が30秒未満を確認してから
  ScriptApp.newTrigger('quickProcess')
    .timeBased()
    .everyMinutes(1)
    .create();
  
  // 毎時実行:重い処理用
  ScriptApp.newTrigger('heavyProcess')
    .timeBased()
    .everyHours(1)
    .create();
}

LockService + 実行ログの組み合わせにより、トリガーの6分制限と並行実行の問題を回避できる。前提は処理時間の測定と、ロック獲得失敗時の明示的なエラー処理。


参照:GASのトリガーと実行管理の実装例

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?