Google Apps Script에서 Exceeded maximum execution time 오류가 뜨는 이유는 코드가 Apps Script의 실행 시간 제한 안에 끝나지 못했기 때문입니다. 먼저 확인할 것은 셀 단위 반복 처리, 외부 API 반복 호출, 한 번에 처리하는 데이터 양, 트리거 분할 여부입니다.


해결 방향은 코드를 빠르게 만드는 것과 오래 걸리는 작업을 나눠 실행하는 것입니다. 구글시트 자동화라면 getValue(), setValue()를 반복문 안에서 계속 호출하는 구조를 getValues(), setValues() 중심의 배치 처리로 바꾸는 것이 우선입니다. 그래도 오래 걸리면 진행 위치를 저장하고 시간 기반 트리거로 다음 묶음을 이어서 처리해야 합니다.


Google Apps Script Exceeded maximum execution time 실행 시간 초과 해결 구조

Apps Script 실행 시간 초과는 반복 작업을 줄이고, 작업을 나누어 실행하는 구조로 해결하는 것이 핵심입니다.


Exceeded maximum execution time 오류 메시지 원문

Apps Script 실행 로그나 오류 알림에서 다음 메시지가 보이면 실행 시간이 제한을 넘었다는 뜻입니다.


Exceeded maximum execution time

Google Sheets 사용자 정의 함수에서 발생하면 셀에는 #ERROR!가 보이고, 셀 메모나 오류 설명에는 Exceeded maximum execution time (line 0)처럼 표시될 수 있습니다. 이 경우 셀 안에서 실행되는 함수가 너무 오래 걸렸거나, 사용자 정의 함수에 맞지 않는 무거운 작업을 넣었을 가능성이 큽니다.


먼저 볼 기준
실행 시간 초과는 단순 문법 오류가 아닙니다. 같은 작업을 너무 많이 반복하거나, 외부 서비스 호출이 많거나, 한 번의 실행에서 모든 데이터를 처리하려고 할 때 자주 발생합니다.


언제 발생하는 오류인지

Exceeded maximum execution time은 Google Sheets 자동화에서 가장 자주 보이지만, Gmail, Drive, Calendar, 외부 API를 반복 처리하는 코드에서도 발생할 수 있습니다. 특히 초보자가 예제 코드를 복사해 데이터가 많은 시트에 적용할 때 자주 만납니다.


  • 구글시트 행을 한 줄씩 읽고 쓰는 경우
  • getValue(), setValue()를 반복문 안에서 계속 호출하는 경우
  • Gmail 메일을 많이 검색하고 각 메일을 반복 처리하는 경우
  • Drive 파일을 대량으로 순회하는 경우
  • 외부 API를 행마다 호출하는 경우
  • 트리거 한 번에 모든 데이터를 처리하려는 경우

데이터가 적을 때는 정상 작동하던 코드도 행 수가 늘어나면 중간에 멈출 수 있습니다. 따라서 Apps Script 자동화 코드는 처음부터 대량 처리를 고려해 작성하는 것이 좋습니다.


실행 시간 초과의 주요 원인

원인은 대부분 반복 횟수와 서비스 호출 횟수에서 시작됩니다. 특히 Spreadsheet 서비스는 셀을 하나씩 읽고 쓰는 방식보다 범위를 한 번에 읽고 쓰는 방식이 훨씬 유리합니다.


원인 자주 보이는 코드 패턴 처리 방향
셀 단위 읽기/쓰기 반복 getValue(), setValue()를 반복문 안에서 호출 getValues(), setValues()로 범위 단위 처리
데이터 양이 너무 많음 수천~수만 행을 한 번에 처리 100행 또는 500행 단위로 분할
외부 서비스 호출이 많음 행마다 API, Gmail, Drive 호출 필요한 행만 호출하고 결과를 캐시 또는 저장
트리거 분할 구조가 없음 트리거 1회 실행으로 전체 데이터 처리 진행 위치 저장 후 시간 기반 트리거로 이어서 실행
사용자 정의 함수에 무거운 작업을 넣음 셀 함수에서 대량 수정, 외부 API 반복 호출 메뉴 실행 또는 설치형 트리거 구조로 이동

