QuerySet을 count()로 row를 세는 것과 len()으로 세는 것의 차이 : 장고 성능 튜닝

QuerySet을 count()로 row를 세는 것과 len()으로 세는 것의 차이 : 장고 성능 튜닝

Programming/Django

2020-12-05 00:49:47


최근 받은 코드리뷰 중에 len()으로 row를 얻어오던 것을 count로 변경해달라는 코멘트를 받았다. ORM과 Django 초짜다보니 stackoverflow를 몇 개 뒤져보니 row를 확인하는 것은 왠만하면 count를 이용해서 얻어오는 것을 다들 권장한다. 그렇다면 그 이유가 무엇인지 확인해보자.

현재 내 서비스의 User의 수를 한 번 알아본다고 하자.

(유저가 백만, 천만인 곳에서 이런 일을 심심풀이로 하면 회사의 Devops 횽들에게 불려가니 조심한다)

qs = User.objects.all()

# queryset의 count()를 사용하는 방법이 있다.
def user_count_by_count():
    return qs.count()

# len으로 확인 하는 방법과
def user_count_by_len():
    return len(qa)

이 때, count()의 경우 SELECT COUNT(*) FROM User 가 RDBS에서 실행되고 count가 python으로 전달될 것이다.

len()의 경우 SELECT * FROM User를 한 번 RDBS에서 실행하고 python으로 전달된 결과를 len()으로 셀 것이다.

흔히들 row 개수를 셀 일이 있다면 count()를 쓰라고 하는데 그 이유는 len()으로 count를 대신하려하면 fetch를 하면서 시간이 O(N)이 걸리고 이 결과를 웹서버의 memory에 담으면서 storage에 O(N)이 발생하며 복사하는 시간이 걸려 time에도 O(N)이 추가로 발생한다. 거기에 추가적으로 len()이 돌아가는 시간도 발생한다. 이 두 과정은 인프라에 따라 다르겠지만 count가 2배정도 빠르다고 한다.

하지만 len()을 쓰는 것이 count보다 유리할 때가 있다.

qs = User.object.all()

def prefer_len_to_count():
# User 정보 안에 telephone 필드가 있다고 가정하고 그 정보를 리스트로 취합한다.
    telephones = [p for p.telephone in qs]
       # 그 정보로 뭔가 작업을 하고...
    ...
    # qs의 개수를 얻는다 (용도는 알아서 생각해보자)
    len(qs)

위와 같이 queryset을 fetch해서 사용을 하는 상황이라면 len이 아주 쪼금 유리하다. 이유는 count()의 경우 RDBS에 두 번 (fetch 한 번, count() 한 번) 접근해야하기 때문이다.

Devops횽들한테 혼날 말이지만 엄청나게 많은(이 기준은 인프라 상황에 따라 너무 다르다) row를 셀 것이 아니라면 그냥 count써도 무방하다.

당연히 template에서도 count의 사용이 권장된다.

아래 count가

{{ some_queryset.count }}

아래의 len보다 더 권장된다.

{{ len(some_queryset) }}

참고 : stackoverflow.com/questions/14327036/count-vs-len-on-a-django-queryset


#django #장고
다음 이전

이 포스트의 위치가 궁금하시다면