ORM은 Object Relational Mapping의 약자로
객체와 관계형 데이터베이스를 자동으로 매핑해 주는 기술을 말한다.
즉 객체를 통해 데이터베이스를 다룰 수 있게 해 주는 것을 ORM이라고 부른다.
ORM의 장단점을 살펴보면 아래와 같다.
장점
단점
ORM에서는 명령을 실행할 때마다 데이터베이스에서 데이터를 가져오는 것이 아니라
모든 명령 처리가 끝난 후 실제로 데이터를 불러와야 할 시점이 되었을 때
쿼리를 실행하게 된다.
쿼리 1번으로 N건의 데이터를 가져왔다고 할 때
여기에서 원하는 데이터를 얻기 위해 N건의 데이터를 데이터의 수만큼 반복해 2차적인 쿼리가 발생하는 상황을 N+1 문제라고 한다.
Django의 예를 들어보면,
Django 웹 프레임워크는 ORM을 사용하는데 Django ORM의 특징 중 하나는 Queryset을 이용한다는 것이다.
Queryset의 특징으로는 Lazy-Loading(지연 호출)이 있는데
이 특징 때문에 N+1 문제가 발생하고 그로 인해 성능 저하 이슈가 나타나게 된다.
Lazy Loading이 없는 웹페이지에 접속하면 그 안에 있는 모든 내용이 다운로드가 된다. 그래서 첫 로딩이 좀 느릴 수 있다.
다만 사용자가 모든 콘텐츠를 실제로 이용한다면 문제가 크지 않은데
최상단 이미지만 확인하고 나가버린다면 낭비가 발생하게 된다.
이런 문제를 해결하기 위해 Lazy Loading 기법이 나오게 되었다.
한마디로 정의하면 Lazy Loding은
페이지를 불러오는 시점에 당장 필요하지 않은 리소스들은 추후에 로딩하는 기술이다.
아래 코드를 보자.
user_list = User.objects.all()
유저의 전체 리스트를 user_list에 담는 선언이다.
이때 쿼리셋에서는 SQL을 호출하지 않는다.
list(user_list)
이렇게 해야 SQL 호출이 일어나게 된다.
실제 데이터를 얻어와야 하는 시점에 호출이 일어난다.
users = User.objects.all()
first_user = users[0]
user_list = list(users)
이렇게 코드를 작성했을 때 SQL 호출은 몇 번 일어났을까?
정답은 두 번이다.
첫 번째 줄은 위에서 말한 것과 같이 쿼리셋으로 만드는 과정이고
실제 호출은 일어나지 않는다.
두 번째 줄에서야 첫 번째 인덱스의 유저 정보를 가져오므로
이때 호출이 발생되며
마지막 줄에서는 유저 정보를 리스트로 담아 user_list에 담기 때문에
이때 불필요한 호출이 한 번 더 발생하게 되는 것이다.
이러한 불필요한 호출을 줄이는 방법은 의외로 간단한데
바로 순서를 바꿔주는 것이다.
users = User.objects.all()
user_list = list(users)
first_user = users[0]
두 번째 줄에서 모든 유저의 정보를 가져오는 실제 호출이 일어났고
쿼리셋은 이때 가져온 데이터를 캐시에 저장하게 된다.
두 번째 줄에 의해 users는 모든 유저 정보를 담고 있는 쿼리셋이 된 것이다.
그래서 첫 번째 인덱스의 유저 정보를 가져올 때
user_list[0]으로 하지 않고 users[0]으로 가져오는 것이 가능해진다.
이처럼 쿼리셋을 호출하는 순서가 바뀌는 것만으로도 불필요한 호출을 막을 수가 있다.
돌아가서, Lazy Loading의 특징으로 인해 발생하는 N+1 문제는
어떻게 해결하면 될까?
바로 Eager Loading(즉시 호출)을 사용하는 것이다.
Eager Loading은 쿼리를 날릴 때 사용할 데이터까지 포함해 쿼리를 날리기 때문에 비효율적으로 늘어나는 쿼리를 방지할 수 있다.
Django에서는 Eager Loading 방식으로 select_related와 prefetch_related 메서드를 제공하고 있다.
select_related는 원래의 쿼리에 JOIN을 통해서 즉시 로딩하는 방식을 말하는데,
foreign-key, one-to-one처럼
single valued relationships에서만 사용이 가능하며 보통 정참조할 때 많이 사용된다.
(SQL의 JOIN을 사용하는 방식)
prefetch_related는 추가 쿼리를 수행해서 데이터를 즉시 로딩하는 방식을 말하는데,
foreign-key, one-to-one뿐만 아니라 many-to-many, many-to-one 등
모든 relationships에서 사용 가능하다.
(SQL의 WHERE ... IN 구문을 사용하는 방식)
select_related는 조건절이 필요하다면 filter를 달면 되고
prefetch_related의 경우 filter 외에 추가적인 조건절을 달고 싶다면
prefetch 함수를 이용하면 된다.
쿼리셋 작성 순서
Model > annotate > select_related > filter > prefetch_related