Python/Django

[Django] Prefetch 에 대한 이해

yubi5050 2024. 5. 19. 17:46

Prefetch 란

perfetch_related 함수는 쿼리에서 Join을 하지 않고, 개별 쿼리를 실행 한 후 django에서 직접 데이터를 조합하는 것

 

예시) A, B, C 모델이 존재 / A:B 는 1:N / B:C 는 1:N의 관계를 가짐

 

비교 1 ) A에서 C 까지의 Prefetch

1안. Prefetch 병렬로 쓰기

a = (
    A.objects.filter(id=1).prefetch_related(
        Prefetch(
            "b_set", # A에서 B 역참조
        ),
        Prefetch(
            "b_set__c_set", # A에서 C역참조
        ),
    )
).first()

 

2안. Prefetch 중첩해서 쓰기

a = (
    A.objects.filter(id=1).prefetch_related(
        Prefetch(
            "b_set", # A에서 B역참조
            queryset=B.objects.all().prefetch_related(
                Prefetch(
                    "c_set", # B에서 C 역참조
                )
            ),
        ),
    )
).first()

 

결론적으로 1안과 2안의 결과(쿼리 포함)는 동일하다.

1안의 경우 A입장에서 C까지 prefetch 하는 과정이 두번의 Prefetch를 타고 가야해서, 부적절해 보일 수 있으나,

django의 Prefetch는 알아서 최적화(조합)을 해주기 때문에 같은 의미임.

단. 실제 가독성 측면에서 2번이  좋아 보이기도 하지만, 팀 규칙으로 정할 문제. 

 

비교 2 ) filter와 prefetch의 관계

 

1안

# filter 의미
a = (
    A.objects.filter( # inner join 수행됨
       B_set__C_set_field_c__gte=10, # C테이블의 field_c가 10이상의 값인 것들 추출
    )
)

 

  • filter에서 B와 C에 id 필드 들에 대해, inner join을 수행
  • 결국 C의 c가 유효한 A를 구하는 것 (유효한 C에서 구하는 것이 아닌 A를 구하는데 사용되고 끝)
  • 만약 B와 C가 N개씩 존재한다면, 매번 B * C 갯수만큼 쿼리를 매번 호출 할 것

 

2안

a = (
    A.objects.filter().prefetch_related(
        Prefetch(
            "B_set__C_set", # 각기 SELECT 쿼리가 실행됨
            queryset=C.objects.filter(c__gte=10),
        )
    )
).first()
  • prefetch 에서 B와 C에 id 필드 들에 대해, SELECT 조회 쿼리가 수행됨 
  • 이 결과를 django에서 합침
  • 결국 유효한 c를 가진 C를 구해서, A에 붙이는 것
  • B에 대한 SELECT 쿼리, C에 대한 SELECT 쿼리가 추가로 수행됨