select_related && prefetch_related

안희수·2021년 7월 14일
0

TIL

목록 보기
26/26

근 2주간 1차 프로젝트를 진행하느라 바빠서
기술 블로그를 작성할 시간적 여유도 없었고
사용하고 있는 기술도 정확히 알고 사용하기 보다는
필요에 따라서 그때 그때 땜빵식으로 불러다 쓰는 예전의 습관이 나와서
시간이 여유로울 때 기술 블로그를 작성하기로 했다.

이번에 작성하는 내용은 select_related 와 prefetch_related 관련 내용인데
이 두가지를 사용해보게 된 배경은 이러하다
1 대 N 구조를 가진 메인 서브 테이블을 통해서 데이터를 불러오는데
N_Row 를 가지고 있는 대상에서 정렬 기준에 따라 최대값 혹은 최소값을 가져와서 그 기준으로 메인 테이블에서 가져온 데이터를 정렬하는 로직을 구현하고 있었다.

이를 쿼리로 구현해보면 다음과 같다.

--예시 쿼리

SELECT PRODUCT.ID,          --제품ID
       PRODUCT.NAME,        --제품명
       PRODUCT.CATEGORY_ID, -- 카테고리
       PRODUCT.DESCRIPTION, -- 제품 설명
       PRODUCT.RATING,      -- 별점
       OPTIONS.SALES,       -- 매출액
       OPTIONS.PRICE,       -- 판매가
       FROM PRODUCTS 
LEFT   OUTER JOIN OPTIONS
ON     PRODUCT.ID = OPTIONS.PRODUCT_ID
WHERE  PRODUCT.CATEGORY_ID = 1

원래는 제품 안에 기본 판매가,매출액 컬럼을 두고 정렬을 관리하려는 생각있다.
그 편이 정렬할 때는 불필요한 조인이 필요 없을 것이라 생각했기 때문이다.
그리고 테이블 트리거로 옵션 테이블의 값이 변경될 때마다 제품 테이블의 값을 동기화 하려고 생각했었는데 장고에서는 트리거와 유사한 기능을 지원하기는 하지만
Observer Pattern 인 경우에 주로 사용되기 때문에
역참조가 거미줄 처럼 얽혀 있는 테이블 구조에서는 사용이 지양되고 있다는 이야기를 들어서 방식을 바꾸기로 하였다. (본인이 구현하고자 하는 테이블 구조 외에도 다른 앱에서 여러 테이블들이 얼기설기 얽혀 있다.)

제품 안에 가격 옵션이 1개 혹은 2개 일 수도 있기 때문에
저 쿼리에서 연산되는 제품은 중복된 행이 나올 수 있었다.
그래서 제품을 먼저 뽑아내고 제품 갯수만큼 반복문을 실행하면서
제품의 ID를 기준으로 가격 옵션을 가져오고 제품 안에 집어 넣는 방식으로 구현을 했었는데
그 때 SELECT RELATED 와 PREFETCH_RELATED를 사용하였다.

여기서 말하는 SELECT RELATED 와 PREFETCH_RELATED 란
수행하는 역할 자체로만 보자면 단순한 LEFT JOIN 이지만
수행 방식에서 큰 차이가 있다.

두 방식 모두 단순한 쿼리 실행문과는 다르게 캐싱 기능을 가지고 있어
불필요한 쿼리의 재실행 및 DB 왕복을 줄인다는 점에서 효율적이지만
SELECT RELATED 는 정 참조시 주로 사용이 되며 쿼리는 복잡해지지만 불러온 데이터를 데이터베이스 서버가 종료되기 전까지 캐싱하기 때문에 매 쿼리마다 DB에 접근하지 않아되 되서 자원 낭비를 차단할 수 있다.
필터 명령어도 캐싱 기능이 있지만 Foreign_Key에 해당하는 참조대상까지는 저장하지 않는다.

SELECT RELATED 를 사용하지 않을 경우에는
매 클래스 참조시마다 쿼리가 실행되기 때문에
데이터가 많다면 SELECT RELATED가 더 바람직할 것이다.

PREFETCH_RELATED 역시 데이터를 캐시에 저장하며 모든 관계형에서 사용이 가능하다.
차이점이 있다면 SELECT RELATED는 하나의 쿼리로 Related Objects 들을 불러오지만 PREFETCH RELATED 는 메인 쿼리가 실행이 된 후 별도에 쿼리가 실행되기 때문에
최소 1번 이상 더 쿼리가 실행이 된다.
그래서 가급적 SELECT RELATED를 사용하는 것이 리소스 소모를 줄일 수 있다라고 하는데 사실은 둘 중 어느 것이 더 효율적이고 쓰지 말아야 한다기 보다는 상황에 맞게 사용하는 것이 바람직하고 어떤 때는 PREFETCH RELATED가 더 효율적일 수도 있다.

대표적인 경우가 Many to Many 관계일 경우인데
PREFETCH RELATED에 경우 참조 대상까지 캐시에 저장하기 때문에 단순 역참조 보다 훨씬 효율적이다.

두 가지 경우 모두 쿼리가 복잡하게 짜이기 때문에 처음에는 좀 어려울 수 있겠지만
순수 쿼리를 짜게 되더라도 쿼리는 복잡하게 나오게 된다. 요지는 위에 두 경우는
캐싱을 하기 때문에 불필요한 쿼리의 재 실행과 DB 접근을 최소화 할 수 있다는 점이다.

그 전에는 데이터를 캐싱해서 사용하지 않고 데이터베이스에서 직접 가져왔기 때문에 데이터베이스 연산을 최소화 하는 쿼리를 작성하는 것이 최고였고 때문에 Many to Many 관계보다는 메인 테이블에 최대한 많은 데이터를 표현하려고 하였다.
그러나 django도 물론이고 웹에서는 그 전 처럼 빈번하게 실제 데이터베이스를 왔다갔다 하는 것이 아니라 필요애 따라 한번에 많은 데이터를 가져다 효율적으로 수행하는 것에서 또 한번 새로움을 느꼈다. 여전히 배울 것은 많고 앞으로 공부해야 될 것은 넘치는데 지금은 파이썬의 장고이지만 다음에는 또 새로운 프레임워크와 언어를 공부해 불 수 있었으면 좋겠다.

profile
9년차 소프트웨어 개발자 (2024년 재 개편 예정입니다)

0개의 댓글