Migration - Tirrilee/TechTalk GitHub Wiki
์ฐธ๊ณ : Django Migration
- model์ ๋ณ๊ฒฝ ์ด๋ ฅ
- Database Version Control System
-
migrate: ์ ์ฉ ์๋ migrations ํ์ผ๋ค์ ์ ์ฉ์ํค๋ ๋ช ๋ น์ด -
makemigrations: model์ ๋ณํ๋ ๊ฒ๋ค์ ๊ธฐ๋ณธ์ผ๋ก ์๋ก์ด migration ํ์ผ์ ์์ฑํ๋ ๊ฒ -
sqlmigrate: migration์ sql ํ์์ผ๋ก ๋ณด์ฌ์ฃผ๋ ๊ฒ (DB์๋ SQL๋ก ์ ์ฉ๋๋ค.) -
showmigrations: migration์ ์ ์ฉ ์ฌ๋ถ๋ฅผ ๋ณด์ฌ์ฃผ๋ ๊ฒ
- default ๊ฐ๊ณผ ํจ๊ป columns๋ฅผ ์ถ๊ฐ, table full rewrite ๋ฑ ๋ชจ๋ ๋ณํ ๊ณผ์ ์ด ๊ฑฐ์ ์ง์ฒด์์ด ์ผ์ด๋จ
- schema๋ฅผ ๋ณ๊ฒฝํ๋ ๊ฒ ๋ฑ์ ์ฒ๋ฆฌ๊ฐ ์๊ฐ์ด ๊ฑธ๋ฆด ์ ์๋ค. ๋ง์ฝ ๋ช๊ฐ ๋์ง ์์ผ๋ฉด ๊ด์ฐฎ์ง๋ง, ์๋ง๊ฐ๋ผ๋ฉด site๋ฅผ down์ํฌ ์ ์๋ค.
- ๋ค์ํ ๊ธฐ๋ฅ์ ์ง์ํ๋, ๋ฒ๊ทธ๊ฐ ์กด์ฌํ๋ค. ๋ง์ฝ ์๋น์ค๊ฐ risk๋ ์ ์ฝ์ ๋งค์ฐ ๋ฏผ๊ฐํ๋ค๋ฉด ์ฌ์ฉํ์ง ์๋ ๊ฒ์ ์ถ์ฒํ๋ค.
- Django๋ local machine์์ full database๊ฐ ํ์์์ด ๊ฐ๋ฐํ๊ฒ ํ๊ธฐ ์ํด SQLite๋ฅผ ์ฌ์ฉํ๋ค.
- Django์์ ์ฐ์ฌ์ง Number๋ ๋จ์ง Developer๋ฅผ ์ํ ๊ฒ์ด๋ค. Django๋ ๋จ์ง migration ํ์ผ ๊ฐ์ ์ฐจ์ด๋ฅผ ํ์ธํ๋ค.
- migration๊ฐ์ ์์๋ migration ํ์ผ์ ์์ฑ๋ ์์กด์ฑ์ ๋ณด๊ณ ํ์ธํ๋ค.
- App์ ์๋ ํ ์ด๋ธ์ ์ ์ฉํ ์ฒซ๋ฒ์งธ Version
-
initial = True๊ฐ ์๋ migration ํ์ผ -
migrate --fake-initial: Database์ ํด๋น ํ ์ด๋ธ์ด ์๋ค๋ฉด, Fake๋ก migration์ ์งํ - ํด๋น ์์ ์ ํด์ผํ๋ ์ด์ ๋ table์ ์์ฑํ ์ดํ๋ก migration์ ๋ณ๊ฒฝํ์ง ์์์ด๋, ์ดํ ์๋ก์ด ๋ณ๊ฒฝ์ ์ ์ฉํ๋ ค๋ฉด initialํ์ผ์ด ํ์ํ๊ธฐ ๋๋ฌธ์ด๋ค.
- Django์์๋ ๊ฐ Database์ ์ญํ ์ ๋ฐ๋ผ Multiple Database๋ฅผ ์งํํ๋ค.
- Database Router์์ ํด๋น Database์ ์ญํ ์ ์ง์ ํด์ค ์ดํ, ์ง์ ํ ์ญํ ๋๋ก Database๊ฐ ์คํ๋๋ค.
settings.py์ ์ฌ์ฉํ ์ฌ๋ฌ database specification์ ์์ฑํด ๋๋๋ค.
DATABASES = {
'default': {},
'auth_db': {},
}class MultiDBRouter(object):
def db_for_read(self, model, **hints):
if model._meta.app_label == 'auth':
return 'auth_db'
return 'default'
def db_for_write(self, model, **hints):
if model._meta.app_label == 'auth':
return 'auth_db'
return 'default'
# migrate ์ ์ฉํ DB๋ฅผ ์ ํ
def allow_migrate(self, db, model):
if db == 'auth_db':
if model._meta.app_label == 'auth':
return True
else:
return False
return TrueDATABASE_ROUTERS = [
'<router_path>',
]from django.db import migrations, models
# ํ์ฌ ํด๋น ๋ชจ๋ธ์ ์๋ ๊ฐ๋ค์ ์ ์ฉ
# ์ดํ์ ์์ฑ๋๋ ๊ฐ๋ค์ ๋ํ ์ ์ฉ์ ํ์ง ์์
# Django Python2๋ migrations ํ์ผ ์์ ์ ์ํ ํจ์๋ค์ ๋ํ ์ง์์ด ๋ถ์กฑ
def combine_names(apps, schema_editor):
Profile = apps.get_model('app', 'Profile')
for profile in Profile.objects.all():
profile.first_name = '%s %s' % (profile.first_name, profile.last_name)
profile.save()
class Migration(migrations.Migration):
dependencies = [
('app', '0001_initial'),
]
operations = [
migrations.RunPython(combine_names),
]- Migrations ํ์ผ์ด ๋๋ฌด ๋ง์ ๋, ํด๋น ํ์ผ์ ์ต์ ํํ์ฌ ํ๋๋ก ํฉ์ณ์ฃผ๋ ๊ธฐ๋ฅ
$ python manage.py squashmigrations app_name migrations_file_name : migrations squash
์ฃผ์ : migrate ๋์ง ์์ ํ์ผ์ squashmigrations์ ํฌํจํ์ง ์๋๋ค.
Squash Migration์ ์งํํ๋ฉด squashํ ํ์ผ ์ด์ธ์ ๋ค๋ฅธ ํ์ผ๋ค์ ์ญ์ ํด๋ ์๋ฌ๊ฐ ๋์ง ์๋๋ค.
migrations ํ์ผ ์์ ์๋ก์ด ํจ์๋ฅผ ์ ์ํ๋๋ฐ ํด๋น ํ์ผ์ ๊ฐ์ ธ์ค๋๋ฐ ๊ณ์ํด์ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.
๋ฐ๋ผ์ ํด๋น ํ์ผ์ ํจ์๋ฅผ ์ง์ ๊ฐ์ ธ์์ suqshํ ํ์ผ์ ์ฌ์ ์ํ๋ค.
from django.db import migrations, models
class Migration(migrations.Migration):
replaces = [('app', '0001_initial'), ('app', '0002_custom_migrations'), ('app', '0003_profile_name')]
initial = True
dependencies = [
]
operations = [
# 0001_initial
migrations.CreateModel( ... ),
# 0002_custom_migrations
migrations.RunPython(
code=app.migrations.0002_custom_migrations.combine_names,
),
# 0003_profile_name
migrations.AddField(...),
]from django.db import migrations, models
def combine_names(apps, schema_editor):
pass
class Migration(migrations.Migration):
replaces = [('app', '0001_initial'), ('app', '0002_custom_migrations'), ('app', '0003_profile_name')]
initial = True
dependencies = [
]
operations = [
# 0001_initial
migrations.CreateModel( ... ),
# 0002_custom_migrations
migrations.RunPython(
code=combine_names,
),
# 0003_profile_name
migrations.AddField(...),
]model migrations์ ์คํํ ๋ ์๋์ผ๋ก system์ ์ํด์ ๋ค๋ค์ง์ง ๋ชปํ๋ ๊ฒ๋ค์ ์ํด ์ฌ์ฉ๋๋ค.
customField ๋ฅผ ์์ฑํ๋ฉด, migrations์ ํด๋น ํ๋๋ฅผ system์ด ์ฝ์ง ๋ชปํด ์๋ฌ๊ฐ ๋๊ฒ ๋๋ค.
๋ฐ๋ผ์ ํด๋น ํ๋๋ฅผ ์์ฑํ๊ณ , System์ด ์ฝ์ ์ ์๊ฒ Customํ๋๋ฐ deconstruct() ๋ Django๊ฐ customํ class instance๋ฅผ serializeํ๋ ๊ฒ์ ๊ทธ๋ง ๋๊ฒ ํ๋ ๊ฒ์ด๋ค.
์ด ํจ์๋ ์ด๋ค argument๋ ์๊ณ , path, args, kwargs ๋ฅผ ๋ฐํํ๋ค.
(๋ง์ฝ superclass์ default value๋ฅผ ์ธํ ํ๋ค๋ฉด, ์ด default value๋ ํญ์ ํฌํจ๋์ด์๋ค๋ ๊ฒ์ ๊ณ ๋ คํด์ผํ๋ค.)
์๋ฅผ ๋ค์ด
class ExclusiveBooleanField(models.BooleanField):
def __init__(self, on=None, *args, **kwargs):
if isinstance(on, string_types):
on = (on, )
self._on_fields = on or ()
super(ExclusiveBooleanField, self).__init__(*args, **kwargs)
def deconstruct(self):
name, path, args, kwargs = super(
ExclusiveBooleanField, self).deconstruct()
if self._on_fields:
kwargs['on'] = self._on_fields
return name, path, args, kwargs์์์ฑ์ด๋, Transaction์ด ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ชจ๋ ๋ฐ์๋๋๊ฐ, ์๋๋ฉด ์ ํ ๋ฐ์๋์ง ์์์ผ ํ๋ค๋ ๊ฒ์ด๋ค.
Postgresql, SQLite๋ ๋ชจ๋ DDL Transaction ์ ์ง์ํ๋ค.
DDL(Database Definition Language) Transaction ์ DDL์ ์งํํ๋ ๋ช
๋ น์ด CREATE, ALTER, DROP, RENAME, TRUNCATEฬ ์ ๋ช
๋ น์ด๋ฅผ Transactionํ ์ ์๋ ๊ธฐ๋ฅ์ด๋ค.
Transaction์ด๋ ์ฌ๋ฌ ๋ช ๋ น์ด๋ฅผ ํ๋ฒ์ ์ฒ๋ฆฌํ๊ณ , ๋ง์ฝ ํ๋ฒ์ ๋์ง ๋ชปํ๋ค๋ฉด, Rollback์ ํ๋ ๊ธฐ๋ฅ์ ์๋ฏธํ๋ค.
DDL Transaction์ Postgresql์ด ์ง์ํ๋ ์ด์ ๋ Write-ahead Log ๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ด๋ค.
Write-Ahaed Log๋ Database์ ์์ฑํ๊ธฐ ์ด์ ์ Log๋ฅผ ์์ฑํด ๋๋ ๊ฒ์ ์๋ฏธํ๋ค.
๋จ DROP ๊ณผ ๊ฐ์ ๊ฒ๋ค์ Rollback ์ด ๋ถ๊ฐ๋ฅ ํ๊ธฐ ๋๋ฌธ์ Transaction์ด ๋ถ๊ฐ๋ฅํ๋ค.
import uuid
from django.db import migrations, transaction
# Model์ ๊ฐ๋ค์ Chunk๋ก ๋๋ ์ update
def gen_uuid(apps, schema_editor):
MyModel = apps.get_model('myapp', 'MyModel')
while MyModel.objects.filter(uuid__isnull=True).exists():
# atomic์ ์ ์ง
with transaction.atomic():
for row in MyModel.objects.filter(uuid__isnull=True)[:1000]:
row.uuid = uuid.uuid4()
row.save()
class Migration(migrations.Migration):
# DDL Transaction์ ์ง์ํ๋ ์๋ DB์๋ ์ ์ฉ๋์ง ์๋๋ค.
atomic = False
operations = [
migrations.RunPython(gen_uuid),
]class Migration(migrations.Migration):
run_before = [
('third_party_app', '0001_do_awesome'),
]from django.apps import apps as global_apps
from django.db import migrations
def forwards(apps, schema_editor):
try:
OldModel = apps.get_model('old_app', 'OldModel')
except LookupError:
# The old app isn't installed.
return
NewModel = apps.get_model('new_app', 'NewModel')
NewModel.objects.bulk_create(
NewModel(new_attribute=old_object.old_attribute)
for old_object in OldModel.objects.all()
)
class Migration(migrations.Migration):
operations = [
migrations.RunPython(forwards, migrations.RunPython.noop),
]
dependencies = [
('myapp', '0123_the_previous_migration'),
('new_app', '0001_initial'),
]
if global_apps.is_installed('old_app'):
dependencies.append(('old_app', '0001_initial'))๊ธฐ๋ณธ์ ์ผ๋ก True๋ก ์ค์ ์ด ๋์ด์๊ณ , True์ ์๋ฏธ๋ Django๊ฐ Database Table์ lifecycle์ ๊ด๋ฆฌํ ์ ์๋ค๋ ์๋ฏธ์ด๋ค.
False๋ผ๋ฉด, database tabel ์์ฑ ๋๋ ์ญ์ ๋ฅผ ์งํํ ์ ์๋ค.
์๋ฅผ ๋ค์ด automatic primary key field๋ฅผ model์ ์์ฑํ๋๋ฐ, ๋ง์ฝ ์ ์ํด ๋์ง ์์๋ค๋ฉด, ๋ค์ ๊ฐ๋ฐ์๊ฐ ํท๊ฐ๋ฆด ๊ฒ์ ํผํ๊ธฐ์ํด, Database์ ๋ชจ๋ Column ๋ช ์ ์ ์ด๋์ง๋ง ์ ์ฉ์ ์ํค์ง ์๋๋ค.