Two Scoops of Django | 쿼리와 데이터베이스 레이어

Jihun Kim·2022년 2월 7일
0
post-thumbnail

이 글은 모범 사례로 배우는 Django 테크닉에 관한 명서 "Two Scoops of DJango"(대니얼 로이 그린펠드, 오드리 로이 그린펠드 저, 2016)를 읽고 요약 정리하여 작성한 것입니다. 이 책을 읽고 새로웠던 것, 기존에 잘 알지 못했던 것을 위주로 작성했습니다.



예외를 일으킬 수 있는 쿼리 주의하기

ObjectDoesNotExist & DoesNotExist

  • ObjectDoesNotExist는 어떤 모델 객체에도 이용이 가능하다. 그러나, DoesNotExist는 특정 모델에서만 이용할 수 있다.
    - 즉, objects에 대해 get으로 접근하는 경우 특정 모델을 가져오게 되는데 이 때 해당 객체가 없다면 DoesNotExist를 사용할 수 있다.
    try:
    	return Product.objects.get(id=1234)
    except Product.DeosNotExist:
    	raise OutOfStock(해당_재고가_없습니다)

여러 개의 객체가 반환될 경우

  • 쿼리가 하나 이상의 객체를 반환할 수도 있다면 MultipleObjectsReturned 예외를 이용할 수 있다.
    - 위에 사용한 예시에서 예외 케이스를 추가할 수 있다. 여러 개의 객체가 반환되었다는 말은 데이터베이스에 이상이 있다는 뜻이다.

    try:
    	return Product.objects.get(id=1234)
    except Product.DeosNotExist:
    	raise OutOfStock(해당_재고가_없습니다)
    except Product.MultipleObjectsReturned:
    	raise CorruptedDatabase(데이터베이스의_이상작동)


지연 연산(Lazy Loading)

지연 연산은 데이터가 정말로 필요하기 전까지 장고가 SQL을 호출하지 않는 특징을 가리킨다. - 이에 대한 자세한 설명은 김성렬님의 PyCon 강의를 참고하는 것이 좋다.

여기에도 정리해 두었다.

  • 우리가 결과를 실행하기 전까지 장고는 실제 데이터베이스에 연동되지 않는다.
  • 따라서, 한 줄에 여러 메서드와 데이터베이스의 각종 기능을 엮어 넣는 대신, 여러 줄에 걸쳐 나눠 쓰는 것이 좋다.
    - 가독성을 높일 수 있고 유지보수가 수월해진다.


고급 쿼리 도구 이용하기

데이터베이스는 데이터 관리와 가공에서 파이썬보다 월등히 빠르다. 따라서, 데이터를 호출한 뒤에 다시 한 번 더 파이썬으로 데이터를 가공하는 것이 과연 옳은 일일까?

  • 파이썬을 이용해 데이터를 가공하기 전에 장고의 고급 쿼리 도구들을 이용해 데이터베이스를 통한 가공을 시도하는 것이 좋다.
    - 이렇게 하면 성능 향상 뿐만 아니라 더 잘 테스트 되어 나온 코드를 이용할 수 있게 된다.

쿼리 표현식을 이용하지 않은 나쁜 예제

from models.products import Product

products = []
for product in Product.objects.iterate():
	if product.ordered > product.left:
    	products.append(product)
  • 이 예제는 반복문을 이용하고 있으며 매우 느리고 많은 메모리를 사용하게 된다.
  • 이 경우 코드 자체가 공유 자원에 대해 여러 개의 프로세스가 동시에 접근을 시도하는 상태인 "경합 상황"에 직면하게 된다.
    - 해당 반복문이 실행 되는 중에 UPDATE 쿼리가 처리되는 환경이라면 데이터 분실 여지가 있다.
  • 따라서, 아래의 쿼리 표현식을 사용해야 한다.
    - 아래 코드는 데이터베이스 자체 내에서 해당 조건을 비교한다.
    from django.db.models import F
    from models.products import Product
    
    products = Product.objects.filter(ordered__gt=F('left'))


로우 SQL 이용하기

