ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 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 초기화

    1. 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
    

     

     

     

     

    참고 문헌

    https://alembic.sqlalchemy.org/en/latest/

    https://blog.neonkid.xyz/257

    반응형

    댓글

Designed by Tistory.