TIL.0319 lazy
Django의 ORM은 다른 ORM
과 마찬가지로 Lazy-loading
방식을 사용한다. Lazy-loading
이란 ORM
에서 명령을 실행할 때마다 데이터베이스에 접근하여 데이터를 가져오는 것이 아닌 모든 명령처리가 끝나고 실제 데이터를 불러 와야할 때 데이터베이스 Query
문을 실행하는 방식
- on-demand loading으로도 불린다. 여기서 on-demand란 ''라는 의미이다. 요구가 있을 때는 언제든지
- 온라인 콘텐츠(웹앱이나 웹사이트)의 기술 최적화이다.
- 전체 웹페이지를 로딩하는 것이 아니라 유저가 필요할 때까지 미루고 남겨두었다가 원하는 부분에 대해서 요구하는 순간에 로딩하는 것이다.
- 대표적인 예로 무한 스크롤이 있다. 웹 페이지를 유저가 내릴 때 로딩된다.
Query문을 실행하는 시점
- Iteration 객체일때
- QuerySet은 반복이 가능하며 처음 반복할때 쿼리를 실행한다.
- 슬라이싱(Slicing)
- QuerySet은 앞서 SQL쿼리 문에서 설명했듯 iterable한 object로서 슬라이싱 구문을 사용하여 슬라이싱을 할수있다.
- Pickling/Caching
- Pickle = 모듈 / Python 객체 구조를 직렬화 및 역 직렬화 하기위한 바이너리 프로토콜을 구현한다.
- 추후 추가설명 필요
- Pickle = 모듈 / Python 객체 구조를 직렬화 및 역 직렬화 하기위한 바이너리 프로토콜을 구현한다.
- repr()
- reper()를 사용할때 쿼리를 실행
- reper = 객체의 표준 문자열 표현을 반환합니다
- len()
- len()을 호출했을때 쿼리를 실행한다.
- list()
- QuerySet을 호출하여 강제로 평가한다.
- ex) \ entry_list = list(Entry.object.all())
- bool()
- if 문을 사용해 boolean값을 확인하게 될때 포함
- 찾는값의 존재 여부만 파악 할때는 if 문사용 보다는 .exists()를 이용하여 확인하는 것이 성능면에서 효율적
Eager-loading
: 즉시로딩
Eager-loading ↔ Lazy-loading
Lazy-loading은 Query문을 하나하나 실행하여 데이터를 가져온다면
Earger-loading은 지금당장 사용하지 않을 데이터도 포함하여 Query 문을 실행 하기 때문에
Lazy-loading의 N+1문제의 해결책으로 많이 사용하게 된다.
Django에서 실행방법
- select_related 메소드와 prefetch_related 메소드를 사용 select_related : 셀렉트할 객체가 역참조 하는 single object(oto or mtm)이거나 정참조의 foreign key일때 사용
select_related
는 각각의 lookup마다 SQL의JOIN
을 실행하여 테이블의 일부를 가져오고,select .. from
에서 관련된 필드들을 가져온다. 형식 parameter에는 참조하는 class 의 이름을 소문자로 쓰고 ‘ ’에 감싼다 여러개의 parameter를 가질수있다. 예) Store.objects.filter(id=1).select_related('gungu','city').values('id','name','city_name') prefetch_related :구하려는 객체가 정참조 multiple objects(many-to-many or one-to-many)이거나, 또는 역참조 Foreign Key일때 사용한다.selected_related
와 달리,prefetch_related
는 SQL의JOIN
을 실행하지 않고, python에서 joining을 실행한다. 형식 <lowercase 모델이름>_set : 장고가 지어주는 default 역참조 메서드 이름 ForeignKey 필드에 이름을 정하면 default이름 대신 given 이름을 쓸 수 있다.
N+1 Query 문제
Lazy-loading
의 성능이슈인 N+1 Query
문제는 외래키(Foreign Key
)를 참조해서 데이터를 가져올 때 발생한다.
아래와 같이 restaurant
와 owner
가 1:1 관계인 모델이 있다고 가정하자
mysql> select * from restaurants;
+----+--------------+--------------+----------+
| id | name | place | owner_id |
+----+--------------+--------------+----------+
| 1 | 얌얌피자 | 서울 | 1 |
| 2 | 굿굿피자 | 인천 | 2 |
| 3 | 좋아좋아 | 부산 | 3 |
| 4 | 배고파아 | 제주 | 4 |
| 5 | 정통스 | 이탈리아 | 5 |
+----+--------------+--------------+----------+
mysql> select * from owners;
+----+------+-----+
| id | name | age |
+----+------+-----+
| 1 | 김 | 21 |
| 2 | 이 | 22 |
| 3 | 박 | 30 |
| 4 | 안 | 40 |
| 5 | 전 | 50 |
+----+------+-----+
>>> restaurants = Restaurant.objects.all()
>>>
>>> for restaurant in restaurants:
... restaurant.owner.name
이때 쿼리를 확인해보면 5개가 아닌 5+1인 6개의 쿼리가 날아간것을 확인할수있다.
{'sql': 'SELECT `restaurants`.`id`, `restaurants`.`name`, `restaurants`.`owner_id`, `restaurants`.`place` FROM `restaurants`', 'time': '0.003'},
{'sql': 'SELECT `owners`.`id`, `owners`.`name`, `owners`.`age` FROM `owners` WHERE `owners`.`id` = 1 LIMIT 21', 'time': '0.004'},
{'sql': 'SELECT `owners`.`id`, `owners`.`name`, `owners`.`age` FROM `owners` WHERE `owners`.`id` = 2 LIMIT 21', 'time': '0.001'},
{'sql': 'SELECT `owners`.`id`, `owners`.`name`, `owners`.`age` FROM `owners` WHERE `owners`.`id` = 3 LIMIT 21', 'time': '0.001'},
{'sql': 'SELECT `owners`.`id`, `owners`.`name`, `owners`.`age` FROM `owners` WHERE `owners`.`id` = 4 LIMIT 21', 'time': '0.001'},
{'sql': 'SELECT `owners`.`id`, `owners`.`name`, `owners`.`age` FROM `owners` WHERE `owners`.`id` = 5 LIMIT 21', 'time': '0.001'}]
맨 첫번 째 Query
문은 전체 restaurant
를 가져오고 그뒤 owner
에서 5번 따로 가져오게 된다. 이는 가져오는 데이터가 많으면 많을수록 비효율적인 코드가 되기 때문에 위에서 언급한 Eager-loading
을 통해 해결해야 한다.
Author And Source
이 문제에 관하여(TIL.0319 lazy), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@auddwd19/TIL.0319-lazy저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)