빠른 해결 순서

실행 시간 초과가 발생했다면 코드 전체를 처음부터 다시 만들기보다 병목 구간부터 줄이는 것이 좋습니다. 아래 순서로 점검하면 원인을 좁히기 쉽습니다.


  1. 처리 범위를 줄입니다.
  2. getValue(), setValue() 반복을 getValues(), setValues()로 바꿉니다.
  3. 외부 API 호출은 필요한 경우에만 실행합니다.
  4. 한 번에 처리할 행 수를 정합니다.
  5. 남은 작업 위치를 PropertiesService에 저장합니다.
  6. 시간 기반 트리거로 다음 묶음을 이어서 실행합니다.
  7. 완료 후 중복 트리거를 삭제합니다.

트리거로 자동 실행을 만들었지만 실행되지 않거나 실패 알림이 반복된다면 트리거 권한과 설치형 트리거 설정도 같이 봐야 합니다.


Google Apps Script 트리거 오류 해결: 자동 실행이 안 될 때 권한·시간 기반 트리거 점검 순서에서 트리거가 멈추는 상황을 따로 정리했습니다.


느린 코드 예시: 셀을 한 칸씩 읽고 쓰는 방식

아래 코드는 각 행을 돌면서 셀을 하나씩 읽고, 다시 셀을 하나씩 쓰는 방식입니다. 데이터가 적을 때는 문제없이 보일 수 있지만, 행이 많아지면 Apps Script와 Google Sheets 사이를 너무 자주 오가게 됩니다.


function slowUpdateRows() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Data');
  const lastRow = sheet.getLastRow();

  for (let row = 2; row <= lastRow; row++) {
    const name = sheet.getRange(row, 1).getValue();
    const amount = sheet.getRange(row, 2).getValue();

    let result = '';
    if (amount >= 100000) {
      result = name + ' / 확인 필요';
    } else {
      result = name + ' / 일반';
    }

    sheet.getRange(row, 3).setValue(result);
  }
}

이 코드가 느린 이유는 반복문 안에서 getRange(), getValue(), setValue()를 계속 호출하기 때문입니다. 1,000행이면 수천 번의 시트 접근이 생기고, 외부 API까지 포함되면 실행 시간이 더 빨리 늘어납니다.


개선 코드 예시: getValues와 setValues로 한 번에 처리하기

구글시트 자동화에서는 범위를 한 번에 읽고, 배열 안에서 계산한 뒤, 결과를 한 번에 쓰는 구조가 기본입니다. 이 방식은 셀 단위 호출을 줄여 실행 시간을 크게 줄일 수 있습니다.


function fastUpdateRows() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Data');
  const lastRow = sheet.getLastRow();

  if (lastRow < 2) {
    return;
  }

  const values = sheet.getRange(2, 1, lastRow - 1, 2).getValues();

  const result = values.map(function(row) {
    const name = row[0];
    const amount = row[1];

    if (amount >= 100000) {
      return [name + ' / 확인 필요'];
    }

    return [name + ' / 일반'];
  });

  sheet.getRange(2, 3, result.length, 1).setValues(result);
}

핵심은 시트 접근 횟수를 줄이는 것입니다. 읽기는 getValues() 한 번, 쓰기는 setValues() 한 번으로 끝냅니다. 계산은 Apps Script 메모리 안의 배열에서 처리합니다.


주의할 점
getValues()setValues()로 바꿔도 데이터가 너무 많거나 외부 API 호출이 많으면 다시 시간 초과가 날 수 있습니다. 그때는 작업을 작은 묶음으로 나누어 이어서 실행하는 구조가 필요합니다.


긴 작업은 한 번에 끝내려 하지 말고 나눠 실행한다

Apps Script 배치 처리 시간 기반 트리거 진행 위치 저장 흐름도

