Alembic과 데이터베이스 마이그레이션

"직접 SQL 명령어 사용하기 보다 더 나은 방법"

by 제임스

"아, 컬럼 하나 추가해야 하는데... 그냥 테이블 DROP하고 다시 만들까?"


11년차 QA 엔지니어였던 저는 개발자들이 개발 환경에서 이런 말을 하는 걸 수없이 들어왔습니다. 그리고 저 역시 JamesCompany 프로젝트를 시작하면서 똑같은 생각을 했습니다. 로컬에서야 뭐... DROP TABLE 한 번이면 깔끔하게 해결되니까요.

하지만 서비스가 조금씩 형태를 갖춰가고, 실제 데이터가 쌓이기 시작하면서 현실을 직면했습니다. 한 번 운영에 올라간 데이터베이스는 절대 함부로 건드릴 수 없다는 것을요.


SQLAlchemy + Alembic 도입기

처음엔 Raw SQL이었습니다

프로젝트 초기, 저는 FastAPI와 함께 Raw SQL을 직접 작성했습니다. QA 엔지니어로서 SQL은 익숙했고, 테스트 데이터를 만들 때도 자주 사용했으니까요.



간단하고 직관적이었습니다. 하지만...


첫 번째 위기: "아, 컬럼 하나 빼먹었네요"

Coffee Chat 기능을 개발하던 중, duration 컬럼을 빼먹었다는 걸 깨달았습니다. 로컬에서야 간단했죠..


하지만 개발 환경(Vercel)에도 적용하려니 문제가 시작됐습니다.

이미 적용한 건지 아닌지 어떻게 알죠?

다른 개발자가 다른 컬럼을 추가했다면?

운영 환경엔 언제, 어떤 순서로 적용해야 할까요?


SQLAlchemy + Alembic의 구원

결국 ORM과 마이그레이션 도구를 도입하기로 했습니다. SQLAlchemy는 Python의 대표적인 ORM이고, Alembic은 SQLAlchemy를 위한 데이터베이스 마이그레이션 도구입니다.


이제 Alembic으로 마이그레이션을 생성할 수 있었습니다.


PostgreSQL 환경별 DB 분리

환경별 데이터베이스 전략

처음엔 하나의 데이터베이스에 모든 환경을 넣으려고 했습니다. 스키마를 분리하면 되지 않을까요? 하지만 이것도 QA 관점에서 보니 문제가 많았습니다.

격리 실패: 개발 환경의 버그가 운영 데이터에 영향을 줄 수 있음.

권한 관리 복잡: 환경별로 다른 권한 설정이 어려움.

백업/복구 어려움: 특정 환경만 롤백하기 힘듦.


Railway를 선택한 이유

처음엔 AWS RDS, Supabase, PlanetScale 등 여러 옵션을 검토했습니다. 하지만 1인 개발자로서 Railway가 가장 매력적이었습니다.

간편한 환경 분리: 프로젝트별로 독립된 PostgreSQL 인스턴스 생성

자동 백업: 일일 백업과 Point-in-time recovery 지원

비용 효율성: 개발 환경은 최소 스펙으로, 운영은 필요에 따라 스케일업

배포 통합: GitHub 연동으로 환경별 자동 배포

결국 Railway에서 환경별로 완전히 분리된 PostgreSQL 데이터베이스를 구성했습니다.


Railway 환경 구성 과정

1. 로컬 환경 (Docker Compose)

개발의 편의성을 위해 로컬에서는 Docker Compose를 사용했습니다.


2. Railway 개발 환경 설정

Railway CLI를 통해 개발 환경을 구성했습니다.


Railway의 장점 중 하나는 환경 변수를 쉽게 관리할 수 있다는 점이었습니다.


3. 운영 환경 분리 전략

운영 환경은 별도의 Railway 프로젝트로 완전히 분리했습니다.


데이터베이스 마이그레이션 파이프라인