ORM은 단순한 쿼리 작성 뿐만 아니라 모델에 대한 접근과 업데이트시 유효성 검사와 보안을 제공한다는 장점이 있다. 그러나, 로우 쿼리를 이용할 경우 ORM을 이용하는 경우보다 코드가 훨씬 간결해지고 단축된다면 로우 쿼리를 이용하자.

  • 가령, 큰 데이터 세트에 적용되는 다수의 쿼리세트가 연동되는 경우라면 로우 SQL을 이용해 더욱 효과적으로 처리가 가능하다.
  • 단, 제이콥 캐플런모스에 의하면 "extra()"의 이용은 자제하고 "raw()"를 이용하는 것이 좋다.


인덱스 이용하기

  • 처음에는 인덱스 없이 시작하는 것이 좋으며 필요에 따라 하나씩 추가하도록 하자.
  • 필드 속성에 db_index=True를 추가하면 된다.

인덱스가 필요한 경우

  • 인덱스가 빈번하게 이용될 때
  • 실제 데이터 또는 실제와 비슷한 데이터가 존재해 인덱싱 결과에 대한 분석이 가능할 때
  • 인덱싱을 통한 성능 향상이 가능할 때
    - PostgreSQL의 pg_stat_activity를 이용하면 실제로 이용되는 인덱스들을 확인할 수 있다.


트랜잭션

  • 트랜잭션은 둘 또는 그 이상의 데이터베이스 업데이트를 단일화된 작업으로 처리하는 기법을 말한다.
  • 하나의 수정 작업이 실패하면 트랜잭션 상의 모든 업데이트가 실패 이전 상태로 복구되는데 이를 롤백이라 하며, 트랜잭션은 롤백 또는 커밋의 상태로 존재한다.
  • 장고는 1.8 이후부터 ORM이 모든 쿼리를 호출할 때마다 자동 커밋 된다.
  • 그런데, 뷰에서 둘 또는 그 이상의 DB 수정이 요구될 때 첫 번째 수정은 문제가 없었지만 두 번째 수정에서 문제가 발생해 데이터베이스상의 충돌이 일어날 위험이 발견되었다.
    - 이를 해결하기 위한 방법이 트랜잭션이다.

각 HTTP 요청을 트랜잭션으로 처리하라

  • 이 책에서는 settings의 DATABASES 설정에 ATOMIC_REQUESTS=True 설정을 추가할 것을 권장한다.
  • 이를 통해 발생할 수 있는 결과는 두가지가 있을 수 있다.
    1) 뷰에서의 모든 DB 쿼리가 보호되는 안전성
    2) 성능 저하
    - 성능 저하의 정도는 각 데이터베이스 디자인과 locking을 얼마나 잘 처리하느냐에 달려 있다.
  • 에러가 발생하고 나서야 데이터베이스 상태가 롤백된다는 점은 주의해야 한다.
    - 이에 대한 대응책으로, 데이터베이스가 아닌 아이템에 대해 데이터를 생성/변경.삭제 하는 뷰를 만들 때는 해당 뷰를 transaction.non_atomic_requests()로 데코레이팅 하는 것이 좋다.
    from django.db import transaction
    from django.utils import timezone
    from products.models import Product
    
    @transaction.non_atomic_requests
    def posting_product_status(requests, pk, status):
    	product = Product.objects.filter(pk=pk).first()
        
        # 오토 커밋(장고 기본 설정)
        product.latest_status_change_attempt = timezone.now()
        product.save()
        
        with transaction.atomic():
        	# 이 코드는 트랜잭션 안에서 실행 된다.
            product.status = status
            product.latest_status_change_success = timzone.now()
            product.save()
            
            return Response(status=status.HTTP_200_OK, data=data)

명시적인 트랜잭션 선언

  • 데이터베이스에 변경이 생기지 않는 데이터베이스 작업은 트랜잭션으로 처리하지 않는다.
  • 데이터베이스에 변경이 생기는 작업은 반드시 트랜잭션으로 처리한다.
  • 독립적인 ORM 메서드 호출을 트랜잭션으로 처리하지 말 것
    - 장고의 ORM은 데이터의 일관성을 위해 내부적으로 트랜잭션을 이용하고 있다. 따라서, 독립적인 ORM 메서드인 create(), update(), delete()를 트랜잭션으로 처리하는 것은 유용하지 않다.
    • 여러 ORM 메서드들을 뷰나 함수 또는 메서드 내에서 호출할 때 사용하는 것이 좋다.



참고자료
Two Scoops of DJango"(대니얼 로이 그린펠드, 오드리 로이 그린펠드 저, 2016)

profile
쿄쿄

0개의 댓글