๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜๋ฉฐ ๋ชจ๋ธ์„ ๋ณ€๊ฒฝํ•ด์•ผ ํ•  ์ผ์ด ์ƒˆ๊ฒผ๋‹ค. ํ…Œ์ด๋ธ”์„ ์ˆ˜์ •ํ•˜๊ณ  migration์„ ์ ์šฉํ•˜๋ฉด์„œ dependency ์˜ค๋ฅ˜๋ถ€ํ„ฐ relationExists ์˜ค๋ฅ˜๊นŒ์ง€ ์•„์ฃผ ๋‚œํ•ญ์„ ๊ฒช์—ˆ๋‹ค.

์‚ฌ์‹ค ํ† ์ดํ”„๋กœ์ ํŠธ์—์„œ๋Š” migration์ด ๊ผฌ์ด๋ฉด ๊ทธ๋ƒฅ ์ „๋ถ€ ๋ฐ€์–ด๋ฒ„๋ฆฌ๊ณ  ๋‹ค์‹œ ์ ์šฉํ•˜๋ฉด ๊ทธ๋งŒ์ด์—ˆ๋‹ค. ํ•˜์ง€๋งŒ ์‹ค์ œ๋กœ ๋ฐฐํฌ๋˜๊ณ  ๋ฐ์ดํ„ฐ๊ฐ€ ๋‹ด๊ฒจ ์žˆ๋Š” DB์˜ ํ…Œ์ด๋ธ”์„ ์ˆ˜์ •ํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” ์ด๋Ÿฐ 1์ฐจ์›์ ์ธ ๋ฐฉ์‹์œผ๋กœ ์ ‘๊ทผํ•  ์ˆ˜๋Š” ์—†์—ˆ๋‹ค.

๋‹ค์†Œ ๊ธด ์‚ฝ์งˆ์˜ ๊ณผ์ •์„ ๊ฒฝํ—˜ํ•˜๋ฉฐ, ๋‚ด๊ฐ€ migration์— ๋Œ€ํ•˜์—ฌ ์ •ํ™•ํžˆ ์ดํ•ด๋ฅผ ํ•˜์ง€ ๋ชปํ•˜๊ณ  ์žˆ์Œ์„ ๊นจ๋‹ฌ์•˜๋‹ค.


Migration์ด๋ž€?

์ผ์ข…์˜ database version control log๋ผ๊ณ  ์ดํ•ดํ•˜๋ฉด ๋  ๊ฒƒ ๊ฐ™๋‹ค. python manage.py makemigrations ๋ช…๋ น์–ด๋ฅผ ์ˆ˜ํ–‰ํ•˜๋ฉด ๊ฐ app์˜ ๋ชจ๋ธ์— ๋Œ€ํ•œ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ๊ธฐ๋กํ•œ python script๊ฐ€ ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋˜๊ณ 

$ python manage.py makemigrations [app_name]
Migrations for 'users':
  apps/users/migrations/0002_user_auth_id_user_social_type.py
    - Add field auth_id to users
    - Add field social_type to users

python manage.py migrate ๋ช…๋ น์–ด๋ฅผ ์ˆ˜ํ–‰ํ•˜๋ฉด db์— ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ๋ฐ˜์˜ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด migration script๋Š” 000X_changelog_contents_whatever.py ํ˜•์‹์œผ๋กœ ๋„ค์ด๋ฐ๋˜๋ฉฐ, ๋ชจ๋ธ ๊ฐ„์˜ ๊ด€๊ณ„(์ƒ์„ฑ ์ˆœ์„œ, ์ฐธ์กฐ ๋ฐฉํ–ฅ ๋“ฑ)๋ฅผ ๊ณ ๋ คํ•˜์—ฌ ์ˆœ์ฐจ์ ์œผ๋กœ dependency๊ฐ€ ์กด์žฌํ•œ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, BookObject ํ…Œ์ด๋ธ”์—์„œ User ํ…Œ์ด๋ธ”์„ FK๋กœ ์ฐธ์กฐํ•˜๊ณ  ์žˆ๋Š” ๊ฒฝ์šฐ๋ผ๋ฉด ๋ฐ˜๋“œ์‹œ User ํ…Œ์ด๋ธ”์˜ ์ƒ์„ฑ์ด ์„ ํ–‰๋˜์–ด์•ผ๋งŒ BookObject ํ…Œ์ด๋ธ”์„ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ๋‹ค.

