PostgreSQL로 전환한 뒤 첫 운영 배포. "잘 돌아가겠지"라는 생각으로 점검을 시작했는데, 예상보다 많은 구멍이 나왔다.

1. 테스트 데이터가 운영에 있었다

운영 DB에 2026년 3월 날짜의 수입 레코드 6건이 있었다. 아직 2월인데. batch_id가 20260301, 20260315 — 누가 봐도 테스트 데이터다. 홍길동, 박영희 같은 이름이 섞여 있었다.

id=4450 | 2026-03-01 | 박영희     | 100,000 | batch=20260301
id=4451 | 2026-03-15 | 홍길동,김영희 | 500,000 | batch=20260315

운영 DB에 직접 테스트를 돌린 흔적이다. DELETE로 즉시 정리했다. 교훈: 운영 DB에서 테스트하지 말 것. 당연한 건데 급하면 잊는다.

2. 마이그레이션이 빠져 있었다

is_deleted 컬럼을 조회하니 "column does not exist" 에러. 소프트 삭제 마이그레이션(002_soft_delete.sql)이 운영에 적용되지 않았다.

원인은 단순했다. 로컬에서 개발만 하고 커밋을 안 했다. 커밋이 안 됐으니 배포도 안 됐고, 배포가 안 됐으니 마이그레이션도 안 돌았다.

다행히 배포된 코드에서 is_deleted를 아직 참조하지 않아서 당장 오류는 없었다. 하지만 다음 배포에서 터질 시한폭탄이었다.

조치: 18개 파일 일괄 커밋 → 배포 → 마이그레이션 자동 실행. deploy.sh가 마이그레이션 단계를 포함하고 있어서 배포만 하면 해결됐다.

3. 백업이 SQLite 파일이었다

Drive에 올라가 있는 DB 백업 파일들 — finance_20260208.db, finance_20260215.db — 전부 SQLite 파일이었다. PostgreSQL로 전환한 이상 이건 의미가 없다.

그리고 운영 서버(VM)에는 백업 디렉토리 자체가 없었다. 앱 컨테이너에 pg_dump도 설치되어 있지 않았다.

조치 3가지:

  • Dockerfile에 postgresql-client 추가 → pg_dump 사용 가능
  • docker-compose.prod.yml에 볼륨 마운트: ./backups → /app/scripts/db/backups
  • close_offering --backup-dbpg_dump SQL 덤프 방식으로 전환
# docker-compose.prod.yml
volumes:
  - ./backups:/app/scripts/db/backups

이제 마감 시 자동으로 pg_dump → .sql → Drive 업로드가 된다.

4. 복원에 안전장치가 없었다

기존 복원 API는 confirm=true 파라미터 하나뿐이었다. 실수로 클릭하면 DB가 통째로 교체된다.

3단계 안전장치를 추가했다:

단계 내용
미리보기 현재 DB vs 백업 파일의 테이블별 레코드 수 비교
비밀번호 관리자 비밀번호 재입력
쿨다운 5초 카운트다운, 취소 가능

백업 파일의 레코드 수는 pg_dumpCOPY 블록을 regex로 파싱해서 추출한다. 별도 DB 연결 없이 파일만 읽으면 된다.

pattern = rf"COPY public\.{table}\s[^\n]*FROM stdin;\n(.*?)\n\\."
match = re.search(pattern, content, re.DOTALL)

5. Docker 빌드와 로컬 빌드는 다르다

로컬에서 npx tsc --noEmit 통과해도 Docker 빌드에서 TS 에러가 난다. 이번에 2번 겪었다.

  • onEdit?: undefined — optional prop에 undefined 전달 (strict mode)
  • 미사용 변수 accentColornoUnusedLocals 차이

Docker의 tsc -b가 더 엄격하다. 로컬 통과를 맹신하면 안 된다.

배포 전 체크리스트

이번 경험으로 만든 운영 배포 전 확인 항목:

  • 커밋되지 않은 변경사항 확인 (git status)
  • 마이그레이션 파일이 커밋에 포함되었는가
  • 운영 DB에 테스트 데이터가 없는가
  • 백업이 현재 DB 엔진(PostgreSQL)과 호환되는가
  • 볼륨 마운트로 영속 데이터가 보호되는가
  • Docker 빌드가 로컬과 동일하게 통과하는가

오늘의 숫자

  • 커밋 5개, 배포 5회
  • 테스트 데이터 6건 삭제
  • 운영 DB: 414 income / 110 expense
  • 백업: finance_20260219.sql (86KB, pg_dump)