GAS엔 점진 배포가 없다: '맞는' 검사 하나가 전 사용자를 잠근다
Apps Script는 promote 하는 순간 100% 사용자에게 나간다 — 카나리가 없다. push와 deploy의 차이, 새 검사를 compat → warn → enforce로 내보내는 법, 옛 클라이언트를 세는 텔레메트리, 그리고 사고 시 롤백 명령까지.
GAS엔 점진 배포가 없다. promote 하면 그 순간 100%에게 나간다. 이미 현장 폰에 깔린 클라이언트가 모르는 새 검사를 강제하면, 배포 즉시 전 사용자가 잠긴다. “보안 강화”라고 세션 검사를 넣었다가, 옛 클라이언트가 토큰을 안 보내서 전 현장의 이력 조회가 막힌 적이 있다.

왜 중요한가
카나리가 없으니 “일부에게 먼저 내보고 문제 없으면 확대”가 불가능하다. 코드가 로컬 테스트를 다 통과해도, 필드의 클라이언트는 지난주 번들이다. 서버만 새 계약을 강제하면 그 순간 옛 클라이언트 전부와 계약이 어긋난다. 롤백해도 그 사이 현장은 멈춰 있다.
push, deploy, promote — 뭐가 사용자에게 가나
먼저 배포 모델부터. clasp push는 소스만 올린다. 사용자는 여전히 옛 버전을 받는다. 사용자에게 가는 건 clasp deploy가 새 버전을 자를 때다.
clasp push # 로컬 → Apps Script 소스 (사용자엔 영향 없음)
clasp deploy -i <deploymentId> -d "메모" # 이 배포 ID를 새 버전으로 (사용자에게 반영)
clasp deployments # 배포 ID·버전 목록 확인
하나의 배포 ID를 유지하고 버전만 올려라. 그래야 웹앱 주소가 안 바뀐다(주소 고정). 매번 새 배포를 만들면 /exec 주소가 바뀌어 옛 링크가 죽는다.
정답: 새 검사는 compat → warn → enforce 사다리로
새로 강제하는 게이트를 즉시 enforce로 내보내지 마라. 스크립트 속성으로 모드를 두고, 기본을 안 깨지는 compat로 시작한다. warn 단계에서 옛 클라이언트 히트를 세서, 0이 되면 그때 enforce로 올린다.
function sessionMode_() {
return PropertiesService.getScriptProperties()
.getProperty("APP_SESSION_MODE") || "compat"; // 기본은 안 깨지는 compat
}
function requireApprovedSession_(token) {
const ok = token && isApprovedSession_(token);
const mode = sessionMode_();
if (ok) return;
if (mode === "compat") { recordLegacyHit_(); return; } // compat: 통과 + 카운트
if (mode === "warn") { // warn: 통과 + 기록
recordLegacyHit_();
console.warn("session missing (warn mode)");
return;
}
throw new Error("APP_SESSION_REQUIRED"); // enforce: 이때만 차단
}
// 옛 클라이언트(토큰 없는 요청) 히트를 센다 → 이 값이 0으로 수렴하면 enforce 승격 가능
function recordLegacyHit_() {
const p = PropertiesService.getScriptProperties();
p.setProperty("legacyHits", String(Number(p.getProperty("legacyHits") || 0) + 1));
}
승격 순서: compat로 배포 → 클라이언트 업데이트 배포 → warn으로 며칠 관찰 → legacyHits가 0이면 enforce. 모드 전환은 코드 재배포 없이 스크립트 속성만 바꾸면 된다.
사고 나면: 롤백
같은 배포 ID를 이전 안전 버전으로 다시 deploy 하면 즉시 되돌아간다.
# 이전 안전 버전으로 롤백 (배포 ID 유지, 버전만 이전으로)
clasp deploy -i <deploymentId> -V <이전_버전번호>
그래서 배포 직전에 현재 프로드 버전 번호를 어딘가 적어둬라. 사고는 대개 밤에 나고, 그 번호를 못 찾으면 롤백이 늦어진다.
놓치기 쉬운 것
- 카나리가 없다. 전부거나 전무다. 그래서 배포되는 기본값 자체가 안전해야 한다.
- 이전 버전 번호를 기록. 롤백은 배포 ID의 버전을 되돌리는 것. 그 번호를 미리 알아야 밤에 되돌린다.
- 필드의 클라이언트는 옛 코드다. 서버 변경은 옛 클라이언트와 호환되게. 새 필드·새 헤더를 “반드시 있어야 함”으로 만들지 마라(additive only).
- 모드는 데이터로 판단. “이제 됐겠지”가 아니라
legacyHits같은 실제 카운트가 0인지로 enforce를 결정하라.
깊이: 새 필수 계약은 언제나 사다리로
서버가 먼저 compat로 받아주고 → 클라이언트가 새 토큰을 보내기 시작하고 → 옛 클라이언트 히트가 0으로 수렴하고 → 그제야 enforce. 이 순서를 건너뛰면 필드가 멈춘다. 핵심 한 줄: 새 게이트는 기본을 compat로 두고, 옛 클라이언트가 사라진 걸 데이터로 확인한 뒤에만 enforce하라.
자주 묻는 질문
- Apps Script 배포를 일부 사용자에게만 먼저 내보낼 수 있나요?
- 없습니다. promote는 즉시 전역이라 카나리(일부 선배포)가 불가능합니다. 대신 코드 안에서 스크립트 속성 같은 모드 플래그로 새 규칙을 compat → warn → enforce 단계로 점진 강제하세요.
- clasp push를 했는데 왜 반영이 안 되나요?
- push는 소스만 업로드하고, deploy가 실제 버전을 웹앱으로 자릅니다. push가 'already up to date'라고 해도 사용자는 여전히 옛 버전을 받습니다. 배포는 push와 deploy가 별개 단계입니다.
- 새 배포가 기존 사용자를 잠갔습니다. 어떻게 복구하나요?
- 같은 배포 ID를 이전 안전 버전 번호로 다시 deploy 하면 롤백됩니다. 그다음 새 검사를 enforce가 아니라 compat 기본값으로 다시 내보내, 옛 클라이언트가 사라질 때까지 기다립니다.
- 롤백하려면 이전 버전 번호를 어떻게 알죠?
- clasp deployments로 배포·버전 목록을 볼 수 있지만, 배포 직전에 '지금 안전한 프로드 버전 번호'를 따로 기록해두는 게 확실합니다. 사고는 대개 밤에 나고, 그때 번호를 못 찾으면 롤백이 늦어집니다.