Haeminway haeminway
English
기술 노트로
4 분 분량

GAS엔 점진 배포가 없다: '맞는' 검사 하나가 전 사용자를 잠근다

Apps Script는 promote 하는 순간 100% 사용자에게 나간다 — 카나리가 없다. push와 deploy의 차이, 새 검사를 compat → warn → enforce로 내보내는 법, 옛 클라이언트를 세는 텔레메트리, 그리고 사고 시 롤백 명령까지.

GAS엔 점진 배포가 없다. promote 하면 그 순간 100%에게 나간다. 이미 현장 폰에 깔린 클라이언트가 모르는 새 검사를 강제하면, 배포 즉시 전 사용자가 잠긴다. “보안 강화”라고 세션 검사를 넣었다가, 옛 클라이언트가 토큰을 안 보내서 전 현장의 이력 조회가 막힌 적이 있다.

compat → warn → enforce 3단 사다리: 옛 클라이언트가 사라진 걸 확인한 뒤에만 위로 올린다

왜 중요한가

카나리가 없으니 “일부에게 먼저 내보고 문제 없으면 확대”가 불가능하다. 코드가 로컬 테스트를 다 통과해도, 필드의 클라이언트는 지난주 번들이다. 서버만 새 계약을 강제하면 그 순간 옛 클라이언트 전부와 계약이 어긋난다. 롤백해도 그 사이 현장은 멈춰 있다.

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로 배포·버전 목록을 볼 수 있지만, 배포 직전에 '지금 안전한 프로드 버전 번호'를 따로 기록해두는 게 확실합니다. 사고는 대개 밤에 나고, 그때 번호를 못 찾으면 롤백이 늦어집니다.