구글 앱스 스크립트 `UrlFetchApp` 요청 시 '429 Too Many Requests' 오류 해결: `Utilities.sleep()`과 `try-catch`를 활용한 지연 호출 및 자동 재시도 로직 가이드

구글 앱스 스크립트 429 오류: 2026년 기준 최적의 해결책

구글 앱스 스크립트(Google Apps Script, 이하 GAS)를 활용해 외부 API 데이터를 수집하거나 자동화 시스템을 구축할 때 가장 흔히 직면하는 난관은 바로 '429 Too Many Requests' 에러입니다. 이 오류는 클라이언트가 특정 시간 동안 서버가 허용하는 한도보다 더 많은 요청을 보냈을 때 서버가 스스로를 보호하기 위해 연결을 차단하는 응답 코드입니다. 2026년 3월 현재, 대부분의 최신 API(OpenAI, Google Cloud, Naver Cloud 등)는 엄격한 Rate Limit을 적용하고 있으므로, `Utilities.sleep()`을 활용한 단순 지연이나 '지수 백오프(Exponential Backoff)' 알고리즘을 통한 자동 재시도 로직 구현이 필수적입니다.

구글 앱스 스크립트 코딩 화면 및 에러 로그 예시
▲ API 요청 과부하로 발생하는 429 에러는 체계적인 재시도 로직으로 해결 가능합니다.
💡 핵심 요약: 429 오류는 단순한 코딩 실수가 아니라 서버의 '속도 제한' 정책입니다. 스크립트 내에 에러를 감지하는 try-catch 문을 배치하고, 재시도 시마다 대기 시간을 늘리는 로직을 추가하면 99% 이상 해결됩니다.

1. 429 Too Many Requests 발생 원인 분석

2026년의 웹 생태계에서 API 서버들은 자원 남용을 방지하기 위해 정교한 트래픽 제어 시스템을 가동합니다. GAS 환경에서 이 에러가 발생하는 주요 원인은 다음과 같습니다.

  • API 제공처의 초당 호출 제한(Rate Limit): 무료 플랜의 경우 초당 1~3회, 유료 플랜도 분당 수백 회로 제한되는 경우가 많습니다.
  • 구글 앱스 스크립트 자체 할당량: Google Workspace 계정 종류에 따라 하루에 수행할 수 있는 `UrlFetchApp` 호출 총량이 정해져 있습니다.
  • 병렬 트리거 충돌: 여러 개의 시간 기반 트리거가 동시에 작동하여 동일한 API 엔드포인트에 요청을 집중시킬 때 발생합니다.
  • 반복문 내 무차별 호출: `for` 루프 안에서 지연 시간 없이 연속적으로 `fetch()`를 수행하는 경우입니다.

2. 해결법 1: Utilities.sleep()을 활용한 기본 지연

가장 직관적인 방법은 요청과 요청 사이에 인위적인 휴식 시간을 주는 것입니다. 데이터 처리량이 많지 않을 때 유용합니다.

function simpleFetch() {
  const urls = ["https://api.example.com/1", "https://api.example.com/2"];
  
  urls.forEach(url => {
    const response = UrlFetchApp.fetch(url);
    console.log(response.getContentText());
    
    // 1.5초(1500ms) 동안 스크립트 실행 중단
    Utilities.sleep(1500); 
  });
}

이 방식은 구현이 쉽지만, 서버 상태가 양호하여 더 빨리 처리할 수 있는 상황에서도 무조건 대기해야 하므로 전체 실행 시간이 길어진다는 단점이 있습니다.

3. 해결법 2: 지수 백오프(Exponential Backoff) 구현 (강력 추천)

지수 백오프는 에러가 발생했을 때만 대기 시간을 점진적으로 늘려가며 재시도하는 고도화된 기술입니다. 이는 구글 공식 문서에서도 권장하는 표준 방식입니다.

네트워크 트래픽 최적화 및 서버 부하 관리 컨셉
▲ 지수 백오프는 서버와 클라이언트 모두에게 가장 효율적인 통신 방식입니다.

핵심 재시도 함수 (fetchWithRetry)

아래 코드는 2026년 실무 환경에서 즉시 사용 가능한 안정화된 로직입니다. muteHttpExceptions: true 옵션을 사용하여 에러 발생 시에도 스크립트가 멈추지 않고 응답 코드를 분석하도록 설계되었습니다.

/**
 * 429 에러 대응을 위한 자동 재시도 fetch 함수
 */
