구글 시트 달력 자동화를 찾는 사람은 보통 “시트 행을 읽어 구글 캘린더에 일정만 만들면 된다”고 생각하지만, 실제 손해는 일정 생성 이후에 생깁니다. 스크립트가 중간에 멈췄는데 Event ID를 기록하지 않았거나, 같은 트리거가 두 번 살아 있거나, 팀 공유 캘린더가 아닌 개인 기본 캘린더에 들어가면 일정 중복과 누락을 사람이 다시 찾아야 합니다.
이 글은 구글 시트 달력 자동화를 바로 실행하기 전에 확인할 공식 한도, 선택 기준, 최소 코드, 실패 방지 순서를 분리해 설명합니다. 결론은 단순합니다. 1) 시트에 Event ID 열과 캘린더 ID를 먼저 확정하고, 2) LockService·PropertiesService·280초 감시·다음 트리거 예약을 한 흐름으로 넣고, 3) 실행 뒤 중복 트리거와 미기록 행부터 점검하세요.
| 역할 | 자료 | 이 글에서 쓰는 범위 |
|---|---|---|
| 분석 대상 원문 | 구글 시트에서 캘린더 일정을 생성하는 내부 운영 시나리오 | 행 데이터, 시작일, 종료일, 제목, Event ID 열을 가진 업무용 시트를 기준으로 설명합니다. |
| 공식 확인 자료 | Google Apps Script quotas | 단일 실행 시간, 트리거 총 실행 시간, 서비스별 일일 한도처럼 자동화 설계에 영향을 주는 기준을 확인했습니다. |
| 공식 확인 자료 | Google Calendar API quota guide | Calendar API의 분당 사용량 제한, 403·429 응답, exponential backoff 권장 등 호출량 관리 기준을 확인했습니다. |
| 공식 확인 자료 | Apps Script LockService | 동시 실행을 막기 위해 문서 단위 잠금을 획득하고 해제하는 근거로 사용했습니다. |
| 보조 공식 자료 | ScriptApp reference, PropertiesService reference | 다음 실행 트리거 예약과 중단 행 번호 저장에 필요한 메서드를 확인했습니다. |
| 편집 판단 | lifestep.io 실무 편집 기준 | 280초 자진 중단, 500건 단위 점검, Event ID 즉시 기록은 공식 한도 위에 얹은 보수적 운영 기준입니다. |
- 공식 문서상 Apps Script 단일 실행 시간은 일반적으로 6분입니다. 그래서 코드는 360초를 채우기 전에 스스로 멈추고 다음 실행을 예약해야 합니다.
- Event ID 열이 없는 구글 시트 달력 자동화는 재실행할 때 같은 일정을 다시 만들 수 있습니다. “생성 후 즉시 기록”이 중복 방지의 출발점입니다.
- 무료 계정과 Workspace 계정은 단일 실행 6분 기준은 같게 보더라도, 일일 총 실행 시간과 서비스 할당량은 계정 유형에 따라 달라질 수 있습니다.
- Calendar API 문서는 분당 할당량과 403·429 응답을 다룹니다. CalendarApp을 쓰는 Apps Script 서비스 한도와 완전히 같은 문서가 아니므로 섞어 말하면 안 됩니다.
- 500건 단위로 끊어 확인하라는 기준은 공식 숫자가 아니라 편집자 판단입니다. 장애 범위를 작게 만들기 위한 운영 단위로 이해해야 합니다.
지금 바로 고쳐야 할 답은 “일정 만들기”가 아니라 “다시 실행해도 안전한가”입니다
구글 시트 달력 자동화의 첫 판단 기준은 코드가 예쁘게 짧은지가 아닙니다. 같은 행을 두 번 실행해도 같은 일정이 두 번 생기지 않는지, 중간에 멈춰도 다음 행부터 이어지는지, 버튼을 두 사람이 눌러도 한 프로세스만 실행되는지가 먼저입니다.
공식 사실: Apps Script quotas 문서는 스크립트 실행 시간과 트리거 관련 한도를 계정 유형별로 제시합니다. 편집 판단: 이 한도에 딱 맞춰 359초까지 돌리는 설계는 위험합니다. 시트 쓰기, 캘린더 응답 지연, 트리거 예약 시간을 남기기 위해 280초 전후에서 끊는 편이 실무적으로 안정적입니다.
지금 확인하지 않으면 사람이 다시 지워야 하는 비용이 생깁니다
구글 시트 달력 자동화가 실패할 때 가장 비싼 비용은 스크립트 오류 메시지가 아니라 캘린더에 이미 생긴 결과입니다. 고객 미팅, 교육 일정, 콘텐츠 발행 일정처럼 알림이 걸린 이벤트가 중복되면 삭제 작업뿐 아니라 팀 신뢰도까지 손상됩니다.
특히 공유 시트에서 CalendarApp.getDefaultCalendar()를 쓰면 실행자의 개인 기본 캘린더가 대상이 됩니다. 팀 캘린더에 쌓여야 할 일정이 개인 캘린더로 흩어지면 누락인지 권한 문제인지 구분하기 어렵습니다. 팀 운영 자동화라면 캘린더 ID를 고정하고, 담당자가 바뀌어도 같은 캘린더에 들어가게 만들어야 합니다.
단순 반복문을 고르면 어디에서 손실이 터질까요?
가장 흔한 손실은 네 가지입니다. 첫째, Event ID를 기록하기 전에 종료되어 재실행 때 중복 생성됩니다. 둘째, 이전에 만든 시간 기반 트리거가 남아 같은 작업을 반복합니다. 셋째, 시작 행 번호를 저장하지 않아 매번 처음부터 돕니다. 넷째, 기본 캘린더를 써서 실행자마다 다른 곳에 일정이 들어갑니다.
불확실한 점: Google Calendar API quota 문서는 API 호출의 분당 사용량과 오류 응답을 설명하지만, Apps Script의 CalendarApp 서비스 사용 제한을 행 수 단위로 보장하지 않습니다. 편집 판단: 그래서 “하루에 몇 건까지 무조건 가능하다”보다 “작게 나누고, 실패 지점을 기록하고, 재시도 가능하게 만든다”가 더 안전한 기준입니다.
무료 계정으로 할지 Workspace로 할지 먼저 무엇을 비교해야 하나요?
비교 기준은 가격보다 작업량과 책임 범위입니다. 개인 일정 몇십 건을 등록하는 정도라면 무료 계정으로도 시작할 수 있습니다. 반대로 팀 일정, 고객 알림, 반복 발행 일정처럼 실패가 외부에 보이는 자동화라면 Workspace 계정, 공유 캘린더 소유권, 관리자 보안 정책을 함께 봐야 합니다.
| 상황 | 먼저 고를 기준 | 피해야 할 선택 |
|---|---|---|
| 개인 일정 50건 이하 | 수동 검수 가능한 단순 자동화 | 처음부터 복잡한 트리거 체계만 만들고 검수 열을 빼는 선택 |
| 팀 공유 일정 100~500건 | 고정 캘린더 ID, Event ID 열, LockService | 기본 캘린더와 수동 재실행에 의존하는 선택 |
| 정기적으로 수백 건 이상 | 280초 이어달리기, 중단 행 저장, 트리거 정리 | 한 번에 끝난다는 가정으로 전체 행을 반복하는 선택 |
| 보안 심사가 있는 조직 | 필요 권한, 실행 계정, 감사 가능성 | 개인 계정으로 만든 스크립트를 팀 운영에 붙이는 선택 |
구글 시트 달력 자동화를 바로 실행하려면 이 최소 코드 흐름부터 넣으세요
아래 예시는 “완성형 제품 코드”가 아니라 실패를 막는 최소 골격입니다. 시트의 1행은 헤더이고, A열 제목, B열 시작 시간, C열 종료 시간, D열 캘린더 ID, E열 Event ID라고 가정합니다. 실제 운영에서는 시간대, 필수값 검증, 제목 규칙, 로그 열을 추가하세요.
const SHEET_NAME = 'CalendarJobs';
const EVENT_ID_COL = 5;
const START_ROW_KEY = 'NEXT_ROW';
const HANDLER = 'syncSheetRowsToCalendar';
const MAX_RUNTIME_MS = 280 * 1000;
function syncSheetRowsToCalendar() {
const startedAt = Date.now();
const lock = LockService.getDocumentLock();
lock.waitLock(30 * 1000);
try {
clearContinuationTriggers_();
const props = PropertiesService.getScriptProperties();
const sheet = SpreadsheetApp.getActive().getSheetByName(SHEET_NAME);
const lastRow = sheet.getLastRow();
let row = Number(props.getProperty(START_ROW_KEY) || 2);
for (; row <= lastRow; row++) {
if (Date.now() - startedAt > MAX_RUNTIME_MS) {
props.setProperty(START_ROW_KEY, String(row));
SpreadsheetApp.flush();
ScriptApp.newTrigger(HANDLER).timeBased().after(60 * 1000).create();
return;
}
const [title, startsAt, endsAt, calendarId, eventId] = sheet.getRange(row, 1, 1, EVENT_ID_COL).getValues()[0];
if (eventId) continue;
if (!title || !startsAt || !endsAt || !calendarId) continue;
const calendar = CalendarApp.getCalendarById(calendarId);
const event = calendar.createEvent(title, new Date(startsAt), new Date(endsAt));
sheet.getRange(row, EVENT_ID_COL).setValue(event.getId());
SpreadsheetApp.flush();
}
props.deleteProperty(START_ROW_KEY);
} finally {
lock.releaseLock();
}
}
function clearContinuationTriggers_() {
ScriptApp.getProjectTriggers()
.filter(trigger => trigger.getHandlerFunction() === HANDLER)
.forEach(trigger => ScriptApp.deleteTrigger(trigger));
}
공식 사실: LockService는 동시 실행 제어, PropertiesService는 실행 사이에 남는 속성 저장, ScriptApp은 트리거 생성과 삭제를 제공합니다. 편집 판단: SpreadsheetApp.flush()는 Event ID 기록 직후와 다음 실행 예약 직전에 둡니다. “성능을 위해 마지막에 한 번만 저장”하면 강제 종료 시 기록되지 않은 생성 결과가 남을 수 있습니다.
코드를 붙이기 전에는 행 구조와 캘린더 소유권부터 정해야 합니다
실행 전 마지막 점검은 코드가 아니라 데이터 계약입니다. 누가 봐도 같은 의미로 읽히는 열 구조가 있어야 자동화가 오래 갑니다.
- Event ID 열은 사람이 임의로 수정하지 못하게 보호합니다.
- 캘린더 ID는 행마다 둘지, 설정 시트에 한 번만 둘지 정합니다.
- 시작 시간과 종료 시간은 날짜 객체로 저장하고, 텍스트 날짜를 섞지 않습니다.
- 재실행 버튼을 만들 경우 실행 권한과 실행 계정을 문서화합니다.
- 처음에는 전체 행이 아니라 10건, 50건, 100건 순서로 늘려 검수합니다.
반복 업무 전체 설계를 같이 손봐야 한다면 업무 자동화 설계 가이드를 먼저 보고, 자동화가 매출·고객 접점에 닿는다면 자동화 리스크 관리 기준까지 확인하는 편이 낫습니다.
중복 일정이 이미 생겼다면 원인은 이 순서로 좁히세요
중복이 생겼을 때는 “구글이 느려서”라고 넘기면 다음 실행에서 같은 일이 반복됩니다. 먼저 Event ID 열이 비어 있는 행이 있는지 확인하세요. 일정은 생성됐는데 ID가 비어 있다면 기록 전 종료, flush 전 중단, 열 보호 해제 가능성이 큽니다.
- Event ID 열이 비어 있는데 캘린더에는 일정이 있는 행을 찾습니다.
- Apps Script 트리거 목록에서 같은 핸들러가 여러 개 남아 있는지 확인합니다.
- 실행 로그에서 403, 429, timeout, permission 오류를 분리합니다.
- 기본 캘린더가 아니라 의도한 공유 캘린더 ID로 생성됐는지 확인합니다.
- 중단 행 번호가 PropertiesService에 남아 다음 실행을 같은 지점부터 시작하는지 확인합니다.
이 점검 순서는 확률값이 아니라 운영상 원인 분리 순서입니다. 실제 원인은 계정 한도, 권한, 데이터 형식 오류가 함께 섞일 수 있습니다.
“그냥 Zapier나 Make를 쓰면 안 되나요?”라는 반론에 답합니다
가능합니다. 결제형 자동화 도구는 연결, 재시도, 로그 화면이 편하고 비개발자 운영에 유리합니다. 다만 구글 시트 달력 자동화가 내부 데이터 규칙, 캘린더 권한, 예외 처리와 강하게 묶이면 직접 Apps Script를 쓰는 편이 더 투명할 수 있습니다.
결제 직전이라면 세 가지를 비교하세요. 월 실행 횟수 초과 비용, 실패한 작업의 재처리 방식, Event ID 같은 외부 시스템 ID를 시트에 다시 저장할 수 있는지입니다. 이 세 가지가 불명확하면 도구를 사도 중복 일정 문제는 남습니다.
팀에 도입하기 전 자주 막히는 질문은 여기서 갈립니다
무료 Gmail 계정으로 팀 공유 캘린더 자동화를 계속 돌려도 되나요?
작은 테스트는 가능하지만 팀 운영의 기본값으로 추천하기는 어렵습니다. 공식 quota 문서에서 계정 유형별 제한이 다르게 제시되고, 조직 보안 정책과 소유권 이관 문제가 생길 수 있기 때문입니다. 팀 일정이 외부 고객이나 매출 일정과 연결된다면 Workspace 계정과 공유 캘린더 소유권을 먼저 정하세요.
보안팀이 캘린더 권한 승인을 막으면 무엇을 줄여야 하나요?
먼저 실행 계정, 접근 캘린더, 필요한 스코프, 로그 보관 범위를 문서화하세요. “모든 캘린더를 마음대로 수정하는 자동화”처럼 보이면 승인 가능성이 낮아집니다. 특정 공유 캘린더 ID만 대상으로 삼고, 시트 권한을 제한하며, 실패 로그와 변경 이력을 남기는 방식으로 예외 승인을 요청하는 편이 현실적입니다.
280초 기준은 공식 권장값인가요?
아닙니다. 공식 문서에서 확인되는 핵심은 단일 실행 시간 한도입니다. 280초는 종료 처리, Event ID 저장, 다음 트리거 예약 시간을 남기기 위한 편집자 판단입니다. 데이터가 작거나 호출 지연이 적으면 더 짧게 끊어도 되고, 조직 정책상 더 보수적으로 240초를 써도 됩니다.
코드 적용 직전에는 이 5단계만 다시 확인하세요
- 시트에 제목, 시작, 종료, 캘린더 ID, Event ID 열이 고정되어 있는지 확인합니다.
- 개인 기본 캘린더가 아니라 운영 대상 공유 캘린더 ID를 쓰는지 확인합니다.
- Event ID가 있는 행은 건너뛰고, 생성 직후 ID를 기록하는지 확인합니다.
- 280초 전후로 중단 행을 저장하고 다음 트리거를 하나만 예약하는지 확인합니다.
- 10건 테스트 후 캘린더 결과, Event ID 기록, 남은 트리거, 로그 오류를 함께 확인합니다.
이 순서를 통과하면 구글 시트 달력 자동화는 “한 번 돌아간 코드”가 아니라 “멈춰도 복구 가능한 운영 절차”에 가까워집니다.
다음에 무엇을 읽을지는 지금 막힌 지점으로 고르세요
| 현재 상태 | 지금 읽지 않으면 생기는 비용 | 읽으면 해결되는 문제 | 다음 글 |
|---|---|---|---|
| 시트 자동화는 되지만 전체 업무 흐름이 자주 끊김 | 개별 스크립트만 늘어나고 담당자 교체 때 운영 지식이 사라집니다. | 업무 자동화를 설계 단위로 나누고 검수 지점을 만들 수 있습니다. | 업무 자동화 설계 가이드 |
| 캘린더 일정이 고객 알림, 매출, 납기와 연결됨 | 중복·누락이 단순 오류가 아니라 신뢰와 매출 리스크로 번집니다. | 자동화 실패를 비용 관점에서 점검하고 중단 기준을 세울 수 있습니다. | 자동화 리스크 관리 기준 |
| 반복 콘텐츠 제작 일정도 함께 자동화하고 싶음 | 캘린더만 정리되고 실제 제작·검수·발행 흐름은 따로 놀 수 있습니다. | 짧은 콘텐츠 제작 흐름을 일정과 작업 단위로 연결하는 기준을 잡을 수 있습니다. | AI 30초 숏폼 워크플로우 |