Alembic

GreenBean·2023년 1월 10일
0
post-thumbnail
post-custom-banner

Alembic

Alembic 공식 문서
SQLAlchemy + Alembic 조합을 이용한 마이그레이션 가이드
Python 의 마이그레이션 툴인 Alembic 사용법
데이터베이스 마이그레이션 도구 Alembic

Alembic 이란?

  • Alembic 은 Python 으로 SQLAlchemy 를 사용하고 있을 때 데이터베이스를 관리해주는 마이그레이션 도구

Tip! 추가 내용

  • 데이터베이스를 마이그레이션 하는 경우는 보통 아래의 3가지가 대표적
    • Instance 의 이전
      • 클라우드를 사용한다면 다른 Instance 혹은 다른 Region 으로의 이전
    • 특정 Entity 에 대한 변경 요청
      • 예시: "우리 서비스에는 이 기능을 넣기 위해 이 컬럼이 추가로 필요할 것 같아요."
    • 일부 영속된 데이터를 특정 데이터로 변경
      • 예시: "이제는 이 명칭을 이 명칭으로 바꿀거에요. 모든 사항에 적용되었으면 좋겠어요."

수동 처리

  • 마이그레이션을 수동으로 적용 가능
    • 다른 데이터베이스에 Instance 를 테스트용으로 하나 만들고 SQL Query 를 작성한 다음 실행

수동 처리 문제점

  • 이러한 마이그레이션이 쌓이다보면 처음에는 어떻게 적용했고 어떠한 점을 변형했는지에 대한 추적이 어려워짐
  • 같은 서비스를 여러 개의 환경으로 나누어서 사용하는 경우 어떤 환경에 마이그레이션이 이루어졌고 이루어지지 않았는지 확인이 어려움
    • 혹여라도 이미 마이그레이션이 진행된 Instance 에 다시 한 번 마이그레이션이 진행된다면 중복이 발생하고 이로 인한 데이터 변화가 일부 데이터 소실에 대한 원인이 되기도 함
      • 생산성을 떨어뜨리게 되거나 심각하게는 서비스의 장시간 장애로도 이어지게 됨

버전 관리

  • Alembic 은 이러한 마이그레이션 버전 관리를 제공
    • Spring 개발에서 Flyway 와 동일한 역할

  • 모든 마이그레이션은 Alembic 스크립트에 의해 이루어짐
    • 버전 관리를 위해 각 Instance 에는 migration 테이블이 존재
    • 이 테이블을 통해 어디까지 마이그레이션이 되었는지를 확인할 수 있으며 이를 통해 중복 마이그레이션 확인 및 버전 관리 가능
  • Flyway 와 다른 점이 있다면 Flyway 는 이러한 변경 정보를 각 테이블에 모두 기록
    • 반면 Alembic 은 마지막에 마이그레이션 된 버전의 해시값만을 기록

사용법

설치

$ pip install alembic

초기화

  • Alembic 을 설치하면 커맨드라인에서 alembic 명령어 사용 가능
    • 아래의 명령어를 사용하여 프로젝트 최상위에 원하는 디렉토리 이름으로 마이그레이션 폴더를 생성
$ alembic init {migration의 환경명}
  • 환경 생성이 완료되면 alembic.ini 파일과 {migration의 환경명} 디렉토리가 생성됨
    • 마이그레이션 스크립트를 초기화함
    • 초기화한 스크립트와 파일은 {migration의 환경명} 새로운 폴더를 생성하여 만들어줌

  • 각 파일에 대한 간략 설명
    • alembic.ini
      • env.py 파일에서 Configuration 파일로 사용되는 alembic 설정 파일
    • env.py
      • 마이그레이션 시 실행되는 서버 연결 및 마이그레이션 실행 코드
    • script.py.mako
      • 마이그레이션 스크립트 템플릿 파일
    • versions
      • 이 폴더에 마이그레이션 할 스크립트 코드가 저장
  • 여기서 우리가 사용해야 하는 파일은 env.pyversions 두 가지
    • env.py 파일로 한 번 환경을 세팅하고 그 이후로는 versions 를 이용하여 관리

데이터베이스 설정

  • alembic.ini 파일을 열고 sqlalchemy.url 변수에 연결하고자 하는 데이터베이스 주소를 입력
# the output encoding used when revision files
# are written from script.py.mako
# output_encoding = utf-8

sqlalchemy.url = mysql+pymysql://root:pwd@mysql:3306/hwaya
  • ini 파일이기 때문에 Python 에서 제공하는 string 관련 함수를 사용할 수 없으며 오직 정적 문자열만을 이용해야 함
  • 이렇게 설정하게 되면 환경별로 데이터베이스를 마이그레이션 할 때마다 Instance 주소를 바꿔줘야 하는 불편함 존재
    • 이런 경우에는 alembic.ini 파일에 있는 sqlalchemy.url 변수를 제거한 후 env.py 파일에서 sqlalchemy.url 을 수동으로 설정할 수 있는 방법 존재
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config

if not config.get_main_option("sqlalchemy.url"):
    config.set_main_option(
        "sqlalchemy.url", 
        "mysql+pymysql://{username}:{password}@{host}:{port}/{db_name}".format(
            username="root",
            password="pwd",
            host="mysql",
            port="3306",
            db_name="hwaya"
        )
    )
  • env.py 파일에은 Python 코드이기 때문에 Python 에서 제공하는 함수 등을 이용하여 환경별로 데이터베이스 마이그레이션을 쉽게할 수 있음

마이그레이션 스크립트 생성

  • alembic 에서 마이그레이션 스크립트 템플릿을 자동으로 만들어주는 명령어를 사용하여 초기 파일 생성
