(Django ORM) 이미 생성되어져 있는 모델에서 UUID 새롭게 추가하기

1. UUID

Django로 개발을 하던 도중, 지금까지는 그냥 AutoField를 PK로 작업해왔지만, uid를 활용해 개발해야 할 필요성을 느꼈다. 이유는 AutoField 자체가 너무 추적당하기 쉽고, jwt에 인증 이외의 정보를 포함하는 것은 잘못됐다는 사실을 알아버렸기 때문이다..

먼저 UUID는 Universally Unique IDentifier의 약자이고, 말 그대로 Universally 하게 Unique한 식별자이다. 128비트 식별자인 만큼 향후 약 1400년 동안 겹칠 일이 없다고 한다.

어쩌고저쩌고 여러 가지 방법이 있지만, 내가 사용하는 Python과 Django에서는 그냥 uuid를 import한 후 uuid.uuid4()를 해버리면 끝난다.

어차피 Unique가 보존되지만, 기존에 있는 모델에 필드를 추가하려고 하는 것이기 때문에 unique=True를 둔 후 야무지게 migrate를 돌려보자.

1
2
3
4
5
6
7
8
9
import uuid
 
class User(AbstractBaseUser, PermissionsMixin):
    uid = models.UUIDField(
        verbose_name='유저의 uid',
        unique=True,
        default=uuid.uuid4,
        editable=False
)
cs


귀신같이 아래의 문제가 발생한다.

2. 문제 사항

1
django.db.utils.IntegrityError: UNIQUE constraint failed:
cs


기존에 있는 필드에 unique=True를 박아버렸지만 위와 같이 입력하고 migrate 시 모두 같은 값의 uuid가 들어가게 되기 때문에,
-> unique=True가 위배되어 Integrity Error가 발생하게 되는 것이다 . . .

문제를 해결해야 하니, 먼저 python manage.py makemigrations 이후 migrations 폴더에 생성된 마이그레이션 파일을 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from django.db import migrations, models
import uuid
 
 
class Migration(migrations.Migration):
 
    dependencies = [
        ('users''(상위 마이그레이션 파일 이름)'),
    ]
 
    operations = [
        migrations.AddField(
            model_name='user',
            name='uid',
            field=models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='유저 uid'),
        ),
    ]
 
 
cs


ORM의 입장에서는 default=uuid.uuid4가 야무지게 박혀있으므로 그냥 저 값으로 모든 필드를 통일시켜버린다. 따라서 이 부분을 처리하지 못한 우리(나)에게 원인이 있다.

저 부분을 고쳐보자. Migration class 내부에 새로운 def를 만들어 for loop를 돌려 새로운 uuid를 만든 후 일일히 집어넣어 주면 쉽게 해결할 수 있다.

물론 해당 모델의 레코드 수가 수천 건 이상 넘어간다면 그닥 효율적인 방법은 아니다. 하지만 나는 테스트 DB에 ADD, ALTER 명령어를 수행하는 것이었고, 딱히 퍼포먼스에 문제가 없어서 아래의 방법을 사용했다.

실 운영중인 DB에 ADD나 ALTER를 날린다는건,,, 생각만 해도 아찔하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from django.db import migrations, models
from django.contrib.auth import get_user_model
import uuid
 
User = get_user_model()
 
 
class Migration(migrations.Migration):
 
    dependencies = [
        ('users''(상위 마이그레이션 파일 이름)'),
    ]
 
    def gen_uuid(apps, schema_editor):
        for row in User.objects.all():
            row.uid = uuid.uuid4()
            row.save()
 
    operations = [
        migrations.AddField(
            model_name='user',
            name='uid',
            field=models.UUIDField(default=uuid.uuid4, verbose_name='유저 uid' ),
        ),
        migrations.RunPython(gen_uuid),
 
        migrations.AlterField(
            model_name='user',
            name='uid',
            field=models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='유저 uid')
        )
    ]
 
 
cs


  1. 14 ~ 17번째 줄의 def는 해당 모델 전체(.all())를 iterate 한 후 uuid를 생성해 DB save()를 진행하는 부분이고,
  2. 20 ~ 23번째 줄은 ADD 명령어에서 unique 옵션을 제거한 후의 코드이다.
  3. 25번째 줄에서 정의해 둔 gen_uuid 메소드를 실행했고,
  4. 27 ~ 30번째 줄은 uuid save() 이후 unique=True 옵션을 다시 넣어주는 부분이다.

이렇게 migration 파일을 수정한 후 다시 python manage.py migrate를 진행하면 모든 유저의 uid 필드값에 정상적으로 uuid가 들어가게 된다.

3. 마무리

이후 admin에 해당 필드를 추가한 후 확인해 보니, 아래와 같이 정상적으로 적용된 것을 확인해볼 수 있다.

result image

하도 ADD랑 ALTER 삑날때마다 DB를 갈아엎고 나니, 이젠 ORM을 잘 다뤄 원하는 SQL 명령어를 잘 날릴 수 있게끔 하고 싶다는 목표가 생겼다. SQLD도 빨리 따야지..

댓글남기기