야간 배치 작업으로 스프레드시트의 1,000개 행을 ChatGPT API로 처리하던 중, 6분 제한에 걸려 처리가 실패하고 재실행하게 되었습니다. 그리고 재실행할 때마다 이전에 처리된 행까지 다시 과금되는 상황을 월간 API 사용 내역을 보고서야 알게 되었습니다. 본문에서는 동일한 요청을 캐시를 통해 확실하게 방지하는 설계 방법을, GAS의 3가지 방식(CacheService/Sheets/PropertiesService)에 대한 '선택 기준'과 그대로 복사하여 사용할 수 있는 완벽한 코드로 작성합니다.
| 수단 | 유지 기간 | 용량 상한 | 읽기/쓰기 속도 | 운영상 확인 가능 여부 | 적합한 케이스 |
|---|
| CacheService | 최대 6시간 | 100KB/건 | 최속 (메모리 수준) | 에디터에서 목록 확인 불가 | 당일 재실행, UI 응답 |
| ... | | | | | |
| 판단 기준은 단 하나입니다. 이전 실행부터 다음 실행까지의 간격이 6시간보다 긴지 여부입니다. 야간 배치는 실행 간격이 24시간이기 때문에, CacheService만으로는 매일 밤 실패합니다 (이것이 저의 두 번째 실수였습니다). 따라서 야간 배치 작업에는 Sheets가 핵심이며, CacheService는 당일 재실행 시 속도 향상이라는 L1 역할을 맡기는 것이 좋습니다. PropertiesService는 총합 500KB 상한이 빠르기 때문에, 배치 결과 저장소로는 사용하지 않습니다. | | | | | |
처음에 작성했던 구현에서는 캐시 키로 행 번호를 사용했습니다. 그런데 열을 하나 삽입하는 것만으로 모든 행의 위치가 밀리면서, 캐시는 '히트'했지만 실제로는 다른 행의 결과가 붙는 상황이 발생했습니다. 실수라면 재처리로 해결되지만, 잘못된 히트는 조용히 오류를 누적시킵니다. 여기서 얻은 교훈이 두 가지입니다.
- 키는 행 번호와 같은 위치 정보가 아니라,
프롬프트 본문 자체에서 생성해야 합니다 (SHA-256 해시)
- 프롬프트 문구가 변경되었을 때 이전 결과가 재사용되지 않도록, 키에 프롬프트 버전을 접두사로 추가해야 합니다.
// ===== ChatGPT API × GAS: L1/L2 캐시 완벽 형태 =====
// L1: CacheService (6시간) / L2: Sheets의 llm_cache 시트 (영구)
// 키: SHA-256 (프롬프트 정규화 + 프롬프트 버전 접두사)
...
설계의 핵심은 네 가지입니다.
키는 정규화 + SHA-256: 전각/반각, 공백의 차이로 인해 다른 키가 되는 사고를 소문자 변환과 공백 압축으로 방지합니다.
- temperature: 0이라도 매번 완전히 동일한 답변은 보장되지 않습니다. 분류와 같이 '같은 입력에 같은 답변'을 요구하는 처리는 캐시 측에서 고정하는 것이 올바른 분담입니다.
- L2가 히트하면 L1로 반환: 재처리할 때마다 Sheets의 선형 탐색(linear search)을 반복하지 않기 위함입니다 (선형 탐색은 1만 행까지 실용적이며, 초과 시 아래 3번 참고).
- Sheets에 추가는 LockService로 배타적 처리: 수동 실행과 야간 배치 작업이 겹치더라도 캐시 테이블이 이중으로 추가되는 것을 방지합니다.
| # | 잘못된 설계 | 발생한 문제 | 해결 방법 |
|---|
| 1 | 키에 행 번호를 사용함 | 열 삽입으로 모든 행의 위치가 밀려, 히트했음에도 내용이 다른 행의 결과였음 | 프롬프트 본문에서 SHA-256을 이용해 키를 생성합니다 |
| ... | | | |
1번 사례가 가장 복구하기 어렵습니다. 캐시 미스는 '느림'으로 끝나지만, 잘못된 히트는 집계 결과를 조용히 오염시킵니다. 위치 정보 키는 절대 사용하지 말고, 해시 키를 사용해야 합니다.
| # | 확인 항목 |
|---|
| 1 | 소문자 변환과 공백 압축을 키 생성 직전에 했는지 |
| ... | |
testCache()
을 그대로 실행합니다. Logger에 '1회차: MISS / 2회차: L1'이 출력되면 도입 완료입니다 (5분 소요). 배치 본체의 LLM 호출은
callLLM_()
로 대체하고, llm_cache 시트에서 캐시 종류별 비율을 1주일간 관측합니다. MISS가 다음 주에도 많다면, 키 정규화 체크리스트를 의심해야 합니다.
- 1만 행이 초과하면
findRowByKey_
의 선형 탐색은 중단합니다: 키 열을 Map으로 만들거나, 해시 앞 두 글자로 시트를 분할합니다.
본문과 같은 구현 패턴을 종합적으로 정리한 치트 시트와 Obsidian/Notion 템플릿 모음을 무료로 배포하고 있습니다. 8권의 내용에 '무엇을 어떤 도구로 할지' 판단하는 차트도 포함되어 있습니다.
- GAS×AI 배치 작업이 6분 만에 끊겨 요약본 380건을 이중 전송한 경험 — 체크포인트 재개와 LockService로 해결한 실패 사례 3가지
- GAS×AI 입문 — 스프레드시트에서 ChatGPT API를 구동하는 방법