Migrations

rang-dev·2020년 3월 17일
0

Migrations

  • 데이터 스키마들의 변경을 관리하는 것을 담당한다.
  • 데이터베이스 스키마에 대한 실수가 발생한다면 전체 앱이 다운될 수도 있다. 따라서 우리는 다음을 원한다.
    • 빠르게 변경사항을 rollback하기
    • 변화를 적용하기 전에 테스트해보기
  • Migraion이란 데이터베이스 스키마에 대한 변경사항들을 추적하는 파일이다.
    • 우리의 스키마에 관한 version control을 제공한다.
  • Migrations의 파일명은 중복되지 않는다.
  • 보통 프로젝트 repo에 local files로서 저장된다.(e.g. a migrations/ folder)
  • 데이터베이스에 생긴 변경사항과 migrations/ 폴더에 저장된 migration files는 1대1 맵핑된다.
  • Our migrations files set up the tables for our databse.
  • db에 생긴 모든 변경사항들은 우리의 repository안에 migration의 부분으로서 물리적으로 존재해야한다.

Upgrades and rollbacks

  • 우리는 migrations을 적용함으로써 데이터베이스 스키마를 upgrade 할 수 있다.
  • 우리는 migrations를 reverting함으로써 데이터베이스 스키마를 이전 버전으로 roll back 할 수 있다.

Migration Command Line Scripts

  • migrate: 만들어진 변경사항에 대하여 migration 파일을 생성한다.
  • upgrade: 아직 적용되지 않은 migrations를 적용한다.
  • downgrade: 문제가 있는 적용된 migrations을 roll back한다.

Migration Library for Flask + SQLAlchemy

  • Flask-Migrate is our library for migrating changes using SQLAlchemy. It uses a library called Alembic underneath the hood.
  • Flask-Migrate is our migration manager for migrating SQLAlchemy-based database changes
  • Flask-Script lets us run migration scripts we defined, from the terminal

Migrations이 일어나는 과정

  • Migrations을 저장하기 위해 migration repository structure를 초기화한다.
  • Flask-Migrate를 이용하여 migration script를 만든다.
  • (Manually)Flask-Scrip를 이용하여 migration script를 실행한다.

Why Use Migrations?

  • migrations이 없다면...
    • 아주 사소한 변화가 발생해도 모든 테이블들을 다시 만들어야한다.
    • 우리가 삭제한 예전 데이터들을 잃는다.
  • migration을 사용한다면...
    • SQLAlchemy 모델의 old version과 new version의 변화를 자동으로 감지한다.
    • Old, new 버전 간의 차이점에 관한 migration script를 만든다.
    • 기존의 테이블을 변경하기 위하여 fine-grain control을 제공한다.
  • migrations 사용이 더 좋은 이유
    • 기존 스키마 구조를 유지하면서 우리가 수정해야되는 부분만 수정하면 된다.
    • 기존 데이터를 보유할 수 있다.
    • We isolate units of change in migration scripts that we can roll back to a safe db state.

Flask-Migrate

설치방법

커맨드창에서 pip install Flask-Migrate를 실행한다.

실행방법

# app.py

from flask_migrate import Migrate

migrate = Migrate(app, db)   # Falsk app, SQLAlchemy database와 연결

Creating the migrations directory structure using flask db init

  • Create inital migrations directory structure
  • 커맨드 창에서 app.py이 저장된 폴더로 이동한 후 falsk db init을 해준다.
  • 생성된 alembic.ini는 로깅, 파일 이름, 기본 포멧등의 구성을 설정할 수 있는 config scripts 이다.

Syncing models using flask db migrate

  • 변경 사항을 감지하고 upgrade와 downgrade이 있는 migration 파일을 생성한다.
  • 기존의 스키마를 모두 기록하기 때문에 db.create_all()을 대체하므로 db.create_all()은 삭제해준다.
  • 만약 우리가 todos를 저장했던 데이터베이스 todoapp을 삭제하고 다시 생성한 후 flask db migrate를 한다면 versions 폴더에 다음과 같은 migration이 생성된다.
from alembic import op
import sqlalchemy as sa


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


def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.create_table('todos',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('description', sa.String(), nullable=False),
    sa.PrimaryKeyConstraint('id')
    )
    # ### end Alembic commands ###


def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_table('todos')
    # ### end Alembic commands ###

Flask db upgrade and flask db downgrade

todoapp이라는 데이터베이스를 삭제했다가 다시 생성하면 안에 있던 테이블들은 다 사라진다.
하지만 flask db upgrade를 한 후 todoapp 내에 있는 테이블을 확인해보면