긴 Apps Script 작업은 일부 행 처리, 진행 위치 저장, 시간 기반 트리거 재실행 순서로 나누어 처리하면 안정적으로 이어갈 수 있습니다.


실행 시간이 긴 자동화는 한 번에 끝내는 방식보다 이어서 처리하는 방식이 안전합니다. 예를 들어 10,000행을 한 번에 처리하지 않고, 100행 또는 500행씩 나누어 처리합니다.


흐름은 다음과 같습니다. 구글시트 데이터 → 일부 행만 처리 → 진행 위치 저장 → 시간 기반 트리거 재실행 → 남은 행 처리 → 완료 확인.


이 구조에서는 마지막으로 처리한 행 번호를 저장해야 합니다. 다음 실행이 시작되면 저장된 행 번호부터 이어서 처리합니다. 남은 데이터가 있으면 다음 트리거를 만들고, 모두 끝나면 진행 위치와 트리거를 정리합니다.


PropertiesService와 시간 기반 트리거 예시

아래 예시는 구글시트 데이터를 일정 행 수만큼 나눠 처리하는 기본 구조입니다. 실제 업무에 맞게 시트 이름, 열 번호, 처리 로직, 묶음 크기를 바꿔 사용해야 합니다.


const SHEET_NAME = 'Data';
const BATCH_SIZE = 100;
const PROPERTY_KEY = 'LAST_PROCESSED_ROW';
const FUNCTION_NAME = 'processRowsInBatches';

function processRowsInBatches() {
  deleteExistingTriggers_(FUNCTION_NAME);

  const properties = PropertiesService.getScriptProperties();
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME);
  const lastRow = sheet.getLastRow();

  const startRow = Number(properties.getProperty(PROPERTY_KEY) || 2);

  if (lastRow < 2 || startRow > lastRow) {
    properties.deleteProperty(PROPERTY_KEY);
    return;
  }

  const endRow = Math.min(startRow + BATCH_SIZE - 1, lastRow);
  const numRows = endRow - startRow + 1;

  const values = sheet.getRange(startRow, 1, numRows, 2).getValues();

  const result = values.map(function(row) {
    const name = row[0];
    const amount = row[1];

    if (amount >= 100000) {
      return [name + ' / 확인 필요'];
    }

    return [name + ' / 일반'];
  });

  sheet.getRange(startRow, 3, result.length, 1).setValues(result);

  const nextRow = endRow + 1;

  if (nextRow <= lastRow) {
    properties.setProperty(PROPERTY_KEY, String(nextRow));
    createNextTrigger_();
  } else {
    properties.deleteProperty(PROPERTY_KEY);
  }
}

function createNextTrigger_() {
  ScriptApp.newTrigger(FUNCTION_NAME)
    .timeBased()
    .after(60 * 1000)
    .create();
}

function deleteExistingTriggers_(functionName) {
  const triggers = ScriptApp.getProjectTriggers();

  triggers.forEach(function(trigger) {
    if (trigger.getHandlerFunction() === functionName) {
      ScriptApp.deleteTrigger(trigger);
    }
  });
}

이 예시는 실행이 시작될 때 같은 함수의 기존 트리거를 먼저 삭제합니다. 중복 트리거가 계속 쌓이면 같은 작업이 여러 번 실행될 수 있기 때문입니다. 다만 실제 프로젝트에 여러 트리거가 있다면 삭제 함수가 필요한 트리거만 지우도록 함수명을 정확히 확인해야 합니다.


완료 조건도 중요합니다. 남은 행이 없으면 PropertiesService에 저장한 진행 위치를 삭제하고 새 트리거를 만들지 않아야 합니다. 완료 조건이 없으면 트리거가 계속 만들어지는 구조가 될 수 있습니다.


사용자 정의 함수에서 발생하는 경우

Google Sheets 셀에 직접 넣는 사용자 정의 함수는 짧게 끝나는 계산에 적합합니다. 셀 안의 함수로 대량 데이터를 수정하거나, 다른 셀을 직접 바꾸거나, Gmail·Drive·외부 API를 반복 호출하는 구조는 적합하지 않습니다.


