Django | ORM과 QuerySet

Sua·2021년 3월 1일
2

Django

목록 보기
21/23
post-thumbnail
post-custom-banner

ORM(Object Relation Mapper)

  • 관계형 데이터베이스의 내용을 객체로 변환해서 어플리케이션 소스코드에서 직접 객체를 조작할 수 있도록 구성.
  • ORM을 사용해서 개발자는 데이터베이스를 조작하는데 SQL을 사용하지 않고 직접 객체를 사용할 수 있다.
  • database의 table은 ORM에서 model로 표현되고 record는 object로 표현된다.

장점

  • 기존 쿼리문을 작성하는 것보다 효율적이고 가독성이 좋고, 유지 보수에 적합하다.
  • Django에 구현된 각 RDBMS 별 wrapper 를 통해 RDBMS 의 종류가 어떤 것인가에 상관없이 만들 수 있다.
  • 직관적인 객체지향 프로그래밍이 가능하다.
  • 기존의 DB 기반 구성을 객체 기반 구성으로 확장하여, 컴포넌트를 조합하는 방식의 개발이 가능하다.

단점

  • SQL 구문의 생성을 추상화하여 구현 하였으므로, 복잡한 쿼리의 경우 비효율적으로 SQL 구문이 생성될 수 있다.

QuerySet

  • 전달받은 모델의 객체 목록. 데이터베이스의 여러 레코드(row)를 나타낸다.
  • 쿼리셋은 데이터베이스로부터 데이터를 읽고, 필터를 걸거나 정렬을 할 수 있다.

When QuerySets are evaluated

QuerySet 객체는 평가되기(Evaluated) 전 까지 데이터베이스에 직접 영향을 주지 않는다. QuerySet에 필터링을 하여 또 다른 QuerySet을 얻을 수 있지만, QuerySet이 평가되기 전까지 절대 데이터베이스 Hit를 하는 일이 없다. QuerySet이 평가될 때, SQL 쿼리를 데이터베이스에서 실행하게 된다.

다음과 같은 경우에 QuerySet은 평가된다.

  • 쿼리셋을 순회(iterate)할 때
  • "Step" Slicing할 때 ex)User.objects.all()[:10:2]
  • Pickling/Caching
  • repr(), len(), list()를 호출할 때,
  • bool(), or, and, if 등으로 쿼리셋을 검사하는 경우

예시

people = Preson.objects.filter(age=20)

위와 같이 filter를 걸면 조건에 맞는 로우를 검색 할 수 있는데 이 쿼리셋에 filter를 추가하거나 함수에 전달해도 이는 DB에 아무런 메시지도 전달하지 않는다.

for person in people:  
    print(person.name)
for person in people:  
    print(person.job)    

쿼리셋을 순회하는 시점에, 쿼리셋에 해당하는 DB의 레코드들을 실제로 가져오며(fetch), 이는 모두 Django 모델로 변환된다. 이를 가리켜 evaluation이라고 한다.
평가된 모델들은 쿼리셋의 내장 캐시에 저장되며, 덕분에 위와 같이 쿼리셋을 다시 순회하더라도 똑같은 쿼리를 DB에 다시 전달하지 않는다.

QuerySet을 통해 알아보는 ORM의 특징

아래의 내용은 PyCon Korea 2020에서 김성렬님이 강의한 Django ORM(QuerySet) 구조와 원리 그리고 최적화 전략 영상을 참고했다.

1) Lazy Loading 지연로딩 : 정말 필요한 시점에 SQL을 호출한다.

# User를 선언하는 시점에는 SQL이 호출되지 않는다.
users = User.objects.all()

# 이 코드에서 실제로 SQL이 호출된다.
list(users)

2) Lazy Loading 지연로딩 : 정말 필요해야만 SQL을 호출한다.

# 이 코드에서는 SQL이 한 번만 호출된다. 
# Order QuerySet과 Company QuerySet을 선언했지만 사용하지 않아서 SQL이 호출되지 않는다.
users = User.objects.all()
orders = Order.objects.all()
companies = Company.objects.all()
list(users)

3) Lazy Loading 지연로딩 : 정말 필요한 만큼만 호출한다.

정말 필요한 만큼만 호출하려는 ORM의 특성 때문에 비효율적으로 ORM이 동작하기도 한다.

users = User.objects.all()

# 0번째 User를 얻고 싶어서 user QuerySet은 SQL을 호출한다.
first_user = users[0]

# 바로 윗줄에서 user 1명 밖에 가져오지 않아서 모든 user를 얻으려면 어쩔 수 없이 다시 SQL을 호출해야 한다. 
user_list = list(users)

4) Caching : QuerySet 캐싱을 재사용하는 법(3번 해결책)

쿼리셋을 호출하는 순서가 바뀌는 것만으로도 QuerySet 캐싱 때문에 발생하는 SQL이 달라질 수 있다.

users = User.objects.all()

user_list = list(users)

# 위에서 user 쿼리셋이 모든 user를 가져오는 SQL을 이미 호출함. 따라서 0번째 user 쿼리셋에 캐싱된 값을 재활용함  
first_user = users[0]

5) Eager Loading 즉시로딩 : N+1 Problem

users = User.objects.all()

# 개발자 관점에서는 각 user의 모든 userinfo가 필요한 것을 알지만 QuerySet은 모른다.
for user in users:
    # QuerySet입장에서 user의 userinfo가 필요한 시점은 여기다.
    # 따라서 userinfo를 알기 위해 SQL을 for문이 돌 때마다 (N번) 호출한다.
    user.userinfo

모든 user를 조회하기 위해 SQL 1번, user의 userinfo를 매번 조회하기 위해 SQL을 N번 호출한다.
이러한 N+1 Problem을 해결하기 위해(즉시로딩을 하기 위해) Django에서는 select_related()와 prefetch_related()라는 메서드를 제공한다.

참고사이트
https://superminy.tistory.com/10
https://medium.com/deliverytechkorea/django-queryset-1-14b0cc715eb7
https://docs.djangoproject.com/en/3.1/ref/models/querysets/
https://velog.io/@ikswary/n1-query-problem

profile
Leave your comfort zone
post-custom-banner

0개의 댓글