몰입공간

[Django] F() expression으로 동시성 해결하기 (Using F expression to solve the concurrency on DB) 본문

Programming/Django

[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. 참고

 

Comments