사용자 정의 함수에서 Exceeded maximum execution time (line 0)이 보이면 함수 안의 작업을 줄이거나, 사용자 정의 함수 대신 메뉴 실행 또는 설치형 트리거 실행 구조로 바꾸는 것이 좋습니다. 셀 함수는 값을 계산해 반환하는 용도에 가깝고, 대량 자동화 작업은 별도 함수로 실행하는 편이 안전합니다.


트리거 사용 시 주의점

시간 기반 트리거는 긴 작업을 여러 번에 나눠 실행할 때 유용합니다. 하지만 트리거에도 quota와 제한이 있으므로 무한 재실행 구조를 만들면 안 됩니다. 처리할 데이터가 남았을 때만 다음 트리거를 만들고, 완료 후에는 진행 상태와 트리거를 정리해야 합니다.


트리거가 자동 실행되지 않을 때는 권한 승인, 설치형 트리거 생성 여부, 실행 계정, 실패 알림, 실행 로그를 확인해야 합니다. 단순 트리거와 설치형 트리거는 권한과 동작 범위가 다르므로, Gmail·Drive·외부 API처럼 권한이 필요한 작업은 설치형 트리거 구조가 필요한 경우가 많습니다.


권한 오류와 함께 발생하는 경우

처음 실행하거나 Gmail, Drive, Sheets 권한이 필요한 코드를 추가하면 실행 시간 초과보다 권한 승인 오류가 먼저 발생할 수 있습니다. 이 경우 실행 시간을 줄이기 전에 권한 승인이 완료되어야 합니다.


예를 들어 Gmail 메일을 검색해 시트에 기록하는 자동화라면 Gmail 접근 권한과 Sheets 접근 권한이 필요할 수 있습니다. 권한 승인이 끝나지 않은 상태에서 트리거를 만들면 자동 실행 단계에서 실패할 수 있습니다.


재발 방지 체크리스트

코드를 수정한 뒤에는 아래 항목을 확인하면 같은 오류가 반복되는 일을 줄일 수 있습니다.


  • 반복문 안에서 getValue(), setValue()를 남발하지 않았는가
  • 한 번에 처리할 행 수를 정했는가
  • 외부 API 호출을 줄였는가
  • 트리거 완료 조건이 있는가
  • 중복 트리거 삭제 로직이 있는가
  • 로그로 마지막 처리 위치를 확인할 수 있는가
  • quota 공식 문서를 확인했는가

Apps Script의 실행 시간, 트리거, 서비스별 quota는 계정 유형과 Google 정책에 따라 달라질 수 있습니다. 발행 전이나 코드 적용 전에는 Google 공식 문서에서 최신 기준을 확인하는 것이 좋습니다.


공식 자료로 더 확인하기

Apps Script 제한, 배치 처리 권장 방식, 트리거 구조, 사용자 정의 함수 제한은 변경될 수 있습니다. 실제 업무 자동화에 적용하기 전에는 Google 공식 문서에서 최신 기준을 확인해야 합니다.


Google Apps Script Quotas for Google Services

Apps Script 서비스별 quota, 실행 시간, 트리거 관련 제한을 확인할 수 있습니다. 제한은 변경될 수 있으므로 최신 기준을 여기에서 확인하는 것이 좋습니다.

Apps Script quota 공식 문서 확인

Google Apps Script Best Practices

Spreadsheet 서비스에서 읽기와 쓰기를 묶어서 처리하는 batch operations 권장 방식을 확인할 수 있습니다.

Apps Script Best Practices 확인

Google Apps Script Installable Triggers

설치형 트리거와 time-driven trigger 구조를 확인할 수 있습니다. 긴 작업을 나눠 실행할 때 필요한 공식 기준입니다.

설치형 트리거 공식 문서 확인

Google Apps Script Simple Triggers

단순 트리거와 설치형 트리거의 차이를 확인할 수 있습니다. 자동 실행이 필요한 코드에서 어떤 트리거를 써야 하는지 판단할 때 도움이 됩니다.

