-
Alembic 사용법 (python data migrations)프로그래밍/Python 2024. 5. 5. 11:46반응형
Alembic 이란?
- Python에서 사용하는 Database Migration Tool
- 주로 Alembic + Sqlarchemy로 사용하여 통합 관리 하게 됩니다.
Alembic을 사용하는 이유?
- 보통 개발 할 때 여러 대의 DB를 생성하여 사용합니다.
- 아래 Flow와 같이 Dev와 Prod DB를 일치 시켜야 운영이 가능 할 때 사람이 일일히 수동으로 하는 것이 아니라 Alembic으로 통합하여 다른 사람이라도 똑같은 컨디션의 환경을 제공해주기 위한 툴입니다.
- spring 진영의 flyway와 비슷 합니다. 차이점이라면 flyway는 변경 정보를 각 테이블에 모두 기록하는 반면, alembic은 마지막에 마이그레이션 된 버전의 해시값만을 기록합니다.
- alembic DB migrations을 버전의 해시값으로 버전별 어떤 작업을 했는지 트래킹이 용이합니다.
- 보통 migration이 생기는 경우는 아래와 같은데 이 모두 10대의 DB 인스턴스가 있다면 10번을 반복해 줘야하지만 alembic을 사용하면 한번 수정과 배포가 진행 됩니다.
- 클라우드 인스턴스 이동하여 DB를 새로 생성해야 할 때
- 특정 Entity에 대한 변경 요청
- Table Schema 변경 되는 경우
Alembic Flow
Alembic 설치
pip install alembic
Alembic의 환경 생성 or 초기화
- alembic init으로 migration의 환경 생성 아래와 같이 migrations 폴더와 alembic.ini 파일도 생성이 됩니다.
# alembic init {migration의 환경명} alembic init migrations
폴더 설명
- versions - 마이그레이션 할 스크립트 코드가 들어감 처음에 비어있음 작성해야함
- env.py - DB 마이그레이션 시 실행 되는 서버 연결 및 마이그레이션 실행 코드
- script.py.mako - 마이그레이션 템플릿 파일
- alembic.ini - env.py파일에서 Configuration으로 사용되는 alembic 설정 파일
Alembic database설정
먼저 alembic을 실행하기 전에 마이그 할 DB를 설정해야하는데 alembic.ini 파일에 sqlalchemy.url에 DB주소를 설정해 주면 초기 셋팅은 어느정도 끝난 것
# the output encoding used when revision files # are written from script.py.mako # output_encoding = utf-8 sqlalchemy.url = postgresql+psycopg2://postgres:postgres@localhost:5432/lms
하지만, ini파일은 동적으로 sql url을 할당 할 수 없기에 class를 만들어 사요하든 python 코드로 사용하든 하면 더 좋습니다.
config = context.config if not config.get_main_option("sqlalchemy.url"): config.set_main_option( "sqlalchemy.url", "postgresql+psycopg2://{username}:{password}@{host}:{port}/{db_name}".format( username="postgres", password="postgres", host="localhost", port="5432", db_name="lms" ) )
alembic 마이그레이션 스크립트 생성
이제 db까지 준비 되었으니 마이그레이션을 진행해보자. 아래 명령어를 사용하면 revision 스크립트가 생기며, revision 버전도 생성이 된다.
alembic revision -m "{commit 메세지}" alembic revision -m "init"
"""init Revision ID: 2a1080a73181 Revises: Create Date: 2024-05-05 11:03:38.853884 """ from typing import Sequence, Union from alembic import op import sqlalchemy as sa # revision identifiers, used by Alembic. revision: str = '2a1080a73181' down_revision: Union[str, None] = None branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: pass def downgrade() -> None: pass
upgrade부분과 downgrade부분을 수정해주면 되는데 2a1080a73181 이 revision 해시 값을 이용하여 이 버전으로 upgrade시에는 upgrade 함수가 downgrade 시에는 downgrade 함수가 호출 되어 실행 된다. 이번에 저는 user와 book 테이블을 생성해 볼 예정이라 아래 처럼 작성해 봤다.
def upgrade() -> None: op.create_table( "user", sa.Column('id', sa.Integer), sa.Column('name', sa.String), sa.PrimaryKeyConstraint('id') ) op.create_table( 'book', sa.Column('id', sa.Integer), sa.Column('title', sa.String), sa.Column('author', sa.String), sa.Column('borrowed', sa.String), sa.PrimaryKeyConstraint('id') ) def downgrade() -> None: op.drop_table('user') op.drop_table('book')
Migrations실행
upgrade
upgrade head를 진행하면 db안에 잇는 현재 alembic_version의 해시값 보다 높은 것을 revision 해시값에 해당하는 스크립트를 실행한다.
alembic upgrade head INFO [alembic.runtime.migration] Context impl PostgresqlImpl. INFO [alembic.runtime.migration] Will assume transactional DDL. INFO [alembic.runtime.migration] Running upgrade -> 2a1080a73181, init
DB도 잘 적용된 것을 알 수 있다.
downgrade
위에 작성 한 것과 같이 downgrade를 하면 삭제 될 것이다.
alembic downgrade -1 INFO [alembic.runtime.migration] Context impl PostgresqlImpl. INFO [alembic.runtime.migration] Will assume transactional DDL. INFO [alembic.runtime.migration] Running downgrade 2a1080a73181 -> , init
삭제가 잘 되었다.
revision 코드 자동 생성
alembic은 sqlalchemy와 연동 되어 있다 보니 sqlalchemy에 있는 model을 기반으로 자동으로 revision migration 코드를 생성 해 줄 수 있다. 먼저 아래와 같이 declarative_base을 이용하여 Base를 선언한다.
from sqlalchemy.ext.declarative import declarative_base Base = declarative_base()
Base에 등록된 모델들의 생성이나 변경점을 찾아 자동으로 generate하기 때문에 test라는 모델을 하나 만들어 보자
from sqlalchemy import Column, Integer, String, Boolean class test(Base): __tablename__ = 'test' id = Column(Integer, primary_key=True) title = Column(String) test = Column(String)
Target_metadata를 바꿨기 때문에 위에 Base기준으로 재설정 된다. 그래서 User와 book은 삭제되며 test table만 남게 되는 revision script가 완성이 되었다.
alembic revision --autogenerate -m "create table test"
"""create table test Revision ID: 536d2ba9a373 Revises: 2a1080a73181 Create Date: 2024-05-05 11:39:10.684664 """ from typing import Sequence, Union from alembic import op import sqlalchemy as sa # revision identifiers, used by Alembic. revision: str = '536d2ba9a373' down_revision: Union[str, None] = '2a1080a73181' branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### op.create_table('test', sa.Column('id', sa.Integer(), nullable=False), sa.Column('title', sa.String(), nullable=True), sa.Column('test', sa.String(), nullable=True), sa.Column('test2', sa.String(), nullable=True), sa.PrimaryKeyConstraint('id') ) op.drop_table('book') op.drop_table('user') # ### end Alembic commands ### def downgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### op.create_table('user', sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False), sa.Column('name', sa.VARCHAR(), autoincrement=False, nullable=True), sa.PrimaryKeyConstraint('id', name='user_pkey') ) op.create_table('book', sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False), sa.Column('title', sa.VARCHAR(), autoincrement=False, nullable=True), sa.Column('author', sa.VARCHAR(), autoincrement=False, nullable=True), sa.Column('borrowed', sa.VARCHAR(), autoincrement=False, nullable=True), sa.PrimaryKeyConstraint('id', name='book_pkey') ) op.drop_table('test') # ### end Alembic commands ###
여기서 add column을 한번 해보고 generate를 다시 해보면 add Column Script가 생성이 된걸 알 수 있다.
class test(Base): __tablename__ = 'test' id = Column(Integer, primary_key=True) title = Column(String) test = Column(String) test2 = Column(String) # 추가
"""add column test2 Revision ID: e3e5f02ba243 Revises: 536d2ba9a373 Create Date: 2024-05-05 11:42:25.873999 """ from typing import Sequence, Union from alembic import op import sqlalchemy as sa # revision identifiers, used by Alembic. revision: str = 'e3e5f02ba243' down_revision: Union[str, None] = '536d2ba9a373' branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### op.add_column('test', sa.Column('test2', sa.String(), nullable=True)) # ### end Alembic commands ### def downgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### op.drop_column('test', 'test2') # ### end Alembic commands ###
Alembic history
alembic으로 마이그레이션은 마쳤지만, 현재 상태를 확인해 볼 필요성이 있다. 아래와 같이 명령어를 실행하면 현재 어떤 것을 진행 했는지 나오게 된다.
alembic history --verbose Rev: e3e5f02ba243 (head) Parent: 536d2ba9a373 Path: C:\\dev\\libraryManagementSystem\\migrations\\versions\\2024_05_05_1142-e3e5f02ba243_add_column_test2.py add column test2 Revision ID: e3e5f02ba243 Revises: 536d2ba9a373 Create Date: 2024-05-05 11:42:25.873999 Rev: 536d2ba9a373 Parent: 2a1080a73181 Path: C:\\dev\\libraryManagementSystem\\migrations\\versions\\2024_05_05_1139-536d2ba9a373_create_table_test.py create table test Revision ID: 536d2ba9a373 Revises: 2a1080a73181 Create Date: 2024-05-05 11:39:10.684664 Rev: 2a1080a73181 Parent: <base> Path: C:\\dev\\libraryManagementSystem\\migrations\\versions\\2024_05_05_1103-2a1080a73181_init.py init Revision ID: 2a1080a73181 Revises: Create Date: 2024-05-05 11:03:38.853884
참고 문헌
반응형'프로그래밍 > Python' 카테고리의 다른 글
python pyenv로 가상화 환경 만들기 (0) 2024.05.04 python 패키지 관리를 위한 poetry 패키지 매니저 활용법 (0) 2024.05.04 python property 사용하기 (0) 2022.03.12 [python] return에서 or/and 연산자 사용법 (0) 2021.06.17