몰입공간
[Django] F() expression으로 동시성 해결하기 (Using F expression to solve the concurrency on DB) 본문
[Django] F() expression으로 동시성 해결하기 (Using F expression to solve the concurrency on DB)
sahayana 2022. 5. 1. 23:07#1. F() expression
장고의 F() 객체는 SQL 쿼리의 '호출'을 파이썬 메모리에 가져오지 않고 순전히 데이터베이스 레벨에서만 처리하는
장고의 쿼리표현식 입니다.
즉, 모델의 필드값 조회나 변경을 데이터베이스 레벨에서 직접 연산할 수 있는 클래스입니다.
F() 객체를 사용하는 이점으로 쿼리 수를 줄이는 것, 효율적인 메모리 사용등이 있지만 아무래도 경쟁조건(Race condition)을 피해 동시성을 해결하는 것이 가장 핵심일 듯 합니다.
#2. 경쟁조건 (race condition)
제품을 나타내는 Product 모델이 있고, 그 제품의 주문수를 기록하는 필드를 따로 설정했다고 가정합니다.
일반적으로 주문의 횟수를 업데이트하는 방식은 다음과 같습니다.
product = Product.objects.get(name="iphone13")
product.order_count += 1
product.save()
만약, 주문 횟수를 업데이트하는 request가 '동시에' 10번 들어오면 어떻게 될까요?
각 요청을 담당하는 스레드는 DB에서 필드값을 파이썬 메모리로 불러오고 스레드간 연산이 얽혀 진행한 요청(DB저장)이 끝나기도 전에 다른 스레드에서 요청을 override합니다.
이것이 경쟁조건입니다.
(race condition은 단일 스레드에서는 적용할 수 없는 개념입니다. 파이썬은 GIL로 인해 일반적으로 single-thread라고 알려져 있으나, 장고 실행 서버의 경우 WSGI 미들웨어로 인한 multi-thread가 기본설정입니다. 참고)
#3. F() 객체 사용
경쟁조건은 F()객체를 사용함으로써 해결합니다.
DB에서 데이터를 가져와 조회하지 않고, 묻지도 않고 일단 값을 업데이트 합니다.
from django.db.models import F
# save(): 2 queries
product = Product.objects.get(name="iphone13")
product.order_count = F("order_count") + 1
product.save()
# update(): 1 query
product = Product.objects.update(order_count = F("order_count") + 1)
#4. F() 주의점
F() 객체를 통한 필드값 업데이트는 오직 save() 혹은 update()를 통해 갱신됩니다.
F() 객체를 통해 DB에서 업데이트한 값을 새로 조회하기 위해선 get() 이나 refresh_from_db() 메서드를 통해
SQL 쿼리를 호출해야 합니다.
product = Product.objects.get(name="iphone13")
# Or
product.refresh_from_db()
따라서 F() 객체를 통해 필드값을 업데이트하는 도중 갱신된 값을 새로 불러오지 않고 연속적으로 save()를 한다면
F() 객체는 묻지도 따지지도 않기 때문에 다시 값을 업데이트 합니다.
# wrong example
product = Product.objects.get(name="iphone13")
product.order_count = F("order_count") + 1
product.save()
product.name = "iphone13-PRO"
product.save()
2번의 save()로 order_count의 값은 +2로 업데이트 됩니다.
따라서 다시 save()를 해야한다면 값을 필수로 갱신해주어야 사고를 방지할 수 있습니다.
#5. 정리
- 장고의 F() expression은 모델의 필드값을 DB레벨에서 처리한다.
- F() 객체 사용으로 경쟁조건을 피할 수 있다.
- F() 객체를 사용한 필드값 업데이트는 save(), update() 메서드를 통해 적용된다.
- F() 객체를 사용하고 새롭게 갱신된 값을 조회하기 위해 get() 혹은 refresh_from_db() 메서드를 쓴다.
#5. 참고
- 공식문서 (https://docs.djangoproject.com/en/4.0/ref/models/expressions/#f-expressions)
- Django: save() vs update to update the database (https://stackoverflow.com/questions/30449960/django-save-vs-update-to-update-the-database)
'Programming > Django' 카테고리의 다른 글
[Django] 장고 ORM cookbook - 1 (Django ORM recipes) (0) | 2022.05.17 |
---|---|
[Django] QuerySet 특징 - Lazy evaluation (Lazy evaluation with Django QuerySet) (0) | 2022.05.06 |
[Django] Custom User모델과 이메일 로그인 (Custom User model and login by email) (0) | 2022.04.29 |
[Django] 토큰 인증 데코레이터 구현하기 (Decorator for token authentication) (0) | 2022.04.27 |
[Django] JWT 토큰 기반 인증 (authentication by JSON Web Token) (0) | 2022.04.19 |