$ alembic revision -m "{commit 메세지}"
  • revision 명령어에 m 옵션을 사용하여 해당 마이그레이션에 대한 코멘트 가능
    • 어떤 변경 사항인지를 쉽게 파악할 수 있음
  • 코드를 확인하면 revisiondown_revision 이라는 변수가 보임
    • 이들 변수를 이용하여 alembic 은 마이그레이션 스크립트가 동작되었는지를 확인
# Script 예시

"""{commit 메세지}

Revision ID: e19af14ddb13
Revises: 
Create Date: 2023-01-10 22:10:21.507160

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = 'e19af14ddb13'
down_revision = None
branch_labels = None
depends_on = None


def upgrade():
    pass


def downgrade():
    pass
  • upgrade 함수에는 이 마이그레이션을 적용했을 때 사용할 스크립트가 들어감
    • alembic upgrade 명령어와 함께 revision_hash 를 입력하면 해당 해쉬에 맞는 마이그레이션 스크립트의 upgrade 함수를 순차적으로 호출하여 마이그레이션이 진행
$ alembic upgrade {revision_hash}
  • downgrade 함수에는 마이그레이션 이후 이를 다시 롤백할 경우 발생하는데 구체적으로는 아래와 같이 동작
    • downgrade 는 현재의 revision 에서 +- 숫자를 조정 입력하여 롤백과 업그레이드를 반복할 수 있음
$ alembic downgrade {revision_hash or current_revision +- 1}
# Script 예시

"""{commit 메세지}

Revision ID: e19af14ddb13
Revises: 
Create Date: 2023-01-10 22:10:21.507160

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = 'e19af14ddb13'
down_revision = None
branch_labels = None
depends_on = None


def upgrade():
    op.create_table("memos",
    sa.Column('id', sa.String(length=200), nullable=False),
    sa.Column('title', sa.String(length=100), nullable=False, index=True),
    sa.Column('content', sa.Text, nullable=True),
    sa.Column('is_favorite', sa.Boolean(), nullable=False),
    sa.PrimaryKeyConstraint('id')
    )


def downgrade():
    op.drop_table("memos")
  • alembicop 에서 제공하는 create_tabledrop_table 등의 함수를 사용하여 시작 쿼리를 정의하고 SQLAlchemyColumnString 등을 사용하여 컬럼의 정보를 정의할 수 있음
    • alembic upgradehead 를 이용하면 최신의 마이그레이션 스크립트까지 전부 적용
$ alembic upgrade head

Tip! 추가 내용

마이그레이션 실행

$ alembic upgrade head
  • upgrade 의 커맨드로 마이그레이션을 실행
    • head 는 최신 버전까지의 마이그레이션을 실행한다는 의미
    • 1개의 버전만 올리고 싶은 경우 head 가 아닌 +1 을 사용
$ alembic downgrade base
  • 버전을 낮추고 싶은 경우는 downgrade 커맨드를 사용
    • 바로 전의 버전으로 돌아고 싶은 경우 base 가 아닌 -1 을 사용

자동 생성

  • alembic 에서 제공하는 autogenerate 옵션은 SQLAlchemy 의 ORM 을 이용하여 정의한 Model 의 메타데이터를 읽어 변경된 부분을 자동으로 확인하여 마이그레이션 스크립트를 작성해줌
    • 단점이 있다면 애플리케이션의 작성이 시작된 시점부터 autogenerate 를 꾸준히 사용해야 하며 애플리케이션 개발 중간에 사용하는 경우 변경점을 alembic 을 파악하지 못하여 오히려 좋지 않은 결과를 초래하기도 함
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

# add your model's MetaData object here
# for 'autogenerate' support
target_metadata = Base.metadata
  • autogenerate 옵션을 사용하기 위해서는 먼저 target_metadata 변수를 설정
    • 초기값이 None 으로 설정되어 있는데 SQLAlchemy 를 사용하고 있다면 제공하는 declarative_base 함수에서 반환하는 metadata 를 사용할 수 있음

버전 관리

  • 마이그레이션 스크립트를 최초로 적용하면 해당 스크립트를 바탕으로 테이블이 만들어지고 그와 함께 alembic_version 이라는 이름의 테이블이 하나 더 생성됨
    • 마이그레이션 스크립트를 생성한 revision 해시 번호가 저장되어 있음
    • 실제로 alembic 은 이 번호를 보고 데이터베이스가 어디까지 마이그레이션 되었는지를 확인
    • 그런데 서비스를 운영하는 스키마와 같은 곳에 버전 관리 테이블이 놓이게 되니 이를 구부하기 어려움
  • alembic 의 버전 관리 테이블을 다른 스키마에서 운영하는 방법
    • 해당 상태에서 스키마를 변경하고자 한다면 alembic downgrade 를 진행한 뒤 env.py 파일에서 스키마를 지정하여 alembic 의 테이블을 별도로 관리 가능
def run_migrations_online():
    """Run migrations in 'online' mode.

    In this scenario we need to create an Engine
    and associate a connection with the context.

    """
    connectable = engine_from_config(
        config.get_section(config.config_ini_section),
        prefix="sqlalchemy.",
        poolclass=pool.NullPool,
    )

    with connectable.connect() as connection:
        context.configure(
            connection=connection, 
            target_metadata=target_metadata,
            include_schema=True,
            version_table_schema="alembic"
        )
        
        connection.execute(
            "CREATE SCHEMA IF NOT EXISTS {schema}".format(schema="alembic")
        )
        
        with context.begin_transaction():
            context.run_migrations()
  • alembic 이라는 이름으로 스키마 설정
    • 만약 alembic 스키마가 없을 경우 자동으로 만들어 줄 수 있도록 쿼리를 삽입
profile
🌱 Backend-Dev | hwaya2828@gmail.com
post-custom-banner

0개의 댓글