GAS 6분 실행 한도: 멈추기 전에 끊어서 재개하라
Apps Script는 한 번 실행이 6분에서 강제 종료된다. 대량 작업은 청크 + 커서 + 시간 트리거 재개로 설계해야 절반만 처리되는 사고를 막는다.
한 번의 실행은 6분에서 강제로 끊긴다. 1만 행을 한 루프에서 처리하려다 6분에 걸리면, 스크립트는 중간에 죽고 절반만 처리된 상태로 남는다. 다시 돌리면 처리한 행을 또 건드린다.
왜 중요한가
절반만 반영된 데이터는 안 한 것보다 위험하다. 어디까지 됐는지 모르니 수동 복구가 필요하고, 그 시간이 자동화로 아낀 시간을 그대로 까먹는다. 트리거 총 실행시간도 별도 한도가 있다(소비자 계정 약 90분/일).
정답: 청크 + 커서 + 트리거 재개
한 번에 끝내려 하지 말고, 처리한 위치(커서)를 저장하고 시간 트리거로 다음 청크를 이어서 돌린다.
const JOB_KEY = "sync.cursor";
function runSyncJob() {
const lock = LockService.getScriptLock();
if (!lock.tryLock(10000)) return; // 겹침 방지
try {
const props = PropertiesService.getScriptProperties();
const cursor = Number(props.getProperty(JOB_KEY) || 0);
const result = processChunk(cursor, 300); // 한 번에 300행
if (result.done) {
props.deleteProperty(JOB_KEY);
deleteTriggers_("runSyncJob"); // 끝나면 트리거 정리
return;
}
props.setProperty(JOB_KEY, String(result.nextCursor));
scheduleOnce_("runSyncJob", 60 * 1000); // 1분 뒤 재개
} finally {
lock.releaseLock();
}
}
function scheduleOnce_(handler, delayMs) {
deleteTriggers_(handler);
ScriptApp.newTrigger(handler).timeBased().after(delayMs).create();
}
function deleteTriggers_(handler) {
ScriptApp.getProjectTriggers()
.filter((tr) => tr.getHandlerFunction() === handler)
.forEach((tr) => ScriptApp.deleteTrigger(tr));
}
놓치기 쉬운 것
- 트리거를 꼭 지워라. 트리거는 스크립트당 20개 한도다. 1회용 트리거를 만들기 전에 같은 handler의 기존 트리거를 먼저 삭제하지 않으면 금방 한도에 부딪힌다.
- 겹침 방지. 트리거가 이전 실행과 겹쳐 같은 커서를 두 번 처리하지 않도록
LockService를 함께 쓴다. - 종료 정리. 끝나면 커서와 트리거를 반드시 지운다. 안 그러면 빈 작업이 매분 돈다.
깊이: 청크 크기는 측정해서 정한다
청크 크기는 100~500행에서 시작해 실제 실행 시간을 보며 조정한다. 한 청크가 6분의 절반을 넘으면 줄인다. 핵심 한 줄: 6분 안에 끝날 단위로 쪼개고, 위치를 저장하고, 이어서 돌려라.
자주 묻는 질문
- Google Apps Script 실행이 6분에서 멈추는 이유는?
- Apps Script는 한 번의 실행을 약 6분에서 강제 종료합니다(소비자 계정 기준). 대량 작업을 한 루프로 처리하다 한도에 걸리면 스크립트가 중간에 죽어, 데이터가 절반만 반영되는 사고가 납니다.
- 6분 실행 한도를 어떻게 우회하나요?
- 한 번에 끝내려 하지 말고, 처리한 위치를 커서로 저장한 뒤 시간 기반 트리거로 다음 청크를 이어서 실행합니다(청크 + 커서 + 트리거 재개). LockService로 실행 겹침도 막습니다.
- 트리거로 돌리면 시간 제한이 없나요?
- 아니요. 트리거 총 실행시간에도 별도 한도가 있습니다(소비자 계정 약 90분/일). 청크 크기와 실행 빈도를 그 한도 안에서 설계해야 합니다.