(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 |
- 14 ~ 17번째 줄의 def는 해당 모델 전체(.all())를 iterate 한 후 uuid를 생성해 DB save()를 진행하는 부분이고,
- 20 ~ 23번째 줄은 ADD 명령어에서 unique 옵션을 제거한 후의 코드이다.
- 25번째 줄에서 정의해 둔 gen_uuid 메소드를 실행했고,
- 27 ~ 30번째 줄은 uuid save() 이후 unique=True 옵션을 다시 넣어주는 부분이다.
이렇게 migration 파일을 수정한 후 다시 python manage.py migrate를 진행하면 모든 유저의 uid 필드값에 정상적으로 uuid가 들어가게 된다.
3. 마무리
이후 admin에 해당 필드를 추가한 후 확인해 보니, 아래와 같이 정상적으로 적용된 것을 확인해볼 수 있다.
하도 ADD랑 ALTER 삑날때마다 DB를 갈아엎고 나니, 이젠 ORM을 잘 다뤄 원하는 SQL 명령어를 잘 날릴 수 있게끔 하고 싶다는 목표가 생겼다. SQLD도 빨리 따야지..
댓글남기기