class User(AbstractUser, TimeStampModel):
    username = None
    email = models.EmailField(
        max_length=255,
        verbose_name='์ด๋ฉ”์ผ',
        validators=[SpecificEmailDomainValidator(allowlist=domain_allowlist)]
    )
    
class BookObject(TimeStampModel):
    user = models.ForeignKey(
        User,
        on_delete=models.CASCADE,
        related_name='note',
        verbose_name='์ž‘์„ฑ์ž'
    )

๋”ฐ๋ผ์„œ BookObject app์˜ migration์—๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ dependency(users.migrations.0001_initial.py ์„ ์ ์šฉํ•œ ํ›„์— ํ•ด๋‹น migration์„ ์ ์šฉํ•  ์ˆ˜ ์žˆ์Œ)๊ฐ€ ๋ช…์‹œ๋˜์–ด ์žˆ๋‹ค.

class Migration(migrations.Migration):
    initial = True
    dependencies = [
        ('users', '0001_initial'),
    ]

์ ์šฉ๋œ migration ํ™•์ธํ•˜๊ธฐ

migration ์ ์šฉ ๋‚ด์—ญ์€ django_migrations ํ…Œ์ด๋ธ”์— ์ €์žฅ๋˜๋Š”๋ฐ ์ด ๋•Œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— migration ๋‚ด์šฉ์€ ์ €์žฅ๋˜์ง€ ์•Š๋Š”๋‹ค. ์ ์šฉ ์ˆœ์„œ, script๋ช…, ์‹œ๊ฐ„ ์ •๋„์˜ ๋ฐ์ดํ„ฐ๋งŒ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

migration-001.png

๋”ฐ๋ผ์„œ ์ด๋ฏธ ์ ์šฉ๋œ migration script๋ฅผ ์ˆ˜์ •ํ•œ ๋’ค ๋‹ค์‹œ migrate๋ฅผ ์ˆ˜ํ–‰ํ•œ๋‹ค๊ณ  ํ•ด๋„ django_migration ํ…Œ์ด๋ธ”์— ๋ณ€๊ฒฝ ๋‚ด์šฉ์€ ์ €์žฅ๋˜์ง€ ์•Š๋Š”๋‹ค. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ˆ˜์ • ๋‚ด์šฉ์ด ๋ฐ˜์˜๋˜์ง€ ์•Š๊ณ  ์ถฉ๋Œ์ด ์ผ์–ด๋‚  ์ˆ˜๋„ ์žˆ๋‹ค.

python manage.py showmigrations ๋ฅผ ํ†ตํ•ด์„œ๋„ ํ•œ๋ˆˆ์— migration ์ ์šฉ์ƒํƒœ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

migration-002.png



์ด๋ฏธ ์ ์šฉ๋œ migration์„ Revertํ•˜๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ?

์•„๋ž˜์™€ ๊ฐ™์ด Revertํ•˜๊ณ ์ž ํ•˜๋Š” app์˜ ์ด๋ฆ„๊ณผ migration ๋ฒˆํ˜ธ๋ฅผ ๋ช…์‹œํ•˜๋ฉด ๋œ๋‹ค.

$ python manage.py migrate [app_name] [number]

์˜ˆ๋ฅผ ๋“ค์–ด 0004๊นŒ์ง€ migration์ด ์ ์šฉ๋˜์–ด ์žˆ๋Š” User app์ด ์žˆ๋‹ค๊ณ  ํ•˜์ž. python manage.py migrate users 0002 ๋ผ๋Š” ๋ช…๋ น์„ ์ˆ˜ํ–‰ํ•˜๋ฉด, 0003 ์ดํ›„์˜ migration์€ unapply๋˜๊ณ  0002 migration ๊ธฐ์ค€์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ๋™์ž‘ํ•œ๋‹ค

์ด ๋•Œ, ์ฃผ์˜ํ•˜์—ฌ์•ผ ํ•  ์ ์€ ๋˜๋Œ๋ฆฌ๊ณ ์ž ์‹œ์  ์ดํ›„์— ๋Œ€ํ•œ migration script๋ฅผ ์ ˆ๋Œ€ ์‚ญ์ œํ•ด์„œ๋Š” ์•ˆ๋œ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค! ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ๋ช…๋ น์„ ์กด์žฌํ•˜์ง€๋„ ์•Š๋Š” migration์„ revertํ•˜๋ ค๋Š” ๊ฒƒ์œผ๋กœ ์ดํ•ดํ•  ๊ฒƒ์ด๋‹ค. ์‚ญ์ œ๋œ migration์„ ๋ณต๊ตฌํ•  ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ๋ผ๋ฉด ์ง์ ‘ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ํ…Œ์ด๋ธ”์„ ์ˆ˜์ •ํ•ด์•ผ ํ•œ๋‹ค.


