django models.py의 변경과 migration 파일에 대한 고찰

Hansu Kim·2022년 2월 15일
1

Django

목록 보기
10/10

django에서 models.py에 모델을 선언할 때,
ModelForm을 오버라이딩하여 모델을 선언하게 된다.

ModelForm에서는 validators를 제공해주어, 특정 필드에 대한 유효성 검사를 진행할 수 있도록 해준다.
관련하여 버전업데이트 적용 작업 중, 마이그레이션을 수행하며 의문점이 생기게 됐다.

의문점

마이그레이션 과정 중 의문이 생긴 상황은 다음과 같다.

  1. 버전 업데이트 과정에서 특정 필드의 validators에 할당된 함수의 이름이 변경됐다.
  2. 변경사항들을 적용하기 위해 makemigration을 수행했다.
  3. validators 변경사항이 마이그레이션 파일로 별도 생성됐다.

validators 함수는 django의 Application Layer에서 동작하는 유효성 체크로 인지하고 있었다.
하지만 해당 변경 사항이 마이그레이션 파일에 반영됐다는 것은, validators로 할당된 로직이 DB에도 적용되고 있다고 유추하게 됐고, 그 뜻은 즉 validators의 유효성 체크가 DB에서 수행되는 작업이지 않을까? 하는 의문점을 갖게 됐다.

본 포스트는 해당 의문점을 확인해나가는 내용이다.

Models.py

from .validators import validate_vlan_band_test

class Zone(models.Model):
    id = models.BigAutoField(help_text="Zone ID", primary_key=True)
    zone_id = models.CharField(max_length=36, verbose_name="Zone ID")
    displaytext = models.CharField(max_length=64, verbose_name="Zone 이름")
    description = models.CharField(
        max_length=254, verbose_name="비고", null=True, blank=True)
    firewall = models.ForeignKey(
        'firewall.Firewall', on_delete=models.PROTECT, verbose_name='firewall', related_name='firewall')
    test_cols = models.CharField(default="", max_length=64, verbose_name='테스트')
    internet_vlan_band = models.TextField(
        default="", verbose_name="인터넷향 대역(형식: start_end, start 이상 end 이하)",
        null=True, blank=True, validators=[validate_vlan_band_test])
        // validators의 함수가 validate_test -> validate_vlan_band_test로 변경됐다.

validators.py

def validate_vlan_band_test(value):
    if not isinstance(value, str):
        raise ValidationError(TYPE_ERROR_MSG)

    if not len(value.split("_")) == 2:
        raise ValidationError(FORMAT_ERROR_MSG)

    if not value.split("_")[0].isdigit():
        raise ValidationError(FORMAT_ERROR_MSG)
     //이하 생략

생성된 마이그레이션 파일

# Generated by Django 3.2.12 on 2022-02-15 18:06

from django.db import migrations, models
import zone.validators


class Migration(migrations.Migration):

    dependencies = [
        ('zone', '0001_initial'),
    ]

    operations = [
        migrations.AddField(
            model_name='zone',
            name='test_cols',
            field=models.CharField(default='', max_length=64, verbose_name='테스트'),
        ),
        migrations.AlterField(
            model_name='zone',
            name='internet_vlan_band',
            field=models.TextField(blank=True, default='', null=True,
            validators=[zone.validators.validate_vlan_band_test], 
            verbose_name='인터넷향 대역(형식: start_end, start 이상 end 이하)'),
        )
    ]

검증하는 과정

유효성 체크가 DB레벨로 넘어간다면 DB의 프로시저나 트리거로 수행되지 않을까 해서 모든 프로시저를 조회해봤다.

fa_db_2=> select prosrc,proname from pg_catalog.pg_proc where proname='validate_vlan_band_test';
 prosrc | proname
--------+---------
(0개 행)

하지만 프로시저에 해당 validators가 등록된 내역은 없었다.
DB에서 더 촘촘하게 조회를 해보기엔 비효율적으로 보여, 좀 더 효율적인 방법을 생각하던 중
마이그레이션 파일의 내용을 SQL문으로 변환해보면 DB에 적용되는지 확인할 수 있을 것으로 추정했다.

(FA-dev) [fasystem@fasystem-lhj FA_proj]$  python manage.py sqlmigrate zone 0002 --settings=FA_proj.settings.dev
BEGIN;
--
-- Add field test_cols to zone
--
ALTER TABLE "fa_zone" ADD COLUMN "test_cols" varchar(64) DEFAULT '' NOT NULL;
ALTER TABLE "fa_zone" ALTER COLUMN "test_cols" DROP DEFAULT;
--
-- Alter field internet_vlan_band on zone
--
--
COMMIT;

SQL문으로 변환하자, 예상과는 달리, 마이그레이션 파일에는 반영되어있던 내용이 SQL문으로는 아무것도 반영되지 않고 있었다.

결론

마이그레이션 파일은 models.py의 모든 변경사항이 기록된다.
마이그레이션 파일의 목적은 DB에 모델의 변경사항을 적용하기 위해 생성되는 것이다.

하지만, DB에 적용되어야하는 변경사항들만이 마이그레이션 파일로 생성되는 것은 아니다.
기본적으로, 모든 모델 변경사항이 기록되나, 기록된 것들 중 필요한 사항들에 대해서만 SQL문으로 DB에 수행된다.

추가 의문

만약, 유효성 체크를 DB레벨에서 수행하고 싶다면 어떻게 구현하면 될까?

django에서는 해당 방안으로 Meta.constraints 클래스를 제공해주고 있었다.
constraint 클래스의 checkconstraints 메소드를 활용하면, SQL방식이나 boolean Expression으로 유효성 체크 로직을 표현할 수 있으며, 해당 사항은 DB에 적용되어 DB레벨에서 유효성 체크가 수행되게 된다.

