DB 마이그레이션 이란, 데이터베이스의 구조를 코드로 기록하고 버전 단위로 변경해 나가는 작업이다. 단순히 테이블을 수정하는 것이 아니라, 그 변경 이력을 추적 가능한 파일로 남기는 것이 핵심이다.
개념 자체는 어렵지 않다. 그런데 특정 상황에서는 이 마이그레이션 이란 개념이 갑자기 훨씬 복잡한 문제로 바뀐다. 서버가 없는 환경에서 앱을 운용해야 할 때다. 이 글에서는 야전 군용 앱을 예시로, 마이그레이션이 왜 존재하는지, 그리고 왜 어떤 환경에서는 기술적으로 까다로운 문제가 되는지를 순서대로 짚는다.
마이그레이션 이란 무엇인가 — 개념부터 잡는다
앱을 개발하다 보면 DB 구조는 반드시 바뀐다. 처음에는 이름과 날짜만 저장했는데, 나중에 담당자 컬럼이 필요해진다. 이때 DB를 직접 수정하면 된다고 생각할 수 있다. 그런데 직접 수정하는 방식에는 치명적인 문제가 있다.
변경 이력이 남지 않는다.
누가 언제 무엇을 바꿨는지 알 수 없고, 이전 상태로 돌아가는 것도 불가능하다. 팀원이 여럿이라면 각자의 DB 상태가 달라져서 충돌이 생긴다. 마이그레이션은 이 문제를 해결하기 위해 존재한다. DB 구조 변경을 코드 파일로 기록해서, 누구나 같은 순서로 같은 상태를 재현할 수 있게 만드는 것이다.
Django에서 마이그레이션의 흐름은 단순하다.
models.py 수정
↓
python manage.py makemigrations ← 변경사항 감지 → 마이그레이션 파일 생성
↓
python manage.py migrate ← 파일을 읽어 실제 DB에 적용
Django는 django_migrations라는 테이블을 내부적으로 관리한다. 이 테이블에 어떤 마이그레이션 파일이 이미 적용됐는지 기록해 두기 때문에, migrate를 여러 번 실행해도 중복 적용 없이 새 변경사항만 반영된다. 이것이 마이그레이션의 핵심 메커니즘이다.
인터넷도 없고 연결도 안 된다 — 야전 앱 시나리오
일반적인 웹 서비스는 서버 한 대에 DB가 있고, 모든 사용자가 그 서버에 접속한다. 개발자가 migrate 한 번 실행하면 모든 사용자의 환경이 동시에 업데이트된다.
그런데 야전에서 운용되는 군용 앱은 이 구조가 성립하지 않는다.
야전 부대는 통신 인프라가 보장되지 않는다. 작전 지역에 따라 인터넷 연결 자체가 없을 수 있고, 위성 통신이 가능하더라도 실시간 서버 접속을 전제로 앱을 설계할 수 없다. 각 부대의 PC나 단말기에서 앱이 독립적으로 실행되어야 한다. DB도 각 기기에 하나씩 존재한다.
일반적인 웹 서비스 야전 군용 앱
───────────────────── ─────────────────────
서버 1대에 DB 1개 부대 A 단말기 — DB 독립
모든 사용자가 접속 vs 부대 B 단말기 — DB 독립
migrate 1번으로 완료 부대 C 단말기 — DB 독립
부대 D 단말기 — DB 독립
이 구조에서 앱 버전을 업데이트해야 한다면 어떻게 되는가. 서버가 없으니 개발자가 원격으로 migrate를 실행할 방법이 없다. 각 단말기의 DB를 개별적으로, 자동으로, 데이터 손실 없이 업데이트해야 한다. 이것이 야전 환경에서 마이그레이션이 특수한 문제가 되는 첫 번째 이유다.
보안 때문에 서버로 못 올린다 — 군사기밀과 네트워크 차단
야전 환경에서 로컬 구동을 선택하는 이유는 인프라 문제만이 아니다. 보안이 더 근본적인 이유인 경우가 많다.
군용 앱이 다루는 데이터는 작전 계획, 병력 현황, 장비 상태, 통신 주파수 같은 군사기밀을 포함할 수 있다. 이런 데이터를 외부 서버로 전송하는 것 자체가 보안 위협이 된다. 아무리 암호화가 잘 되어 있어도, 네트워크로 데이터가 이동하는 순간 탈취나 감청의 가능성이 생긴다.
그래서 군사 환경에서는 기술적으로 가능하더라도 외부 서버 연결을 차단하는 정책을 택한다. 데이터는 물리적으로 폐쇄된 단말기 안에만 존재해야 한다. 로컬 구동은 선택이 아니라 보안 요건이 된다.
이 상황을 다른 분야와 비교하면 구조가 명확해진다.
| 환경 | 로컬 구동 이유 | 공통점 |
|---|---|---|
| 야전 군용 앱 | 네트워크 단절 + 군사기밀 보안 | 데이터가 외부로 나가면 안 된다 |
| 의료 진료기록 앱 | 개인정보 보호 + 보안 거부감 | 데이터가 외부로 나가면 안 된다 |
| 산업 현장 설비 앱 | 공정 기밀 + 내부망 폐쇄 | 데이터가 외부로 나가면 안 된다 |
기술적으로 보안이 구현되어 있어도, 네트워크에 연결한다는 사실 자체를 거부하는 환경이 실제로 존재한다. 마이그레이션 문제는 바로 이런 환경에서 부각된다.
왜 기술적으로 어려운가 — 3가지 핵심 문제
서버가 없고, 연결도 안 되고, DB가 각 단말기에 분산되어 있다. 이 상황에서 앱 버전을 올리면 어떤 문제가 생기는지 구체적으로 살펴본다.
문제 1 — 업데이트 시점이 제각각이다
부대 A는 v1.0에서 v1.5로 업데이트했다. 부대 B는 v1.0을 계속 쓰다가 v2.0이 나왔을 때 바로 올렸다. 마이그레이션 파일은 버전마다 누적된다. v1.0 → v1.5에는 파일이 3개, v1.5 → v2.0에는 파일이 2개 추가됐다고 하자.
부대 A는 총 5개 파일을 순서대로 적용하면 된다. 부대 B는 v1.5를 건너뛰었기 때문에, v1.0 → v2.0 사이의 마이그레이션 파일 5개를 한꺼번에 처리해야 한다. 이 누적 적용이 정상적으로 동작하는지, 중간 버전을 건너뛰어도 데이터가 깨지지 않는지 검증이 필요하다.
문제 2 — 개발자가 직접 개입할 수 없다
서버 환경이라면 마이그레이션 오류가 났을 때 개발자가 접속해서 수동으로 처리할 수 있다. 야전 단말기는 그게 안 된다. 마이그레이션이 실패했을 때 현장에서 자체적으로 복구가 가능해야 한다. 그 복구 절차조차 앱 안에 설계되어 있어야 한다.
문제 3 — 실패하면 데이터가 손상된다
마이그레이션은 기존 데이터를 유지하면서 구조를 바꾸는 작업이다. 도중에 실패하면 일부는 바뀌고 일부는 안 바뀐 상태가 된다. 이 상태에서 앱을 계속 쓰면 데이터가 조금씩 오염된다. 군용 앱에서 작전 데이터가 손상된다면 단순한 버그가 아니다.
Django는 이 문제를 어떻게 푸는가
분산된 로컬 DB 마이그레이션 문제의 핵심은 자동화다. 개발자가 없어도, 앱이 실행될 때 스스로 현재 DB 상태를 확인하고 필요한 마이그레이션만 적용해야 한다.
Django는 이것을 코드로 구현할 수 있는 구조를 제공한다.
# 앱 시작 시 자동 실행되는 구조
from django.core.management import call_command
def on_app_start():
backup_db() # 1단계: 마이그레이션 전 DB 백업
call_command('migrate') # 2단계: 미적용 마이그레이션만 자동 탐지 후 적용
launch_app() # 3단계: 앱 정상 구동
이 구조가 동작하는 이유는 앞서 설명한 django_migrations 테이블 때문이다. Django는 앱이 실행될 때마다 이 테이블을 읽어서 어떤 마이그레이션 파일이 이미 적용됐는지 확인한다. 부대 B처럼 버전을 건너뛴 경우에도, 적용되지 않은 파일 목록을 자동으로 파악해서 순서대로 실행한다.
마이그레이션 이란 결국 이 자동 추적 메커니즘이 핵심이다. 누가 언제 어디서 앱을 실행하든, DB 상태를 최신으로 맞추는 것이 마이그레이션의 역할이다.
단, 실패 시 복구를 위한 백업은 반드시 선행되어야 한다. Django의 migrate는 트랜잭션을 지원하지만, SQLite 환경에서는 제약이 있다. 야전 앱처럼 개발자 개입이 불가능한 환경일수록 백업 → 마이그레이션 → 검증의 3단계 구조를 앱 레벨에서 직접 설계해야 한다.
핵심 요약
마이그레이션 이란 DB 구조 변경을 코드 파일로 기록하고, 순서대로 적용해 어떤 환경에서도 동일한 DB 상태를 재현하는 메커니즘이다.
서버 중심 환경에서는 migrate 한 번으로 끝난다. 그런데 야전 군용 앱처럼 네트워크 단절과 보안 요건이 동시에 요구되는 로컬 구동 환경에서는, 분산된 수십 개의 DB를 자동으로 안전하게 업데이트하는 구조를 직접 설계해야 한다. 마이그레이션이 단순한 명령어가 아니라 하나의 설계 문제가 되는 것은 바로 이 지점에서다.
[링크 제안]
마이그레이션이 실제로 어떻게 DB와 연결되는지 구조로 보고 싶다면 이 글이 이어진다.
로컬 구동 앱을 실제로 만들 계획이라면, 실행 환경 격리부터 설계해야 한다.
Django 마이그레이션의 세부 동작은 Django 공식 Migrations 문서에서 직접 확인할 수 있다.







