목차
- “이게 원래 이렇게 되는 거예요?” — 뼈 때리는 질문
- 민낯 — 화끈거리는 자존심
- 진단 — 스테이징을 장악한 3인의 수퍼빌런
- 실행 — 환경 정복 과정
- 결과 — 다시 되찾은 확신
- 회고 — 당연한 것을 당연하게 만드는 일
1. “이게 원래 이렇게 되는 거예요?” — 뼈 때리는 질문
최근 업무 조정을 거치며 기존 운영팀 인원에게 결제 모듈의 추가 QA를 요청했습니다. 그런데 얼마 지나지 않아 날카로운 피드백이 돌아왔습니다.
“팀장님, 된다고 하셨는데 왜 안 돼요? 스테이징에서 확인했을 땐 시나리오 외의 문제가 계속 터지는데, 이게 원래 이렇게 되는 거예요?”
말문이 막혔습니다. 사실 저 역시 고쳐야 한다는 걸 진작 알고 있었습니다. 하지만 ‘급한 개발 일정’이라는 핑계 뒤에 숨어 차일피일 미루고 있었을 뿐입니다. 그 비겁한 핑계가 운영팀원의 정당한 불만 섞인 질문 하나에 무너져 내린 순간이었습니다.
당시 우리 팀의 스테이징은 아무도 믿지 않는 환경이었습니다. 데이터가 안 쌓이면 로그를 뒤지며 ‘제발 한 번에 돼라’고 기도하는 배포가 일상이었죠. “스테이징은 원래 그래, 그냥 프로덕션에서 확인하자”는 말은 신뢰를 포기한 우리들의 비참한 합의였습니다.
2. 민낯 — 화끈거리는 자존심
결정적인 사건은 앰버서더 정산 모듈 배포 때 터졌습니다. if (environment === 'sandbox') 필터를 제거하는 간단한 커밋을 스테이징에 배포하면서도, 확신이 없으니 “이제 될 수도 있을 것 같아요”라는 모호한 말을 남겼죠.
결과는 역시나 실패였습니다. 필터를 뺐음에도 로직은 꿈쩍하지 않았고 데이터는 쌓이지 않았습니다. 다음 날 들려온 “오늘 안에 못 끝내겠죠?”라는 말은 백엔드 개발팀장으로서의 자존심에 깊은 스크래치를 남겼습니다. 그 화끈거림이 저를 움직이게 했습니다.
3. 진단 — 스테이징을 장악한 3인의 수퍼빌런
원인을 파헤쳐 보니, 우리 환경은 단순한 버그가 아니라 세 명의 ‘수퍼빌런’에게 점령당한 상태였습니다.
- 빌런 1. 닥터 싱크-리스 (Dr. Sync-less): 프로덕션과 데이터 구조가 완전히 다른 ‘멀티버스’ DB입니다. staging에서는 결제 시 DB 업데이트를 건너뛰는 분기 코드가 존재했고, 정산 대상 데이터 자체가 존재하지 않는 상황이 비일비재했습니다.
- 빌런 2. 메뉴얼 저거너트 (Manual Juggernaut): 마이그레이션 도구를 멈춰 세우고, 모든 DB 변경을 수동 SQL로 처리하게 만드는 굴레입니다. 스키마 변경마다 수동 SQL을 실행하다 보니, 실수로 한쪽만 적용되거나 순서가 뒤섞이며 환경 간의 격차를 벌렸습니다.
- 빌런 3. 미스터 글래스 (Mr. Glass): 누군가 건드리면 전체 팀의 작업이 깨져버리는 연약한 환경입니다. 로컬에서 외부 API 연동을 확인할 방법이 없어 무조건 staging에 배포해야만 했고, 이는 곧 “마음껏 망가뜨려 볼 자유”의 상실로 이어졌습니다.
그리고 배후에는 ‘사일런트 킬러(Silent Killer)’가 있었습니다. 클라이언트에서는 201 응답을 받았는데 콘솔에는 에러가 찍히고, 서버에는 아무런 로그도 남지 않는 침묵의 상태. 로그가 없으니 디버깅은 칠흑 같은 어둠 속에서 바늘을 찾는 격이었습니다.
4. 실행 — 환경 정복 과정
“지금 안 고치면 앞으로 만드는 모든 기능이 시한폭탄이 된다”는 판단하에 7단계의 대수술을 시작했습니다. 문제를 5개의 구조적 개선사항으로 분류하고, 각각의 의존관계를 정리했습니다. 로컬 환경 정비가 먼저 되어야 워크플로우 개선이 가능하고, 환경별 분기 코드를 정리해야 테스트가 의미를 갖습니다.
(1) DB 멀티버스 통합
먼저 staging DB를 production 구조와 동기화했습니다. 앰버서더 정산에 필요한 seed 데이터를 정비하고, production에만 존재하던 데이터 구조를 staging에도 반영해 ‘닥터 싱크-리스’를 퇴치했습니다.
(2) 마이그레이션 자동화
수동 SQL 관리를 폐기하고 Prisma migration이 CI/CD 파이프라인에서 자동으로 실행되도록 워크플로우를 확립했습니다. 이제 staging이든 production이든 배포 시 마이그레이션이 자동 적용됩니다. 레거시 데이터와의 호환성 원칙도 문서화해서, 마이그레이션 작성 시 기존 데이터를 깨뜨리지 않도록 가이드라인을 세웠습니다. ‘메뉴얼 저거너트’를 멈춰 세운 핵심 무기입니다.
# .github/workflows/deploy-server-staging.yml
- name: 데이터베이스 마이그레이션
run: pnpm --filter @manyfast/server exec prisma migrate deploy
env:
DATABASE_URL: $
DIRECT_URL: $
# 로컬에서 마이그레이션 생성 → push하면 CI/CD가 자동 적용
npx prisma migrate dev --name add_settlement_column
# staging/production 배포 시 GitHub Actions가 prisma migrate deploy 실행
(3) 나만의 안전한 실험실 (Docker)
Docker Compose로 로컬 전용 Supabase + Redis 환경을 구성했습니다. npx supabase start로 로컬 DB를 띄워, staging을 건드리지 않고도 모든 동작을 확인할 수 있는 환경을 만들었습니다. ‘미스터 글래스’로부터 자유를 찾은 순간입니다.
# docker-compose.dev.yml — 로컬 Redis
services:
redis:
image: redis:7-alpine
ports:
- "6379:6379"
환경 전환도 스크립트 하나로 자동화했습니다.
./scripts/setup-env.sh dev # 개발 환경
./scripts/setup-env.sh staging # staging 환경
./scripts/setup-env.sh local # 로컬 Supabase 환경
(4) “될 수도 있어요” 코드 삭제
가장 부끄러웠던 isSandbox 분기 코드를 모두 걷어냈습니다. 코드베이스 전체를 grep으로 목록화하니 총 10개 파일에 산재해 있었습니다. 대신 isProduction(), isDevelopment() 헬퍼 유틸리티를 도입하고, secure-by-default 원칙을 적용했습니다.
// BEFORE: '이제 되겠지?'라는 희망 고문
if (environment === 'sandbox') {
return true;
}
// AFTER: 환경 헬퍼로 표준화, NODE_ENV 미설정 시 production 동작 (secure-by-default)
export function isProduction(): boolean {
return process.env.NODE_ENV !== 'development' && process.env.NODE_ENV !== 'test';
}
(5) 사일런트 킬러에게 입을 달아주기 (Logging)
nestjs-pino 기반 구조화 로깅을 도입했습니다. 스테이징은 debug, 프로덕션은 warn으로 로그 레벨을 분리해서, 스테이징에서는 모든 요청의 흐름을 추적할 수 있게 했습니다. 쿼리파라미터 같은 민감 정보는 로그에서 제외하도록 처리했습니다. 201 응답 뒤에 숨어있던 에러들이 드디어 비명을 지르며 로그에 나타나기 시작했습니다.
(6) E2E 테스트로 보험 들기
앰버서더 정산 플로우 전체를 커버하는 E2E 테스트 7건과 단위 테스트 23건을 추가했습니다. 코드 적용 → 수익 생성 → 정산 통계 → 환불 → 이탈 처리까지 전체 플로우를 테스트로 검증할 수 있게 됐습니다.
테스트를 작성하는 과정에서 숨어있던 버그도 발견했습니다. PRO_X1~PRO_X5 같은 플랜이 정산 대상에서 빠지고 있었는데, 정확한 문자열 매칭('PRO')만 지원하고 있었기 때문입니다. prefix 기반 매칭(startsWith)으로 수정했습니다. Prisma 6.x에서 NOT NULL 필드에 null 조건을 넣으면 validation error가 발생하는 호환성 문제도 함께 해결했습니다.
(7) 환경 아키텍처 재설계
BEFORE:
production ──── staging (불일치, 수동 관리)
AFTER:
production ──── staging (동기화) ──── local (자유 실험)
│ │ │
└── migration으로 스키마 일관성 보장 ──┘
5. 결과 — 다시 되찾은 확신
“스테이징에서 됐으니 배포해도 됩니다.” — 이제 이 말을 당당하게 할 수 있게 됐습니다.
총 34개 파일을 변경하고, 20개의 커밋을 하나의 통합 브랜치로 병합했습니다. Critical Review를 2라운드(25건) 거치고, 전체 CI 파이프라인을 통과시켰습니다.