단순 트리거 공식 문서 확인

Custom Functions in Google Sheets

Google Sheets 사용자 정의 함수의 동작 방식과 제한을 확인할 수 있습니다. 셀 함수에서 실행 시간 초과가 날 때 먼저 확인할 문서입니다.

Google Sheets 사용자 정의 함수 공식 문서 확인

함께 보면 좋은 글

구글시트 반복 작업을 Apps Script로 줄이는 기본 흐름
실행 시간 초과를 줄이려면 Apps Script가 구글시트 데이터를 어떻게 읽고 쓰는지 먼저 이해하는 것이 좋습니다. 자동화 구조를 처음 잡을 때 함께 보기 좋은 글입니다.
Google Apps Script 자동화 입문: 구글시트 반복 작업 줄이는 법

권한 승인 오류가 먼저 뜰 때 확인할 기준
Gmail, Drive, Sheets 권한이 필요한 코드는 실행 시간 초과보다 권한 오류가 먼저 보일 수 있습니다. 승인 화면과 경고 메시지 처리 흐름을 함께 확인할 수 있습니다.
Google Apps Script 권한 오류 해결: Authorization required와 This app isn’t verified 처리법

Gmail 반복 처리 자동화 구조 살펴보기
Gmail 메일을 검색해 구글시트에 저장하는 자동화는 반복 처리와 권한, 실행 시간 제한을 함께 고려해야 합니다. 메일 자동화 예시를 보고 구조를 비교할 수 있습니다.
Gmail 자동화 방법: 문의 메일을 구글시트에 자동 정리하는 법

자주 묻는 질문

Q1. Google Apps Script 실행 시간 제한을 늘릴 수 있나요?

일반적으로 사용자가 코드 안에서 실행 시간 제한 자체를 원하는 만큼 늘리는 방식으로 해결하지 않습니다. 대신 처리 범위를 줄이고, 시트 읽기·쓰기를 배치 처리로 바꾸고, 긴 작업은 시간 기반 트리거로 나눠 실행하는 방식으로 설계해야 합니다. 정확한 제한은 계정 유형과 Google 정책에 따라 달라질 수 있으므로 공식 quota 문서를 확인하는 것이 좋습니다.


Q2. getValues와 setValues로 바꾸면 항상 해결되나요?

셀 단위 읽기와 쓰기가 원인이라면 큰 도움이 됩니다. 하지만 데이터가 매우 많거나 외부 API, Gmail, Drive 호출이 반복된다면 배치 처리만으로 부족할 수 있습니다. 이때는 한 번에 처리할 행 수를 정하고, 마지막 처리 위치를 저장한 뒤, 시간 기반 트리거로 남은 작업을 이어서 실행하는 구조가 필요합니다.


Q3. 시간 기반 트리거로 나누면 안전한가요?

시간 기반 트리거는 긴 작업을 여러 번에 나눠 처리할 때 유용합니다. 다만 완료 조건 없이 계속 새 트리거를 만들면 중복 실행이나 무한 실행 구조가 될 수 있습니다. 진행 위치를 저장하고, 남은 데이터가 있을 때만 다음 트리거를 만들고, 작업이 끝나면 상태값과 기존 트리거를 정리해야 합니다.


Q4. 사용자 정의 함수에서 Exceeded maximum execution time이 뜨면 어떻게 해야 하나요?

Google Sheets 셀에 직접 넣는 사용자 정의 함수는 짧게 끝나는 계산에 적합합니다. 대량 데이터 수정, 다른 셀 수정, Gmail·Drive 처리, 외부 API 반복 호출은 사용자 정의 함수보다 메뉴 실행 또는 설치형 트리거 구조가 더 적합합니다. 셀 함수 안의 작업을 줄이거나 별도 실행 함수로 분리하는 방향으로 수정하는 것이 좋습니다.


Exceeded maximum execution time 오류는 셀 단위 반복을 줄이고, 긴 작업을 작은 묶음으로 나누어 진행 위치를 저장하는 구조로 해결하는 것이 핵심입니다.