MMeshOps
데이터베이스 마이그레이션 실전: 다운타임 없는 안전한 DB 변경
5예상 10중급

자동 실행의 함정: 운영 서버 무조건 실행 금지

CI/CD나 앱 구동 시 마이그레이션을 자동으로 실행할 때 발생할 수 있는 DB 락(Lock)과 장애 상황을 피하는 방법을 익힙니다.

Part 1을 통해 마이그레이션 파일 시스템을 완벽하게 세팅하고 나면, 개발자는 한 가지 강력한 유혹에 빠지게 됩니다. *"앱을 배포하고 실행할 때, 앱이 스스로 데이터베이스 마이그레이션을 자동(Auto)으로 실행하게 만들면 편하지 않을까?"* 이 생각은 로컬 개발 환경에서는 천재적인 아이디어지만, 수많은 트래픽이 몰리는 운영 서버(Production)에서는 서비스 전체를 마비시키는 시한폭탄이 됩니다. 이번 시간에서는 마이그레이션 자동 실행이 왜 위험한지 그 숨겨진 함정을 파헤쳐 봅니다.

앱 구동 시 자동 마이그레이션의 유혹

현대의 백엔드 프레임워크나 바이브코딩 튜토리얼을 보면, 서버 애플리케이션이 시작될 때(Startup) 마이그레이션 코드를 호출하는 로직이 흔히 등장합니다.

로직은 매우 단순합니다.

  1. 앱이 실행된다.
  2. 데이터베이스에 연결한다.
  3. 내장된 마이그레이션 도구를 실행하여 최신 스키마로 업데이트한다.
  4. 웹 서버가 트래픽을 받기 시작한다.

개발자의 컴퓨터나 트래픽이 없는 테스트 서버에서는 이 방식이 정말 환상적입니다. 코드를 실행하기만 하면 귀찮은 명령어 없이 DB가 알아서 최신 상태로 맞춰지기 때문입니다. 하지만 이것을 그대로 운영 서버로 가져가면 재앙이 시작됩니다.

첫 번째 함정: 분산 환경에서의 동시 실행 충돌

운영 서버는 보통 한 대의 컴퓨터로 돌아가지 않습니다. 트래픽을 분산하기 위해 똑같은 앱 여러 개(인스턴스 또는 컨테이너)를 동시에 띄워 놓는 것이 일반적입니다.

마이그레이션 레이스 컨디션(Race Condition)

만약 새로운 버전을 배포하기 위해 앱 인스턴스 3대를 동시에 재시작했다고 가정해 봅시다. 이 3대의 앱은 켜지자마자 약속이라도 한 듯 동시에 데이터베이스에 달려가 마이그레이션을 실행하려고 할 것입니다.

데이터베이스 입장에서는 똑같은 테이블을 만들거나 컬럼을 추가하려는 명령이 3곳에서 동시에 쏟아지는 셈입니다.

  • 최악의 경우, 데이터베이스 데이터가 꼬이거나 데드락(Deadlock)에 빠져 통신이 끊어집니다.
  • 운이 좋아 마이그레이션 툴의 자체적인 잠금(Lock) 기능이 작동하더라도, 하나의 앱이 마이그레이션을 진행하는 동안 나머지 앱들은 데이터베이스 연결을 대기하다가 시간 초과(Timeout)로 기동에 실패하게 됩니다.

결국 정상적인 배포가 불가능해지고, 서버가 올라오지 않아 서비스 다운타임(장애)이 발생합니다.

실전 장애 시나리오: 배포 중 서버 다운

쿠버네티스(Kubernetes) 환경에서 앱을 5대 운영 중입니다. 스키마 변경이 포함된 새 코드를 배포했습니다. 5개의 파드(Pod)가 일제히 새로 켜지면서 마이그레이션을 시도합니다. 첫 번째 파드가 DB 락을 걸고 마이그레이션을 시작합니다. 이 마이그레이션은 데이터가 많아 1분이 걸립니다.