| 항목 | 수술 전 (Before) | 수술 후 (After) |
|---|---|---|
| 배포 전 심정 | “제발 한 번에 돼라” (기도) | “테스트 통과했으니 문제없음” (확신) |
| DB 관리 | 수동 SQL (실수와 격차의 온상) | Prisma Migration (자동화) |
| 로컬 검증 | 불가능 — staging 배포 후 확인 | Docker 로컬 환경에서 전체 플로우 확인 |
| 디버깅 시간 | 로그가 없어 반나절 이상 소요 | 구조화 로깅으로 수 분 내 파악 |
| 환경 전환 | 수동 설정 파일 교체 | setup-env.sh 한 줄 |
| 팀의 사기 | QA의 불만과 개발자의 자존심 하락 | 안정적인 검증 사이클 확보 |
6. 회고 — 당연한 것을 당연하게 만드는 일
가장 중요한 일이 가장 눈에 안 띄는 일일 때가 있습니다. 인프라 정비는 새로운 기능을 만드는 것처럼 화려하지 않습니다. 하지만 하지 않으면 모든 것이 느려집니다.
이번에 가장 크게 느낀 것은 두 가지입니다.
첫째, 신입의 “당연한 질문”은 팀의 묵은 부채를 드러내는 가장 예리한 칼날이라는 점입니다. “원래 이런 거야”라는 말로 그 칼날을 무디게 만들어서는 안 됩니다.
둘째, 환경에 대한 신뢰도가 개발 속도의 근본을 결정한다는 점입니다. “오늘 안에 못 끝내겠죠?”라는 비수는 아팠지만, 덕분에 저는 굴러만 가는 코드가 아닌 ‘믿을 수 있는 시스템’을 만드는 법을 다시 배웠습니다. 팀이 안심하고 배포할 수 있게 만드는 것, 그것이 화려한 기능 개발보다 더 중요한 엔지니어의 숙명일지도 모릅니다.