Django DB 액세스, 쿼리 최적화

강현구·2022년 6월 2일
0

Django

목록 보기
12/12

Django와 같은 ORM을 사용하여 DB에 접근하다 보면 불필요한 DB의 접근이 많이 발생할 수 있다.
QuerySet의 특징으로 인해서 성능 문제가 발생할 수 있는데, 이를 이해하고 효율적인 처리를 하도록 최적화하는 과정은 필수적이다.

DB의 액세스와 쿼리를 최적화

를 하기 위해서는 실제로 어떻게 실행되고 있는지를 확인하는 것이 필요하다.
다양한 툴이 있지만, QuerySet.explain()을 사용할 수도 있고, django-debug-toolbar와 같은 라이브러리를 활용하는 방법도 있겠다.

1. 보편적인 DB 최적화 기법

  • 인덱스 활용 : django에서 filter(), exclude(), order_by() 등을 사용하여 자주 쿼리하는 필드에 인덱스를 추가할 수 있다. 인덱스를 사용하면 조회속도를 높이는데 도움이 된다. 하지만, 남발할 경우에는 인덱스 유지 비용이 더 들 수 있기 때문에 적절히 사용해야한다.
  • 적절한 필드 타입의 사용.

2. QuerySet의 이해

2-1. QuerySet의 계산

django의 성능 이슈와 관련해서 다음을 이해하는 것이 중요하다.

  • Lazy Query

    • django는 실제로 필요하기 전까지는 DB에 접근하지 않는다.
  • QuerySet의 계산 시점

    • Iteration, Slicing, Pickling/Caching, repr(), len(), list(), bool()
      등의 시점에서 QuerySet이 실제로 계산된다.
  • 데이터 캐싱

    • 각각의 QuerySet은 DB 접근을 최소화하기위한 캐시가 포함되어 있다. 이를 통해 효율적인 코드 작성을 할 수 있다.

2-2. 캐시된 속성(attribute) 이해하기

QuerySet 캐싱 뿐만 아니라 ORM 객체에 대한 캐싱도 있다.
일반적으로 호출할 수 없는 attribute라면 캐싱된다.
예를들어 모델 객체의 인스턴스에서 필드값을 attribute로 호출하면 이것은 캐싱된다.
하지만 일반적으로 호출하는 all()과 같은 속성은 매번 DB를 히트한다.

이외에도

  • with 템플릿 태그 사용
  • iterator(), explain()을 사용하는 방법들이 있다.

3. DB에서 작동하는 경우

  • filter() or exclude()를 사용하여 DB에서 필터링
  • F표현식으로 같은모델의 다른 필드를 기반으로 필터링
  • DB에서 aggregation하기 위해 annotation

과 같은 경우에 SQL의 생성이 적절하지 않다면 RawSQL class로 일부 SQL을 명시적으로 넣거나, raw SQL로 완전히 SQL을 사용할 수도 있다.

4. unique하거나 인덱스된 열을 사용한 객체 검색

get()을 사용하여 개별 객체를 검색할 때 unique 또는 db_index열을 사용하는데 두가지 이유가 있다.

  1. DB 인덱스로 쿼리 속도가 더 빠르다.
    또한 여러 객체가 조건과 일치하면 쿼리가 훨씬 느릴 수 있다.
    때문에 열에 고유한 조건이 있다면 이러한 현상이 일어나지 않을 것이고, get()을 사용할 때는 unique, db_index 된 열을 사용한다.

  2. 조회는 단 하나의 객체가 반환될 것이라고 보장하지 않는다.
    쿼리가 두개 이상의 객체와 일치하면 DB에서 모든 객체를 검색하여 전송한다.
    데이터가 많다면 이로 인한 성능저하는 더욱 심각해 질 것이다.

>> 다시말해 name = "str"의 형태 보다는 id = 000 의 형태가 속도가 빠르다는 것!
>> 인덱싱도 되어있고, 고유한 값임을 알고 있기 때문.

5. 필요한 항목은 즉시 조회

일반적으로 데이터 집합의 여러 부분에 대해 DB를 여러번 히트하는 것은 하나의 쿼리에서 모두 검색하는 것 보다 효율적이지 않다. 특히나 이런 상황은 루프에서 실행되는 쿼리의 경우에 특히 중요하다(N+1)

select_related() 또는 prefetch_related()를 이해하고 사용하자.
manager의 사용여부를 숙지해야한다.

6. 불필요 항목은 검색하지 않는다.

QuerSet.values() 및 values_list()

dict 혹은 list의 값을 원할 때, ORM 모델 객체가 필요하지 않은 경우에는 values()를 적절히 사용한다.

QuerySet.defer() 및 only()

DB의 열이 필요하지 않다는걸 알고 있거나, 로드되지 않도록할 때 사용한다. 하지만 부적절하게 사용시 ORM이 별도로 쿼리를 날리게될 수 있다.

QuerySet.count()

개수만 원할 경우 len(queryset)보다 빠르다.

QuerySet.exists()

적어도 하나의 결과가 존재하는지에 대한 확인, if 문보다 적절하다.

QuerySet.update() 및 delete()

개별적으로 저장하는 대신 대량 SQL UPDATE문을 사용한다. 삭제일 경우에도 동일.
그러나 일괄 업데이트 메서드는 개별 인스턴스의 save(), delete()를 호출할 수 없다.

외래키 값 직접 사용.

entry.person.id (x)
entry.person_id (o)

7. 일괄 삽입.

가능한 경우 bulk_create()를 사용하여 쿼리 수를 줄인다.

출처 : https://blog.myungseokang.dev/posts/database-access-optimization/

profile
한걸음씩

0개의 댓글