function fetchWithRetry(url, options = {}) {
  const MAX_RETRIES = 5; // 최대 재시도 횟수
  let waitTime = 1000;   // 초기 대기 시간 (1초)

  // 에러 발생 시 스크립트 중단을 막기 위해 필수 설정
  options.muteHttpExceptions = true;

  for (let i = 0; i < MAX_RETRIES; i++) {
    const response = UrlFetchApp.fetch(url, options);
    const code = response.getResponseCode();

    // 성공(200~299) 시 응답 반환
    if (code >= 200 && code < 300) {
      return response;
    }

    // 429(Too Many Requests) 또는 5xx(서버 오류)인 경우 재시도
    if (code === 429 || (code >= 500 && code < 600)) {
      if (i === MAX_RETRIES - 1) break; // 마지막 시도면 루프 종료

      console.warn(`[경고] ${code} 에러 발생. ${waitTime}ms 후 재시도합니다... (시도 ${i + 1})`);
      Utilities.sleep(waitTime);
      
      // 대기 시간을 2배로 늘리고 약간의 무작위성(jitter) 추가
      waitTime = (waitTime * 2) + Math.floor(Math.random() * 1000);
    } else {
      // 400(잘못된 요청), 401(인증 실패) 등은 재시도 의미 없음
      throw new Error(`요청 실패: ${code} - ${response.getContentText()}`);
    }
  }
  
  throw new Error("최대 재시도 횟수를 초과했습니다.");
}
⚠️ 주의사항: 재시도 로직을 구현할 때는 반드시 MAX_RETRIES를 설정해야 합니다. 무한 재시도는 구글 계정의 서비스 일시 정지를 초래할 수 있습니다. 또한, 사용하시는 API의 비용 정보는 2026년 기준 수시로 변동될 수 있으니 구매 전 공식 사이트에서 확인하시기 바랍니다.

4. 솔루션 비교 분석

각 방식의 특징을 비교하여 프로젝트 규모에 맞는 최적의 전략을 선택하세요.

항목 단순 지연 (Sleep) 지수 백오프 (Backoff)
구현 난이도 매우 낮음 중간 (함수화 권장)
처리 속도 느림 (고정 대기) 빠름 (에러 시에만 대기)
안정성 낮음 (예외 대응 불가) 매우 높음 (유동적 대응)
추천 대상 개인용 간단한 작업 기업용 자동화 시스템

5. 추가 최적화 팁: Quotas 관리

API 요청 성공률을 높이기 위해 코드 외적으로 관리해야 할 2026년형 팁입니다.

  1. 트리거 시간 분산: 매 정각(00분)에 실행되는 트리거가 많으면 구글 서버 자체의 부하로 인해 실패 확률이 높습니다. '오전 02:07'과 같이 비대칭적인 시간에 트리거를 설정하세요.
  2. 캐싱(CacheService) 활용: 동일한 데이터를 반복해서 요청해야 한다면 `CacheService`를 사용해 API 호출 횟수 자체를 줄이십시오.
  3. 벌크(Bulk) API 사용: 100개의 데이터를 가져올 때 100번 호출하는 대신, 한 번에 100개를 가져오는 벌크 엔드포인트를 제공하는지 확인하세요.
데이터 효율성 및 캐시 시스템 시각화
▲ API 호출 횟수를 줄이는 것 자체가 최고의 최적화 전략입니다.

6. 자주 묻는 질문 (FAQ)

Q1: muteHttpExceptions 옵션을 꼭 사용해야 하나요?
A: 네, 필수적입니다. 기본적으로 GAS는 4xx/5xx 에러가 발생하면 즉시 실행을 중단(Exception 발생)합니다. 재시도 로직을 태우려면 이 옵션을 true로 설정해 응답 객체를 먼저 받아야 합니다.

Q2: Utilities.sleep()의 최대 대기 시간 제한이 있나요?
A: GAS의 총 실행 시간 제한(개인 6분, Workspace 30분) 내에서는 자유롭지만, 너무 긴 대기 시간은 전체 스크립트 타임아웃을 유발할 수 있으므로 주의해야 합니다.

Q3: 429 에러가 계속되는데 IP 차단인가요?
A: 429는 일시적인 제한인 경우가 많습니다. 하지만 지속적으로 무시하고 요청을 보내면 서버에서 IP를 일정 시간 블랙리스트에 올릴 수 있으니 즉시 실행을 멈추고 로직을 수정해야 합니다.

Q4: 구글 할당량(Quota)을 확인하는 방법은?
A: Google Cloud Console의 API 대시보드에서 UrlFetch 사용량을 모니터링하거나, 스크립트 내에서 특정 API 호출을 통해 잔여 할당량을 체크할 수 있습니다.

Q5: 재시도 로직을 써도 500 에러가 납니다.
A: 500 에러는 서버 내부 오류입니다. 지수 백오프는 서버의 일시적 과부하에 효과적이지만, 서버 자체의 버그나 점검 중일 때는 해결되지 않습니다. 이 경우 에러 로그를 남기고 관리자에게 알림을 보내는 처리가 필요합니다.

※ 면책 고지 (Disclaimer): 본 포스팅에서 제공하는 코드는 교육 및 참고 목적으로 작성되었습니다. 실제 운영 환경에 적용하기 전 충분한 테스트를 거치시기 바라며, API 호출로 인해 발생하는 비용이나 데이터 손실에 대해서는 책임을 지지 않습니다. 2026년 3월 기준의 최신 정보를 반영하고 있으나, 구글 서비스 정책 변화에 따라 세부 수치는 달라질 수 있습니다. 외부 링크 이용 시 rel="noopener nofollow" 원칙을 준수합니다.

댓글

이 블로그의 인기 게시물

윈도우 11 '연결됨, 인터넷 없음' 오류 해결: DNS 캐시 초기화와 IPv4 수동 설정 가이드

챗봇의 외부 DB 연동 오류 해결: Botpress의 Execute Code와 Voiceflow의 API Step을 활용한 JSON 데이터 파싱 및 변수 매핑 비교

노션 포뮬러 2.0 `filter()`와 `map()` 함수를 활용해 관계형 데이터베이스에서 특정 조건의 팀원 이름만 추출하여 리스트로 자동 생성하는 방법