작은 사이드 프로젝트 중간에 투입돼서 코드 개선을 하게 되었는데,
FastAPI + SQLAlchemy (postgres) + Alembic으로 구성된 기존 방식에서 Alembic을 atlas로 변경했다.
Python으로 구성하는 API Server + ORM + DB migration tool 조합의 정석으로 많이 쓰이지만,
글 작성 기준 alembic 최신버전인 v1.13.1 프로덕트를 써보니 장점보다는 단점이 더 크게 느껴졌다.
당연하게도, 내가 느낀 단점이나 불편함이 alembic 프로덕트 자체의 한계는 아닐 수도 있다. 문서에서 내가 놓친 부분이 있을 수도 있다.
이런 고민을 하게 된 상황은 아래와 같은 조건에서였다.
- Python으로 production level의 웹 서비스를 목표로 상정하고 개발해본 경험은 없는 팀 구성원들과 협업해야 함.
- alembic의 모든 기능을 샅샅이 써보고, 일일이 비교한 뒤에 결정한 것이 아님. 그럴 시간이 없었다.
- 규모가 작은 사이드 프로젝트 팀 특성상, 회사의 DBA 같은 DB management 전문 인력이 없다
Alembic이란?
https://github.com/sqlalchemy/alembic
https://alembic.sqlalchemy.org/en/latest/
sqlalchemy 개발한 쪽에서 만드는 database migration tool.
SQLAlchemy 진영에서 만들었기 때문에 Python ORM으로 SQLAlchmey를 쓰면 자연스럽게 접하게 된다.
yourproject/
alembic/
env.py
README
script.py.mako
versions/
3512b954651e_add_account.py
2b1ae634e5cd_add_order_id.py
3adcc9a56557_rename_username_field.py
- Python ORM으로 정의한 Object를 감지해서, DB schema로 변경해주는 python script를 생성해준다.
offline mode를 사용하면 python script 말고 sql를 만들어준다. - revision 단위로 DB 변경사항의 버전관리를 할 수 있다.
하지만, 소규모 팀에서 DBA 없이 DB management 형상관리를 수행하기에는
alembic이 지원하지 않는 기능의 단점이 너무 크게 다가왔다.
단점 1. Lint 기능 없음
alembic을 걷어내야겠다는 생각을 하게 된 근본적인 이유.
제대로 된 DBA 없이 API 서버의 DB management를 감당해야 하는 우리 팀 특성상
- 개발자가 Pull Request 했을 때,
프로젝트에 정의된 ORM과 DB schema 형상이 일치하는지를
CI / CD 프로세스에서 자동으로 확인해야 한다. - 변경이력에 문제가 없다면 Pass, conflict가 발생할 경우 Fail을 CI / CD 프로세스에서 리턴할 수 있어야 한다.
그런데, alembic 명령어에는 lint 비슷한 기능을 수행할 방법이 없다. dry run 옵션조차 없다.
- upgrade 명령어에 --sql 옵션을 쓰면 sql 파일을 만들어주는데,
이 sql 파일을 DB에 직접 dry run 요청하는 스크립트를 만드는 식으로 구현해야 한다. - 개발자가 DB에 직접 sql문으로 요청을 보내는 작업을 하지 않으려고 ORM을 쓰는 건데
lint 체크하려고 dry run을 DB 서버에 직접 요청해야만 하는 구조는 불합리하게 느껴졌다.
문서: https://alembic.sqlalchemy.org/en/latest/api/commands.html
단점 2. ORM 변경사항이 없으면 빈 python script를 생성함
lint가 안 되면, alembic revision --autogenerate
명령어를 실행했을 때
- ORM과 DB의 형상이 일치했을 때엔 No-Op으로 동작해 준다면
- migration 파일 디렉토리에 아무런 변경사항이 없는지 체크하는 식으로 우회할 수도 있을 것이다.
그런데 alembic은 revision autogenerate 명령어를 실행하면, 아무런 동작도 하지 않는 python script를 생성한다.
예시: https://alembic.sqlalchemy.org/en/latest/tutorial.html#create-a-migration-script
"""create account table
Revision ID: 1975ea83b712
Revises:
Create Date: 2011-11-08 11:40:27.089406
"""
# revision identifiers, used by Alembic.
revision = '1975ea83b712'
down_revision = None
branch_labels = None
from alembic import op
import sqlalchemy as sa
def upgrade():
pass
def downgrade():
pass
매번 alembic 명령어를 실행할 때마다 아무 의미없는 python script가 만들어지면
- CI / CD로 실행한 결과가 누적되었을 때
- 어떤 Python script가 유의미한 ORM 변경사항이 포함된 코드인지 어떻게 알 수 있나?
alembic은 merge라는 옵션을 써서 revision 이력을 하나로 합쳐주는 명령어가 있다.
- 하지만, 무의미한 python script를 생성하는 것 자체가 version management 관점에서 불필요한 기능이다.
- '이게 불편하면 너가 불필요한 py 파일을 합쳐서 써라' 라는 식의 접근법처럼 느껴지기도 했다.
단점 3. 본인들이 문서에 적어놓은 alembic autogenerate 한계점
autogenerate 옵션에서
- alembic이 변경사항을 감지할 수 있는 것
- Optionally Detect할 수 있는 것
- Cannot Detect하는 것
무슨 조건들이 많이 걸려 있었다.
DB 전담으로 관리해줄 팀원이 없어서 migration tool에 전적으로 의존해야 하는 팀 사정상,
"되는 게 있고, 안되는 게 있고, 특정 상황에서만 되는 게 있는" tool을 믿고 써야 할 이유가 없다고 느꼈다.
대안으로 선택한 tool : atlas
https://github.com/ariga/atlas
atlas: go로 작성된 Declarative, Language-agnostic DB migration tool
회사에서 GORM의 migration 기능을 대체하기 위한 프로덕트를 리서치할 때 찾아뒀던 건데, sqlAlchemy가 최근에 지원되기 시작했다.
https://atlasgo.io/guides/orms/sqlalchemy
https://github.com/ariga/atlas/releases/tag/v0.18.0
(2024. 01.12일 v0.18 버전에서부터 공식 지원)
lint 기능 지원
개발자가 ORM을 수정한 뒤, migration을 위한 sql 파일을 생성하지 않고 pull request를 올렸을 때,
CI 에서 lint 명령어로 Pass / Fail 여부를 확인할 수 있다.
성공 예시
실패 예시
CI / CD 프로세스에 DB migration 로직 추가해서 자동화 가능
코드리뷰를 마치고 dev 브랜치에 머지되면, CD 과정에서 자동으로 Dev DB에 migration을 수행하도록 Pipeline을 구축할 수 있다.
그럼에도 약간 아쉬운 점: major 버전 release가 없다는 불안감, pip 패키지의 기능 부족, cli 의존성이 높음.
atlas 자체는 go로 빌드된 cli tool이고, cli 사용을 상정한 방식으로 구현되어 있다.
- sqlalchemy를 지원하기 위한 pip 패키지를 설치해도, (v0.18 버전 기준) python 코드 내에서 migration을 실행하는 방법이 없다.
- 공식문서를 봐도, atlas pip 패키지는 python ORM을 import하기 위한 최소한의 인터페이스만 제공한다.
atlas 자체의 기능은 결국 cli binary를 설치해서 사용해야 한다.
공식문서의 사용법 (https://atlasgo.io/guides/orms/sqlalchemy) 을 보면 단적으로 알 수 있는데
# filename: load_models.py
# import one of the models
from models import User
from atlas_provider_sqlalchemy.ddl import print_ddl
print_ddl("mysql", [User])
load_models.py 라는 파일을 프로젝트 내에 만들어두고
data "external_schema" "sqlalchemy" {
program = [
"python3",
"load_models.py"
]
}
env "sqlalchemy" {
src = data.external_schema.sqlalchemy.url
dev = "docker://mysql/8/dev"
migration {
dir = "file://migrations"
}
format {
migrate {
diff = "{{ sql . \" \" }}"
}
}
}
cli에서 사용하는 atlas.hcl 파일을 프로젝트 디렉토리에 생성해줘야 한다.
atlas migrate diff --env sqlalchemy
를 터미널에서 실행하면
$ tree
migrations
|-- 20230918143104.sql
`-- atlas.sum
0 directories, 2 files
위와 같이 migrations 디렉토리 하위에 atlas가 관리하는 sql 파일 / 무결성 검사를 위한 sum 파일이 생성된다.
따라서 이 기능을 CI / CD에 적용하려면, CI / CD가 돌아가는 서버 (github action 같은) 에 atlas binary를 반드시 설치해줘야 한다.
결론
데이터베이스를 붙여야 하는 Python 웹 서비스를 검색하면,
보통은 기술 스택으로 FastAPI + SQLAlchemy + Alembic을 많이 소개한다.
하지만 내가 속한 사이드 프로젝트 팀에 필요한 것들이 무엇인지 파악한 뒤
- 우리에게 필요한 기능을 제공하지 않는 Alembic 대신
- 우리 상황에 필요한 기능을 제공하는 atlas로 대체한다.
라는 방향으로 프로젝트 개선을 제안했고,
DB management 관련 기능을 CI / CD 프로세스에 붙여서 데이터베이스 관리 부담을 줄였다.
'프로그래밍 > 이것저것_개발일지' 카테고리의 다른 글
Python SQLAlchemy의 many to many relation에 soft delete 기능 적용하기 (0) | 2024.03.25 |
---|---|
2023 서울디지털재단 주최 생성AI 해커톤 - 상담부문 최우수상 후기 (0) | 2023.07.15 |
Paketo buildpack의 Stack Customization 테스트 기록 (0) | 2023.04.12 |
Streamlink로 유튜브 멤버십 스트리밍 영상 다운로드하기 (1) | 2021.09.27 |
화상 모의면접 연습 플랫폼 개발 프로젝트 (2) - KeyCloak 활용해서 서비스 DB에 OAuth 인증 붙이기 (0) | 2021.04.18 |