์ด๋ฏธ ์ ์šฉ๋œ migration ๋‚ด์šฉ์„ ์ˆ˜์ •ํ•˜๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ?

n๋ฒˆ์งธ migration ์ ์šฉ ๋‚ด์—ญ์„ ์ˆ˜์ •ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•˜๋ฉด ๋ ๊นŒ? n+1๋ฒˆ์งธ์˜ ์ƒˆ๋กœ์šด migration script๋ฅผ ์ƒ์„ฑํ•˜๋ฉด ๋œ๋‹ค. ์œ„์— ์–ธ๊ธ‰ํ•œ ๊ฒƒ์ฒ˜๋Ÿผ ์ด๋ฏธ ์ ์šฉ๋œ migration์„ ์ˆ˜์ •ํ•˜๋Š” ๊ฒƒ์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ถฉ๋Œ์„ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜๋„ ์žˆ๊ณ , ์ดํ›„์— ์ ์šฉํ•  migration์—์„œ ์ถฉ๋Œ์ด ๋ฐœ์ƒํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

๊ทธ๋ž˜๋„ n๋ฒˆ์งธ๋กœ ์ ์šฉ๋œ migration์„ ์ˆ˜์ •ํ•˜๋Š” ๋ฐฉํ–ฅ์œผ๋กœ ๊ฐœ๋ฐœ์„ ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, ์ด์ „ n-1๋ฒˆ์งธ version์œผ๋กœ revert๋ฅผ ์ˆ˜ํ–‰ํ•œ๋’ค์— ์ƒˆ๋กญ๊ฒŒ n๋ฒˆ์งธ migration script๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ ์šฉํ•˜๋ฉด ๋œ๋‹ค.


migration์„ Squashํ•˜์—ฌ log๋ฅผ ๊ด€๋ฆฌํ•˜๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ?

python manage.py squashmigrations๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์กด์žฌํ•˜๋Š” ๋งŽ์€ ์ˆ˜์˜ migration์„ ํ•œ ๊ฐœ(๋˜๋Š” ๊ทธ ์ด์ƒ)์˜ migration์œผ๋กœ ํ•ฉ์ณ์„œ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด CreateModel๊ณผ DeleteModel action์„ ํ•ฉ์น  ์ˆ˜ ์žˆ๊ณ , AddField action์„ CreateModel ๋‚ด๋ถ€๋กœ roll ์‹œํ‚ฌ ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋‹ค.

squash๋œ migration๊ณผ ์ด์ „ ๋ฒ„์ „์˜ migration log๋Š” ๋™์‹œ์— ์กด์žฌํ•  ์ˆ˜ ์žˆ๋‹ค. Django๋Š” migration์ด ์ ์šฉ๋˜๋Š” ์‹œ์ ์„ ์ž๋™์œผ๋กœ ํŒŒ์•…ํ•˜์—ฌ ์ƒˆ๋กœ์šด version์—์„œ๋Š” squash๋œ migration์„ ์ ์šฉํ•˜๊ณ  ์ด์ „ ๋ฒ„์ „์˜ migration์„ skipํ•œ๋‹ค.

--squashed-name ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ squashํ•  migration script์˜ ์ด๋ฆ„์„ ์ง€์ •ํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

$ python manage.py squashmigrations [app_name] [number] --squaushed_name [SQUASHED_NAME]
Will squash the following migrations:
 - 0001_initial
 - 0002_some_change
 - 0003_another_change
 - 0004_undo_something
Do you wish to proceed? [y/N] y
Optimizing...
  Optimized from 12 operations to 7 operations.
Created new squashed migration /home/andrew/Programs/DjangoTest/test/migrations/0001_squashed_0004_undo_somthing.py
  You should commit this migration but leave the old ones in place;
  the new migration will be used for new installs. Once you are sure
  all instances of the codebase have applied the migrations you squashed,
  you can delete them.