나머지 4개의 파드는 DB 락을 기다리다가 구동 제한 시간(30초)을 넘겨 강제 종료됩니다. 시스템은 비정상 상태로 판단하고 파드를 계속 재시작합니다. 마이그레이션이 끝날 때까지 서버는 무한 재시작 루프에 빠지고, 사용자는 1분 넘게 '502 Bad Gateway' 에러 창을 보게 됩니다.

두 번째 함정: 실패 시 롤백의 어려움

자동 실행의 또 다른 문제는 과정이 철저하게 숨겨져 있다는 점입니다.

마이그레이션은 생각보다 실패할 확률이 높습니다.

  • 예상치 못한 중복 데이터로 인한 제약조건 위반
  • 컬럼 데이터 타입 변경 시 발생하는 용량 문제
  • 쿼리 작성 실수 등

만약 마이그레이션을 사람이 직접 수동 명령어로 실행(CLI)했다면, 에러 로그를 즉각 확인하고 즉시 이전 상태로 롤백하거나 문제를 파악할 수 있습니다. 하지만 앱이 켜질 때 은밀하게 자동 실행되다가 실패하면 앱은 그냥 조용히 죽어버립니다. 개발자는 앱이 죽은 원인이 코드 버그 때문인지, 아니면 DB 마이그레이션이 꼬여서 그런 것인지 파악하기 위해 수많은 로그 더미를 뒤져야 하며, 이는 귀중한 골든타임을 낭비하게 만듭니다.

안전한 운영 서버 마이그레이션 원칙

권장

  • 운영 서버 배포 전, CI/CD 파이프라인의 독립된 단계(Step)에서 마이그레이션을 단 1회만 먼저 실행하세요.
  • 앱 인스턴스와 완전히 분리된 전용 마이그레이션 실행 작업(Job)을 사용하세요.
  • 개발/테스트 환경과 운영 환경의 마이그레이션 실행 전략을 확실하게 분리하세요.

주의

  • 앱 코드 내부에 앱 구동(Startup) 시 마이그레이션을 자동 실행하는 코드를 넣지 마세요.
  • 다수의 서버가 동시에 DB 형상 변경을 시도하도록 방치하지 마세요.

결론: 앱과 마이그레이션의 생명주기 분리하기

핵심은 하나입니다. "앱의 실행과 데이터베이스 스키마의 변경은 완전히 독립적인 이벤트로 취급해야 합니다."

현대적인 클라우드 운영(DevOps) 환경에서는 배포 스크립트를 분리합니다. 새로운 코드를 서버에 올리기 직전, 배포 관리자(GitHub Actions 등)가 마이그레이션 전용 명령어를 단 한 번만 독립적으로 실행하여 DB를 갱신합니다. 마이그레이션이 성공적으로 완료된 것을 100% 확신한 뒤에야, 비로소 새로운 앱 인스턴스들을 재시작하여 트래픽을 받게 하는 것입니다.

이것이 다운타임을 방지하고 운영 서버의 안정성을 지키는 가장 중요한 아키텍처 규칙입니다.

예시

-- 예시

개념 퀴즈

퀴즈 답을 맞춰야 학습 완료가 됩니다.

운영 서버(Production)에서 애플리케이션 시작(Startup) 시 마이그레이션을 자동으로 실행하게 설정하면 안 되는 가장 치명적인 이유는 무엇인가요?

3줄 요약

  1. 1개발 환경에서 편리한 '앱 구동 시 마이그레이션 자동 실행'은 운영 서버에서 절대 금지해야 하는 안티 패턴입니다.
  2. 2여러 서버가 동시에 마이그레이션을 시도하면 DB 락 경합이 발생해 서비스 전체가 뻗어버리는 장애가 발생합니다.
  3. 3안전한 운영을 위해서는 앱 배포 전에 별도의 CI/CD 파이프라인에서 마이그레이션을 단 1회만 단독으로 실행해야 합니다.