todoapp=# \dt
           릴레이션(relation) 목록
 스키마 |      이름       |  종류  |  소유주
--------+-----------------+--------+----------
 public | alembic_version | 테이블 | postgres
 public | todos           | 테이블 | postgres

이와 같이 todos가 다시 생성된 것을 볼 수 있다. 생성된 내용은 migration에 있는 upgrade() 내용과 같다.
alembic_version이라는 테이블은 우리의 데이터베이스의 버전을 저장하고 migrations을 관리한다.

또한 여기서 flask db downgrade를 하게되면 생성되었던 todos가 다시 사라지는 것을 볼 수 있다. 이것은 migration의 downgrade()이 실행된 결과이다.


Adding the completed column to test migration

class Todo(db.model):
  __tablename__ = 'todos'
  id = db.Column(db.Integer, primary_key=True)
  description = db.Column(db.String(), nullable=False)
  completed = db.Column(db.Boolean, nullable=False, default=False)

위와 같이 completed라는 컬럼을 추가한 후 커맨드 창에서 flask db migrate를 실행한다.

INFO  [alembic.autogenerate.compare] Detected added column 'todos.completed'
Generating C:\Users\gusfk\Desktop\class-demos\todoapp\migrations\versions\7c53ca560e11_.py ...  done

completed라는 새로운 칼럼을 인식하고 그에 따른 migrations인 7c53ca560e11_.py을 생성한 것을 확인할 수 있다.

# 7c53ca560e11_.py

def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.add_column('todos', sa.Column('completed', sa.Boolean(), nullable=False))
    # ### end Alembic commands ###
    
def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_column('todos', 'completed')
    # ### end Alembic commands ###

내용을 확인한 후 flask db upgrade를 통해 적용되지 않은 migraions의 가장 최근 버전으로 업그레이드 해준다.


Working with existing data

만약 completed 컬럼을 추가하기 전 테이블에 기존 값(id, description)들이 있다면 어떨까?
기존의 테이블에 값들이 존재할 때 flask db upgrade를 실행하면 다음과 같은 오류가 나타난다.

sqlalchemy.exc.IntegrityError: (psycopg2.errors.NotNullViolation) 오류:  "completed" 열에는 null 값 자료가 있습니다
 [SQL: 'ALTER TABLE todos ADD COLUMN completed BOOLEAN NOT NULL'] (Background on this error at: http://sqlalche.me/e/gkpj)

우리가 클래스를 작성할 때 completed 컬럼에서 nullable=False라고 했지만 해당 컬럼이 생기기 전에 작성했던 레코드에서는 null값일 수 밖에 없기 때문이다.

이 문제를 해결하기 위해서는 해당 migration script를 수정해주어야 한다.

# 7c53ca560e11_.py

def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###

    # op.add_column('todos', sa.Column('completed', sa.Boolean(), nullable=False))  --> nullable=False에서 True로 변경한다.
    op.add_column('todos', sa.Column('completed', sa.Boolean(), nullable=True))  #1
    
    op.execute('UPDATE todos SET completed = False WHERE completed IS NULL;')  #2

    op.alter_column('todos', 'completed', nullable=False)  #3

    # ### end Alembic commands ###
  1. nullable=True로 변경하여 기존 값들에도 새로운 스키마가 적용될 수 있도록 한다.
  2. op.execute()로 SQL update문들 이용하여 기존 테이블에 있던 레코드의 completed 칼럼의 값을 null에서 False로 변경한다.
  3. completed컬럼에 새로운 null값들이 올 수 없도록 nullable=False로 변경해준다.

그리고 flask db upgrade를 한 후 psql을 통해 todos 테이블을 확인해보면

todoapp=# \d todos
                                   "public.todos" 테이블
   필드명    |       종류        | Collation | NULL허용 |              초기값
-------------+-------------------+-----------+----------+-----------------------------------
 id          | integer           |           | not null | nextval('todos_id_seq'::regclass)
 description | character varying |           | not null |
 completed   | boolean           |           | not null |
 
 todoapp=# select * from todos;
 id | description | completed
----+-------------+-----------
  1 | todo1       | f
  2 | todo2       | f
  3 | todo3       | f

completed 컬럼이 잘 추가된 것을 확인할 수 있다.


추가 자료를 보고싶다면?

profile
지금 있는 곳에서, 내가 가진 것으로, 할 수 있는 일을 하기 🐢

0개의 댓글