Django 업그레이드

서론

Django 4.1 버전을 사용하다 5로 업그레이드 하며 조사했던 내용.

Django 업그레이드는 왜 해야 할까?

  1. 보안 패치
  2. 버그 패치
  3. 신기능 추가
  4. 라이브러리들의 예전 버전 지원 종료
  5. Python 버전 업데이트

마이너 버전만 해도 가끔 쫄릴때가 있는데 메이저 버전이야 더 그렇다. Django 는 무겁다는 얘기를 하는데 그런 면이 없잖아 있다. 그럼에도 불구하고 ORM 의 편의성이 압도적이고, 웹서버에 필요한 많은 것이 내장되어 있기 때문일 것이다. 그렇기에 보안, 버그 패치 때문에라도 업데이트는 꾸준히 따라가는게 좋다. 개인적으로는 4.1 버전에서 5로 업데이트 하고 싶었던 이유가 GenericPrefetch 5.0에서 도입된걸 알게 된 후였다. 후에 코딩으로 해결하긴 했지만… 현재 버전은 5.2까지 나왔기 때문에 가는 길이 험난했다.

업그레이드 전략

  1. 먼저 커버리지가 높은 테스트 코드가 작성되어 있어야 한다. 만약 DRF 도 사용 중이라면 API 에 대해서도 테스트 코드가 작성되어 있는 것이 좋겠다. 테스트 코드가 없거나 커버리지가 충분하지 않다면 테스트 코드부터 보강해야 한다.
  2. 업그레이드 가이드정독 한다.
  3. 목표로 하는 버전으로 requirements.txt 등을 변경해서 django 를 업그레이드 한다. 그런후 django-upgrade 를 실행해본다. 마이너 버전 1단계씩 확인해가며 반복적으로 업그레이드 단계를 진행하는 것을 추천한다.
  4. Release Note정독 한다. 특히 Backwards incompatible changes 항목은 매우 조심해야 한다. 4.2에서 save() 메소드가 변경되었는데 파급 효과에 대해 크게 생각하지 않았다가 꽤 삽질했던 기억이 생생하다. 귀찮겠지만 Backwards incompatible changes 항목을 전수조사 해야한다. django-upgrade 모듈은 많은 부분을 변경 해 주긴하는데 모든 변경 사항을 업데이트 해주거나 경고해주지 않으므로 꼭 전수조사가 필요하다.
  5. DB 와 연결 및 호환성 문제가 발생하는지 확인한다.

4.2 버전의 save() 메소드 변경점

QuerySet.update_or_create가 save() 메소드를 호출한다. 따라서 4.1->4.2에서 breaking change 가 발생할 수 있다.

소스 코드를 비교해보자.

1
2
3
4
obj, created = self.select_for_update().get_or_create(defaults, **kwargs)
if created:
return obj, created
....

생성 했을 때 까지는 공통코드가 유지된다. 그러면 업데이트 시 달라진다는 얘기. 그리고 save를 오버라이드하고 update_fields 를 save 안에서 수정하거나 하면 영향을 받을 수 있으니 post_save 시그널을 사용하거나 save 오버라이드 한게 있으면 테스트 케이스를 만들어서 정상 동작하는지 확인해야 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  obj.save(using=self.db)
---
update_fields = set(defaults) # 딕셔너리를 변환하면 key만 set으로 남는다.
concrete_field_names = self.model._meta._non_pk_concrete_field_names
# update_fields does not support non-concrete fields.
# 1.concrete_field 는 DB 에 저장되는 필드.(non abstract)
# 2.concrete_field_names 가 defaults 키를 모두 포함하고 있다면
if concrete_field_names.issuperset(update_fields):
# Add fields which are set on pre_save(), e.g. auto_now fields.
# This is to maintain backward compatibility as these fields
# are not updated unless explicitly specified in the
# update_fields list.
# 3. PK 가 아니고, pre_save() 가 오버라이딩 되지 않은 필드를 추가한다.
for field in self.model._meta.local_concrete_fields:
if not (
field.primary_key or field.__class__.pre_save is Field.pre_save
):
update_fields.add(field.name)
if field.name != field.attname:
update_fields.add(field.attname)
# 4. update_fields 를 지정해서 save() 를 호출한다.
obj.save(using=self.db, update_fields=update_fields)
else:
obj.save(using=self.db)

테스트 및 배포 전략

  1. QA 와 잘 협의해본다.
  2. 다수의 서버를 사용한다면 canary 배포를 활용한다.
  3. 테스트 용 환경을 하나 만들고, 입력된 모든 리퀘스트를 테스트 서버에 그대로 보내는 방법도 고려해 볼 수 있다.

그 외

  1. 4.1 에서 업그레이드 할 때는 save 변경점에 대해 주의해야 한다. post_save 시그널이 있는지도 확인 필요하다. 우리팀은 update_fields 는 보통 list 를 사용했는데, django 코드에서는 set 을 사용해서 update_fields 는 set 을 파라미터로 사용 하는 것을 추천한다.