환경별로 다른 마이그레이션 전략을 수립했습니다.


Railway 백업 및 복구 전략

Railway는 자동 백업을 제공하지만, 추가적인 백업 전략도 구현했습니다.


환경별 성능 최적화

Railway의 PostgreSQL 인스턴스를 환경별로 다르게 최적화했습니다.


모니터링과 알림

Railway의 로그와 메트릭을 활용한 모니터링 시스템도 구축했습니다.


Railway와 PostgreSQL의 조합은 1인 개발자에게 정말 좋은 선택이었습니다. 특히 환경별로 완전히 격리된 데이터베이스를 쉽게 관리할 수 있다는 점이 큰 장점이었죠.

QA 엔지니어로서 "환경 분리"의 중요성은 알고 있었지만, 직접 구현해보니 그 복잡성과 필요성을 더욱 절실히 느낄 수 있었습니다.



마이그레이션 히스토리 관리의 중요성

데이터베이스 마이그레이션을 시작하면서 가장 크게 깨달은 건, 이게 단순히 "스키마 변경"이 아니라 "시간 여행"과 같다는 점이었습니다. 각 마이그레이션은 데이터베이스의 특정 시점을 나타내고, 우리는 이 시간선을 앞뒤로 이동할 수 있어야 합니다.


실수 1: 되돌릴 수 없는 마이그레이션

제가 처음 만든 마이그레이션 중 하나는 이런 모습이었습니다.


간단해 보이잖아요? 하지만 이 마이그레이션을 개발 환경에 적용한 후 문제가 터졌습니다.


문제 1: 실행 중인 애플리케이션과의 충돌 마이그레이션은 성공했지만, 이미 실행 중인 FastAPI 서버는 여전히 coffee_chats 테이블을 찾고 있었습니다. SQLAlchemy 모델을 업데이트하고 서버를 재시작하는 사이에 약 30초간 서비스 장애가 발생했죠.


문제 2: 외래 키 제약의 복잡성 더 큰 문제는 다른 테이블에서 이 테이블을 참조하고 있었다는 점입니다.


교훈: Blue-Green 마이그레이션 전략 이 경험으로부터 배운 건, 테이블 이름 변경 같은 breaking change는 단계적으로 진행해야 한다는 것입니다.

Blue-Green 마이그레이션 전략이란?
Blue-Green 마이그레이션 전략은 기존 스키마(Blue)를 유지하면서 새로운 스키마(Green)를 병렬로 구성하고, 애플리케이션이 둘 다 지원할 수 있게 만든 후 점진적으로 전환하는 무중단 배포 전략입니다. 데이터베이스에서는 주로 뷰(View)나 트리거를 활용해 하위 호환성을 유지하면서 스키마를 안전하게 변경합니다.


실수 2: 데이터가 있는 컬럼 삭제

더 큰 실수는 운영 환경에서 일어날 뻔했습니다. 사용하지 않는다고 생각한 컬럼을 삭제하는 마이그레이션이었죠.


다행히 스테이징 환경에서 먼저 테스트했고, 몇몇 사용자가 실제로 전화번호를 입력했다는 걸 발견했습니다. 만약 이게 운영에 적용됐다면... 생각만 해도 아찔합니다.


개선된 접근법: Soft Delete 패턴


이렇게 하면 실제로 컬럼이 사용되는지 모니터링할 수 있고, 충분한 시간이 지난 후 안전하게 제거할 수 있습니다.


베스트 프랙티스 정립

이런 실수들을 겪으면서, 팀에서 사용할 마이그레이션 가이드라인을 만들었습니다.

1. 마이그레이션 템플릿 표준화


2. 마이그레이션 체크리스트

모든 마이그레이션 PR에는 다음 체크리스트를 포함시켰습니다.


3. 위험도별 마이그레이션 분류


QA 관점에서 DB 스키마 변경 테스트