마이그레이션 파일은 operation 기준으로 나뉘어 기록된다. 만약 오퍼레이션이 도중 실패한다면, Operation들은 원복될까?

해당 사항에 대해 하나하나 실험해본 결과, 하나의 마이그레이션 파일 적용시 세부적인 operation들은 모두 일괄적으로 원복되는 것으로 확인했다. 아마 DB에 변경사항이 하나의 트랜잭션 형태로 수행되는 것이 아닐까 유추해볼 수 있었다.

Django의 showmigrations는 장고 서버에서 자체적으로 관리하는 히스토리 성격의 데이터가 아니다.

Active-Standby 구조의 같은 소스로 관리되나 별도로 구축되고 하나의 DB에 연동된 django 서버에서 버전업데이트를 수행하며 아래 과정을 실험해보았다.

  1. Active 서버에서 소스 업데이트 후 makemigrations / migrate
  2. Backup 서버에서 소스 업데이트 후 makemigrations

위 과정에서 예상되던 결과는 Backup 서버의 Showmigrations시, 새로 생성된 마이그레이션파일들은 미수행된 상태로 마이그레이션 히스토리가 조회되는 것이었다.
하지만 예상과 달리, 2번 과정에서, 백업서버에 새로 생성된 마이그레이션 파일들은 showmigration시 모두 적용 완료된 것으로 조회됐고, 적용일은 Active서버에서 작업한 날짜가 기록되어 있었다.
그에 따라 showmigration의 히스토리는 DB에서 관리되는 데이터라고 확인할 수 있었고, 실제 DB테이블 확인 결과 아래와 같은 테이블에서 별도로 관리되고 있음을 확인할 수 있었다.

fa_db=> \dt
                릴레이션(relation) 목록
 스키마 |            이름            |  종류  | 소유주
--------+----------------------------+--------+--------
 public | auth_group                 | 테이블 | fa
 public | auth_group_permissions     | 테이블 | fa
 public | auth_permission            | 테이블 | fa
 public | auth_user                  | 테이블 | fa
 public | auth_user_groups           | 테이블 | fa
 public | auth_user_user_permissions | 테이블 | fa
 public | authtoken_token            | 테이블 | fa
 public | django_admin_log           | 테이블 | fa
 public | django_content_type        | 테이블 | fa
 public | django_migrations          | 테이블 | fa
 public | django_session             | 테이블 | fa
 public | fa_article                 | 테이블 | fa
 public | fa_company                 | 테이블 | fa
 public | fa_user                    | 테이블 | fa
(14개 행)

fa_db=> select * from django_migrations;
 id |     app      |                   name                   |            applied
----+--------------+------------------------------------------+-------------------------------
  1 | contenttypes | 0001_initial                             | 2021-04-06 22:00:04.122866+09
  2 | auth         | 0001_initial                             | 2021-04-06 22:00:04.155074+09
  3 | admin        | 0001_initial                             | 2021-04-06 22:00:04.246029+09
  4 | admin        | 0002_logentry_remove_auto_add            | 2021-04-06 22:00:04.260355+09
  5 | admin        | 0003_logentry_add_action_flag_choices    | 2021-04-06 22:00:04.267252+09
  6 | contenttypes | 0002_remove_content_type_name            | 2021-04-06 22:00:04.282181+09
  7 | auth         | 0002_alter_permission_name_max_length    | 2021-04-06 22:00:04.28933+09
  8 | auth         | 0003_alter_user_email_max_length         | 2021-04-06 22:00:04.296303+09
  9 | auth         | 0004_alter_user_username_opts            | 2021-04-06 22:00:04.304398+09
 10 | auth         | 0005_alter_user_last_login_null          | 2021-04-06 22:00:04.312346+09
 11 | auth         | 0006_require_contenttypes_0002           | 2021-04-06 22:00:04.314298+09
 12 | auth         | 0007_alter_validators_add_error_messages | 2021-04-06 22:00:04.322395+09
 13 | auth         | 0008_alter_user_username_max_length      | 2021-04-06 22:00:04.333149+09
 14 | auth         | 0009_alter_user_last_name_max_length     | 2021-04-06 22:00:04.342027+09
 15 | auth         | 0010_alter_group_name_max_length         | 2021-04-06 22:00:04.350815+09
 16 | auth         | 0011_update_proxy_permissions            | 2021-04-06 22:00:04.358019+09
 17 | auth         | 0012_alter_user_first_name_max_length    | 2021-04-06 22:00:04.365728+09
 91 | company      | 0001_initial                             | 2021-10-24 20:27:21.94671+09
 97 | user         | 0001_initial                             | 2021-10-24 20:27:21.959161+09
 39 | sessions     | 0001_initial                             | 2021-04-06 22:00:04.509214+09
 64 | authtoken    | 0001_initial                             | 2021-10-08 09:16:48.256101+09
 65 | authtoken    | 0002_auto_20160226_1747                  | 2021-10-08 09:16:48.28043+09
 66 | authtoken    | 0003_tokenproxy                          | 2021-10-08 09:16:48.284273+09
 83 | articles     | 0001_initial                             | 2021-10-24 20:16:54.742278+09
(24개 행)

결론을 정리하자면,

  1. showmigration시 조회되는 히스토리는 django에서 자체적으로 관리하는 히스토리가 아니다.
  2. showmigration시 각 마이그레이션 파일의 적용 여부는 DB의 django_migrations 테이블에서 관리되고 있다.

0개의 댓글