Google Apps Script 자동화, 공짜인 줄 알았는데 돈이 새는 지점
Apps Script 자동화가 실제 업무에서 비용으로 바뀌는 지점을 트리거, 외부 API, 유지보수 관점에서 정리합니다.
결론 먼저: Google Apps Script 자동화, 공짜인 줄 알았는데 돈이 새는 지점라는 제목은 세게 잡았지만, 실제 핵심은 클릭 장사가 아니라 운영 설계입니다. 이 문제는 “툴을 하나 더 쓰면 해결”이 아니라 원본, 로그, 권한, 재시도, 동시성을 어떻게 잡느냐의 문제입니다.
좋은 자동화는 버튼이 아니라 증거를 남깁니다. 누가, 언제, 어떤 입력으로, 어떤 결과를 만들었고, 실패하면 어디까지 반영됐는지 설명할 수 있어야 합니다.
1. 왜 이런 일이 생기나

데모에서는 한 사람이 한 번 누릅니다. 실제 업무에서는 같은 폼이 두 번 제출되고, 네트워크가 끊기고, 담당자가 시트를 정렬하고, 브라우저가 저장소를 비우고, 외부 API가 느려지고, 관리자가 권한을 바꿉니다. 자동화가 무너지는 지점은 대부분 여기입니다.
특히 Google Apps Script와 시트 기반 자동화는 시작이 쉽습니다. 그래서 더 위험합니다. onFormSubmit 하나, 시간 기반 트리거 하나, 확인메일 하나를 붙이면 그럴듯한 업무 시스템처럼 보입니다. 하지만 아래 다섯 가지가 없으면 시스템이 아니라 “운 좋게 도는 스크립트”입니다.
| 필요한 것 | 없을 때 벌어지는 일 |
|---|---|
| 고유 ID | 같은 요청이 두 번 반영됩니다 |
| 원본/정제/로그 분리 | 사람이 고친 값과 자동화가 쓴 값이 섞입니다 |
| 실패 로그 | 고객이 알려줄 때까지 멈춘 줄 모릅니다 |
| 잠금/큐 | 동시 제출 때 마지막 저장이 앞 저장을 덮습니다 |
| 롤백 기준 | 배포 후 깨져도 어디로 돌아갈지 모릅니다 |
관련 기본기는 앱스크립트 자동화 가이드, 6분 실행 한도, LockService 동시성, 재시도와 멱등키를 같이 봐야 합니다.
2. 먼저 확인할 것
현재 사용하는 시트나 스크립트에는 아래 항목이 있는지 확인이 필요합니다. 기능 추가보다 이 점검이 먼저입니다.
- 입력마다 UUID나 폼 응답 ID가 있습니까?
- 원본 응답 시트를 사람이 직접 고치지 않습니까?
- 자동화 실행 결과가
runs같은 로그 시트에 남는가. - 실패했을 때 담당자에게 알림이 가는가.
- 같은 요청을 두 번 실행해도 결과가 한 번만 반영되는가.
appsscript.json권한 범위가 설명 가능한가.- 이전 배포 버전이나 이전 시트 백업으로 돌아갈 수 있는가.
3. 추천 시트 구조
업무가 작아도 시트는 최소 네 장으로 나누는 게 좋습니다.
| 시트 | 역할 | 사람이 직접 수정? |
|---|---|---|
raw | 폼·외부 API 원본 | 금지 |
clean | 정규화된 업무 데이터 | 제한적 허용 |
ledger | 승인·예약·발송·재고 같은 사건 기록 | 금지 |
runs | 스크립트 실행 로그 | 금지 |
raw는 증거입니다. clean은 사람이 보는 표입니다. ledger는 돈, 예약, 재고, 승인처럼 나중에 설명해야 하는 사건입니다. runs는 자동화의 블랙박스입니다. 이 네 장을 섞으면 나중에 값 하나가 틀렸을 때 원인을 찾을 수 없습니다.
4. 구현 뼈대
아래 구조가 핵심입니다. 실제 업무 로직보다 먼저 ID, 잠금, 실패 로그, 중복 방지가 들어갑니다.
function runJob(event) {
const id = event.id || Utilities.getUuid();
const lock = LockService.getScriptLock();
if (!lock.tryLock(10000)) throw new Error('busy: retry later');
const started = Date.now();
try {
if (alreadyProcessed_(id)) return { id, skipped: true };
const normalized = normalizeInput_(event);
validate_(normalized);
const result = doWork_(normalized);
markProcessed_(id, result);
logRun_('job', Date.now() - started, 'ok', id, '');
return result;
} catch (err) {
logRun_('job', Date.now() - started, 'error', id, String(err.message || err));
throw err;
} finally {
SpreadsheetApp.flush();
lock.releaseLock();
}
}
이 뼈대에서 중요한 건 doWork_()가 아닙니다. 진짜 중요한 건 그 앞뒤입니다. 이미 처리한 ID인지 확인하고, 동시에 두 실행이 같은 값을 만지지 못하게 잠그고, 실패해도 로그를 남기고, 마지막에 flush()로 시트 쓰기를 확정합니다. 이게 없으면 코드는 짧아 보이지만 운영은 길어집니다.
5. 판단 기준
아래 기준으로 “그냥 시트+Apps Script로 충분한지”와 “전용 SaaS나 서버가 필요한지”를 가릅니다.
| 상황 | 시트+Apps Script | 전용 SaaS/서버 |
|---|---|---|
| 하루 수십~수백 건 입력 | 적합 | 과함 |
| 동시 제출이 가끔 있음 | LockService로 가능 | 초당 다수면 필요 |
| 결제·법적 증빙이 핵심 | 보조 기록까지만 | 전문 도구 권장 |
| 이미지·파일 대량 저장 | Drive/IndexedDB 조합 | 전용 저장소 권장 |
| 외부 API가 여러 개 | 가능하나 로그 필수 | 장애 격리 필요 |
이 선을 넘으면 GAS를 떠나야 할 신호를 봐야 합니다. 작은 도구로 시작하는 건 좋지만, 작은 도구로 영원히 버티겠다는 뜻은 아닙니다.
6. 하지 말아야 할 것
- 행 번호를 ID로 쓰면 정렬이나 삭제 한 번에 기준이 깨질 수 있습니다.
- 원본 응답 시트 위에 수식과 수동 수정이 섞이면 추적이 어려워집니다.
catch (e) {}로 에러를 삼키면 실패 원인을 나중에 찾기 어렵습니다.- 외부 API 키를 코드에 직접 쓰면 권한 이전과 유출 대응이 어려워집니다. AI 비용과 키 관리를 참고할 수 있습니다.
- 자동메일, 예약 확정, 재고 차감처럼 되돌리기 어려운 작업은 검수 단계를 두는 편이 안전합니다.
- 무료 한도 안이라도 모니터링이 없으면 장애를 늦게 발견하게 됩니다.
마지막 점검 기준
이 글의 기준은 기능 목록이 아니라 운영 가능성입니다. 아래 네 가지가 갖춰져 있으면 작은 업무 자동화로는 꽤 안전한 편이고, 빠져 있으면 데모 단계에 가깝습니다.
- 원본 데이터와 사람이 보는 화면이 분리되어 있는가
- 실행 로그에 성공, 실패, 처리 건수, 오류 메시지가 남는가
- 같은 입력이 다시 들어와도 결과가 중복되지 않는가
- 권한 변경, 쿼터 초과, 외부 API 실패를 나중에 확인할 수 있는가
고객 응대, 예약 확정, 재고 차감, 결제처럼 되돌리기 어려운 업무라면 기준이 더 높아집니다. 그때는 Apps Script만으로 충분한지, 전용 SaaS나 별도 서버 구성이 더 안전한지까지 같이 봐야 합니다.
자주 묻는 질문
- 이 글은 어떤 사람에게 필요한가요?
- 구글 시트, 폼, 캘린더, Apps Script로 업무 자동화를 만들었거나 만들려는 사람에게 필요합니다. 특히 이미 데모는 되는데 운영 안정성이 걱정되는 경우에 맞습니다.
- 바로 구현할 때 가장 먼저 해야 할 일은 무엇인가요?
- 원본 시트와 업무 시트를 분리하고, runs 로그 시트를 만들어 마지막 성공·실패·실행시간을 남기는 일부터 해야 합니다.
- Apps Script로 충분하지 않은 경우는 언제인가요?
- 동시 요청이 많거나 결제·법적 증빙·대용량 파일·실시간 양방향 동기화가 핵심이면 전용 SaaS, 외부 DB, 서버 큐를 검토해야 합니다.