1차 프로젝트를 진행하면서 Django를 이용한 상품 구매나 결제, 장바구니 기능 구현 글들을 많이 찾아봤다. 그 중 데이터의 sort나 aggregation같은 처리를 할 경우 유독
F()
로 데이터 Field를 감싸는 것을 많이 보게 되었다.F()
는 무슨 의미를 가지고 있고, 왜 사용하는 걸까?
Django 공식 문서를 살펴보니 Q()
, F()
와 같은 표현은 Query Expression이라고 하며, create
, update
, filter
, order by
, annotation
, aggreagate
의 일부로 사용할 수 있는 Value 혹은 Computation이다. 짧은 설명만 봐도 기능 구현에 큰 도움이 될 것 같아 그 중 가장 핵심이라고 하는F()
에 대해 알아봤다.
Django Documentation의 설명을 보면,
F()
object는 Model Field나 Annotate된 Column의 Value를 나타낸다. 실제로 데이터베이스에서 Python 메모리로 가져오지 않고, 모델 필드 값을 참조하고 이를 데이터베이스에서 사용하여 작업할 수 있다.
사실 이 글만 봐서는 이해가 잘 안간다. 주어진 Model Field의 값을 Python으로 가져오지 않고 직접 Database에서 처리한다는 것 같은데, 큰 의미가 있을까? Django Documentation의 예제를 살펴보자.
# Normal Condition
reporter = Reporters.objects.get(name='Tintin')
reporter.stories_filed += 1
reporter.save()
# Use F()
from django.db.models import F
reporter = Reporters.objects.get(name='Tintin')
reporter.stories_filed = F('stories_filed') + 1
reporter.save()
똑같이 Databse의 stories_field
값은 +1 처리 되었을 것이다. 그러나 F()
를 사용하면 실제로 코드에 보이는 것처럼 Python Operator를 사용한 것이 아니고, Python 연산자를 override 해서 캡슐화된 SQL문을 만든다.
이 작업은 Database 내에서만 이루어지므로, Python에서는 reporter.stories_Field
의 값을 알 수 없다. 아래 코드로 쉽게 이해해보자.
# 예시로 보자
product = Product.objects.get(name="Wecode")
product.price # product.price = 100
product.price += 1 # product.price = 101
product = Product.objects.get(name="Wecode")
product.price # product.price = 100
product.price = F('price') + 1 # product.price = <CombinedExpression: F(price) + Value(1)>
product.save()
product.price # product.price = <CombinedExpression: F(price) + Value(1)>
product.price.refresh_from_db() # 저장한 값을 사용하기 위해 사용해줘야 한다.
product.price # product.price = 101
이와 같이 실제로 python에서 연산하는 것이 아니라 db에서 연산하기 때문에 다시 product = Product.objects.get(name="Wecode")
로 선언해서 db로부터 재선언해주거나 refresh_from_db()
를 사용해 불러온 값을 refresh한다.
왜 굳이 F()를 사용하는걸까? F() Expression을 사용했을때의 장점을 몇가지 살펴보자.
1. Python으로 데이터를 가져오지 않고 Database에서 해당 연산을 처리한다.
2. 연산이 필요한 경우 Query 수를 줄일 수 있다.
products = Product.objects.all()
for product in products:
product.price *= 1.2
product.save()
# F() 사용
Product.objects.all().update(F('price') * 1.2)
3. Race Condition(경쟁조건)을 피할 수 있다.
python의 여러 thread에서 product.price
를 1씩 증가시킨다고 가정하자. 만약 python 연산자로 +=1
해줄때 1번 thread가 먼저 작업을 하고 2번 thread가 연산한다면 1번 thread의 작업은 손실되고 Database에는 그냥 +1로 계산할 것이다.
하지만 F()
를 사용하면 각 연산이 처리될 때 Database에서 연산을 하기 때문에 경쟁조건을 피할 수 있고, Database에는 의도한대로 +2로 연산될 것이다.
4. Sort, Annotate와 같은 연산에 유용하게 쓰인다.
company = Company.objects.annotate(
chairs_needed=F('num_employees') - F('num_chairs'))
위와 같이 anootate로 Model Instance의 다른 Field 값을 참조하여 연산을 통해 동적으로 Field를 추가해줄 수 있다. 이는 상품의 가격과 할인과 같은 연산을 할 때 유용할 것이다.
이번엔 Query Expression중 F()
만 블로깅했으나, 다음엔 Q()
와 같은 다양한 Expression들을 더 알아보겠다. 이외에도 Transaction, QueryString, RESTful API 등 개념을 이해해야 좋은 것들에 대해 블로깅해야겠다.
Why Use F()? 부분이 정말 좋네요! 항상 유용한 블로깅 감사드립니다. 👍🏼👍🏼👍🏼👍🏼👍🏼