11년간 QA 엔지니어로 일하면서 수많은 데이터베이스 관련 버그를 봤습니다. 하지만 직접 마이그레이션을 작성하고 테스트하면서, 왜 그런 버그들이 발생했는지 더 깊이 이해하게 됐습니다.


마이그레이션 테스트 전략

처음엔 "마이그레이션도 테스트가 필요한가?"라고 생각했습니다. 하지만 첫 번째 롤백 실패를 경험한 후, 마이그레이션 테스트 스위트를 만들었습니다.


스키마 변경 영향도 분석

마이그레이션을 작성할 때마다 영향도 분석 문서를 자동으로 생성하는 도구도 만들었습니다.


영향도 분석 보고서 예시


깨달은 것들

1. 마이그레이션은 버전 관리입니다

Git의 커밋처럼, 각 마이그레이션은 데이터베이스의 특정 상태를 나타냅니다. 그리고 Git처럼 이력을 추적하고, 특정 시점으로 되돌릴 수 있어야 합니다.

처음엔 단순히 "ALTER TABLE"만 생각했지만, 이제는 각 마이그레이션을 하나의 "데이터베이스 커밋"으로 봅니다. 심지어 commit message처럼 명확한 설명과 함께요.


2. 항상 롤백을 고려해야 합니다

"이 마이그레이션을 되돌려야 한다면?"이라는 질문을 항상 던져야 합니다. 특히 데이터가 있는 운영 환경에서는 더욱 그렇죠.


3. 환경별 격리는 필수입니다

개발 환경의 실험이 운영 데이터를 오염시키면 안 됩니다. Railway를 통해 환경별로 완전히 독립된 PostgreSQL 인스턴스를 운영하면서, 진정한 환경 격리의 가치를 깨달았습니다.

각 환경은 자신만의 마이그레이션 이력을 가지고, 독립적으로 발전합니다. 마치 Git의 브랜치처럼요.


4. QA의 새로운 영역

QA 엔지니어로서 "기능 테스트"만 생각했던 제가, 이제는 "인프라 테스트"의 중요성도 깊이 이해하게 됐습니다.

마이그레이션도 버그를 가질 수 있습니다

성능 저하도 버그입니다

데이터 손실은 가장 치명적인 버그입니다

이제 저는 새로운 기능을 테스트할 때, 데이터베이스 스키마 변경사항도 함께 검토합니다. 그리고 이런 질문들을 던집니다.

이 마이그레이션이 실패하면 어떻게 되나요?

대용량 데이터에서도 잘 동작하나요?

롤백이 필요하다면 데이터는 안전한가요?


마치며

"테이블 DROP하고 다시 만들면 되지"라고 생각했던 시절이 이제는 아득합니다. Alembic과 PostgreSQL, 그리고 Railway를 통해 체계적인 데이터베이스 관리를 경험하면서, 단순한 "스키마 변경"이 아닌 "데이터베이스 진화"라는 개념을 이해하게 됐습니다.

특히 QA 엔지니어로서, 이제는 개발자가 "스키마 좀 바꿔야 하는데..."라고 할 때 더 깊이 있는 대화를 나눌 수 있게 됐습니다.

"마이그레이션 스크립트는 준비됐나요?"

"영향도 분석은 해보셨나요?"

"성능 테스트는 어떻게 진행할까요?"

"환경별 배포 순서는 어떻게 되나요?"


데이터베이스 마이그레이션은 단순한 기술적 작업이 아닙니다. 그것은 서비스의 심장인 데이터를 안전하게 진화시키는 예술입니다. 그리고 QA 엔지니어로서, 이제 저는 그 예술의 감상자가 아닌 참여자가 되었습니다.

다음 편에서는 Monorepo에서 Multi-repo로 전환하면서 겪은 이야기를 다뤄보겠습니다. "모든 걸 한 곳에 넣으면 편할 줄 알았는데..."

keyword
이전 03화패키지 관리자의 혼돈