[Django] ORM 구조와 원리 (1)

WooJin Chung·2022년 6월 29일
0

Django

목록 보기
1/2

Django를 공부하다보면 ORM이라는 개념이 등장한다.
ORM을 찾아보면 항상 객체와 관계를 연결해준다는 등의 설명이 먼저 나오는데 비전공자의 입장에서는 조금 이해하기 어려운 개념이었다.
이번 포스팅을 통해 django의 ORM에 대해서 정리해보고자 한다.

ORM이란?


사실 ORM은 말 그대로 객체(object)와 관계(relational)의 연결(mapping)이다.

ORM의 개념을 이해하기 위해서는 먼저 SQL에 대해서 알아야 한다.
SQL이란 Structure Query Language의 줄임말로 데이터베이스와 대화할때 사용하는 언어라고 생각하면 된다.
이를 콘솔에 작성하는 것을 SQL statement라고 하고 다음은 SQL문의 예시이다.

SELECT * FROM users WHERE country="korea" ORDER BY created_date

파이썬으로 데이터베이스에 접근하기 위해서는 SQL문을 작성하고 이를 연동해 주는 과정이 필요하고, Django는 파이썬 기반의 프레임워크이다.
원래대로라면 두가지 언어를 사용해야 하는 이 상황을 장고에서는 ORM을 통해 해결한다.
한마디로 장고 ORM을 파이썬과 SQL사이의 통역사라고 표현할 수 있다.
예를들어, 위의 SQL 예문을 다음과 같은 파이썬 언어로 표현할 수 있게 해준다.

User.objects.filter(country="korea").order_by('created_date')

그렇다면 ORM에서 객체(O), 관계(R), 연결(M)이라고 표현하는 이유는 뭘까?
말 그대로 객체 기반 언어(Object Oriented Programming)와 데이터베이스 관리 시스템(Relational Database Management System)을 연결(mapping)해 주는 시스템이기 때문이다.

ORM의 특징


Django의 ORM은 쿼리셋(Queryset)이라는 자료형을 사용한다.
이 쿼리셋으로부터 오는 ORM의 대표적인 세가지 특징은 다음과 같다.


1. Lazy loading

쿼리셋은 필요한 시점에만 SQL문을 호출하는 Lazy loading이라는 특징을 갖는다.
다음과 같이 users가 선언된 상황을 생각해 보자.

users = User.ojbect.all()

Lazy loading이라는 특징 때문에 이 구문이 실행되는 시점에서 users는 모델이 넘어온 것이 아니라 단순한 Queryset에 지나지 않는다.
이후 쿼리셋을 사용하여 연산하는 로직이 수행될 때 해당되는 SQL 문이 호출되는 것이다.
언뜻 보면 굉장히 똑똑하고 최적화된 알고리즘처럼 보인다.

하지만 다음과 같은 경우는 어떨까?

users = User.ojbect.all()

user_1 = users[0]
users_list = list(users)

user_1에서 users의 첫번째 요소를 호출했고, users_list에서 users에 담긴 User 모델을 리스트화했다.
이 과정 속에서 Queryset의 Lazy loading이라는 특징 때문에 users의 첫번째 요소가 두 번 호출된다.
지금 당장 필요한 만큼만 호출한다는 이 Lazy loading이 때론 비효율적으로 동작할 수 있는 것이다.


2. Caching
바로 위의 예시에서 한번의 쿼리로 해결 가능한 부분이 한번 더 호출되는 것을 확인했다.
그런데 이는 쿼리셋의 또다른 특징인 Caching을 통해 해결이 가능하다.
각각의 쿼리셋은 Database에 접근할 때 캐시 메모리를 포함하고 있다.
쿼리셋에서 SQL을 호출하면 그 데이터 결과 값은 캐시 메모리에 저장되고 이후 동일한 쿼리셋을 사용할 경우 추가적인 Query는 발생하지 않고 캐시에서 꺼내 사용한다.

따라서 위의 코드를 다음과 같이 순서를 바꿔주는 방식으로

users = User.ojbect.all()

users_list = list(users)
user_1 = users[0]

간단하게 SQL의 중복 호출을 방지할 수 있다는 것이다.
즉, 쿼리셋의 Caching을 이용할 수 있도록 비지니스 로직을 구성하는 것이 가격 및 성능 측면에서 굉장히 중요하다.

3. Eager Loading
쿼리셋은 기본적으로 Lazy loading 전략을 택한다.
하지만 SQL로 한번에 많은 데이터를 끌어오고 싶을 때가 존재할 것이고, 쿼리셋에서는 이를 Eager loading(즉시 로딩) 전략을 통해 이를 지원한다.

Eager loading의 필요성을 위해 Lazy loading에서 발생한 N+1 문제를 살펴보겠다.

users = User.ojbect.all()

for user in users:
	user.id

당연하게도 우리는 각 User의 모든 id가 필요한 것을 알지만 아쉽게도 쿼리셋은 알지 못한다.
따라서 user.id가 조회될때마다 SQL이 반복적으로 호출되는 문제가 생기고, 이러한 문제를 쿼리셋의 Eager loading을 통해 해결할 수 있다.
Eager loading에 대한 구체적인 내용은 다음 포스팅에서 다뤄볼 예정이다.



최대한 내용을 검토하면서 작성하지만 틀린 내용이 있을 수 있습니다. 댓글로 남겨주시면 감사하겠습니다.

profile
Student Studying medical A.I

0개의 댓글