SWING의 배포는 GitHub Actions 워크플로우 3개로 굴러간다. 한동안 잘 돌아갔지만, 공유 패키지를 하나 건드린 날 사용자 화면이 잠깐 깨졌다. 원인을 추적하다 보니 파이프라인 곳곳에 시한폭탄이 있었다. 고치기 전에, 무엇이 어떻게 깨질 수 있는지부터 정리했다.
현재 구조
독립적인 워크플로우 3개가 있다.
| 워크플로우 | 트리거 | 배포 대상 |
|---|---|---|
deploy-backend.yml | apps/backend/** 또는 packages/** | ECS (NestJS 서버) |
deploy-frontend.yml | apps/frontend/** 또는 packages/** | S3 + CloudFront (React 앱) |
deploy-batch.yml | apps/backend/** 또는 packages/** | ECS (배치 서버) |
세 워크플로우는 서로를 모른다. 조율 메커니즘이 없다. 백엔드 배포 순서는 이렇다.
이미지 빌드 → ECR 푸시 → Task Definition 등록 → DB 마이그레이션 → ECS 배포이 단순한 구조에서 문제가 7개 나왔다.
문제 1 — FE가 먼저 배포되는 레이스 컨디션
packages/types에 새 필드를 추가하면 packages/** 경로에 걸려 백엔드와 프론트엔드 워크플로우가 동시에 트리거된다. 그런데 둘의 소요 시간이 다르다.
- 프론트엔드: pnpm install → Vite 빌드 → S3 업로드 → CloudFront 무효화 → 약 3~5분
- 백엔드: Docker 빌드 → ECR 푸시 → 마이그레이션 → ECS 안정화 → 약 10~25분
프론트엔드가 먼저 끝난다. 새 타입으로 만들어진 프론트엔드가 구 백엔드를 호출하는 상황이 수십 분간 이어진다. 새 엔드포인트를 부르면 404, 새 응답 필드를 참조하면 undefined. 사용자에겐 그냥 장애다.
문제 2 — 마이그레이션 성공 후 ECS 배포 실패
[7단계] DB 마이그레이션 (성공)[8단계] ECS 서비스 업데이트 (실패 — 이미지 크래시, 헬스체크 실패 등)DB 스키마는 새 버전인데 ECS는 구 이미지로 계속 돈다. 구 코드가 새 스키마를 만난다.
이 프로젝트가 마이그레이션에서 DROP을 금지하고 ADD만 허용하는 이유가 여기 있다. 신규 컬럼은 구 코드가 무시하면 그만이지만, 삭제된 컬럼을 구 코드가 참조하면 즉시 크래시다. 단, ADD-only는 완충재일 뿐 근본 해결책이 아니다. 기본값 없는 NOT NULL 컬럼을 추가하면 구 코드의 INSERT가 실패한다.
문제 3 — 마이그레이션 부분 실패 시 복구 불가
TypeORM 마이그레이션은 파일을 순차 실행한다. 한 파일 안의 여러 DDL이 하나의 트랜잭션으로 묶이지 않으면, 중간 실패 시 앞부분이 롤백되지 않는다.
ALTER TABLE employee ADD COLUMN bonus INT; -- 성공ALTER TABLE employee ADD COLUMN grade VARCHAR; -- 실패bonus는 이미 추가됐는데 마이그레이션 테이블엔 이 파일이 “실패”로 기록된다. 재실행하면 bonus 중복 에러. 수동 개입 없이는 못 푼다.
문제 4 — CloudFront 캐시 전파 지연
프론트엔드 배포 마지막은 S3 sync → CloudFront 무효화다. 무효화는 글로벌 엣지로 전파되는 데 최대 15분 걸린다. 그동안 일부 사용자는 새 번들을, 일부는 캐시된 구 번들을 받는다. 새 JS가 새 API 경로를, 구 JS가 구 경로를 호출하면 같은 시간대에 다르게 동작하는 앱이 공존한다.
문제 5 — 백엔드와 배치의 무조율 배포
apps/backend/** 변경 시 백엔드와 배치 워크플로우가 동시에 뜬다. 둘은 같은 소스를 다른 Dockerfile로 빌드해 별도 ECS 서비스로 독립 배포된다. 배치가 먼저 끝나면 새 배치 코드가 구 DB 스키마(백엔드 마이그레이션 전)를 바라본다.
문제 6 — 롤백 메커니즘 없음
배포가 실패하면 워크플로우는 그냥 끝난다. 이전 Task Definition으로 자동 복구하는 로직이 없다. AWS 콘솔에서 손으로 되돌려야 한다. ECS가 이전 Task Definition을 보존하므로 수동 롤백은 가능하지만, 마이그레이션된 DB는 롤백되지 않는다.
문제 7 — 롤링 배포 중 구/신 컨테이너 공존
wait-for-service-stability: true로 안정화를 기다려도, ECS 기본 방식은 롤링 배포다. 안정화 전까지 일부 요청은 구 컨테이너, 일부는 신 컨테이너로 간다. Stateless API면 대개 무해하지만, 세션이나 인메모리 상태가 있으면 예상 못 한 동작이 생긴다.
위험도별 정리
| 문제 | 위험도 | 발생 조건 |
|---|---|---|
| FE 먼저 배포, BE 구버전 | 높음 | packages/** 변경 시 항상 |
| 마이그레이션 성공 + ECS 실패 | 높음 | 배포 실패 시 |
| 마이그레이션 부분 실패 | 높음 | 복잡한 마이그레이션 시 |
| CloudFront 전파 지연 | 중간 | 매 FE 배포 시 |
| 배치/백엔드 무조율 | 중간 | apps/backend/** 변경 시 |
| 롤백 없음 | 중간 | 배포 실패 시 |
| 롤링 배포 중 혼재 | 낮음 | 매 BE 배포 시 |
완전한 해결책은 복잡도가 높다. 현실적인 순서는 (1) packages/** 변경 시 BE 완료 후 FE를 트리거하는 의존성 추가, (2) 마이그레이션을 배포에서 분리, (3) 블루/그린 배포 도입이다.
다음 글부터 위험도 높은 것부터 하나씩 잡는다. 먼저 “FE가 먼저 배포되는” 레이스 컨디션이다. 워크플로우 3개를 하나로 합치고 배포 순서를 강제하는 것부터다.