Django 3.2에 추가될 몇가지 기능

Django 3.2에 추가될 몇가지 기능

Django(이하, 장고)가 3.2 버전으로 업데이트가 예정되어 있습니다. ORM에 적용될 몇가지 사항을 정리 하였습니다.

Add support for adding non-key columns to indexes(Ticket #30913)

Covering Indexes(이하, 커버링 인덱스)를 사용할 수 있습니다. 커버링 인덱스의 주요 이점은 쿼리가 인덱스에 있는 필드만 사용할 때, 실제 테이블에 전혀 액세스하지 않아도 된다는 점 입니다. 당연히 데이터를 더 빨리 가져올 수 있습니다. 쿼리 인덱스의 대표적인 예가 COUNT 인데, SELECT A FROM ... WHERE X < 3.14 일 경우 A에 커버링 인덱스가 적용되어 있다면, 인덱스에서 곧바로 처리 가능합니다. 커버링 인덱스의 경우 복합키를 사용할 때, 성능상 이점을 볼 수 있다고 알려져 있습니다. 자세한 사항은 아래 링크를 참고하세요. 그리고 해당 기능의 경우 Postgres, MSSQL, IBM DB2에서 사용할 수 있습니다.

Add support for tzinfo parameter to TruncDate() and TruncTime()(Ticket #31948)

Timestamp와 관련된 작업은 언제나 까다롭고, 많은 실수를 유발합니다. 모바일 애플리케이션의 기본 설정이 '글로벌'이 되고 난 이후로, Timestamp 처리를 더 많이 하게되었고, 더 많은 실수를 하게되었습니다. 장고 3.2에서는 이 문제를 조금 쉽게 해결할 수 있는 TruncDateTruncTime 함수를 제공합니다. tzinfo의 매개 변수를 사용하면 특정 시간대의 날짜와 시간을 쉽게 가져올 수 있습니다.

import pytz
from django.db.models.functions import TruncDay

Post.objects
.annotate(created_at=TruncDay('created_at_day', tzinfo=pytz.timezone("Asia/Seoul")))
.values('created_at_day')

Add JSONObject Func(Ticket #32179)

PostgreSQL은 JSON 지원합니다. 물론 MySQL(≥5.7)과 MariaDB(≥10.2)도 지원합니다. 장고 3.2부터 임의의 Key-Value를 허용하는 json_build_object가 ORM에 추가되었습니다. DRF를 사용하신다면 성능 테스트를 진행하시고 선택하세요!

from django.db.models import F
from django.db.models.functions import JSONObject

Post.objects.annotate(obj=JSONObject(
    id=F('id'),
    title=F('title'),
    created_at_day=TruncDay('created_at', tzinfo=pytz.timezone("Asia/Seoul")),
).values_list('obj').first()

({
    'id': 123,
    'title': 'Hello! World!',
    'created_at_day': '2021-03-17T00:00:00',
},)

Add queryset.alias() to mimic .annotate() for aggregations without loading data(Ticket #27719)

ORM으로 복잡한 쿼리를 작성할 때 Subquery와 OuterRef 등을 사용하곤 합니다. Subquery를 사용할 때 항상 겪는 문제는 Subquery가 SELECTWHERE에 모두 적용되기 때문에 실행 계획에 영향을 줍니다.

from django.db.models import Subquery, OuterRef

# 장고 3.1
Post.objects.annotate(
    id_of_previous_post=Subquery(
        Post.objects.filter(created_at__lt=OuterRef('created_at'))
        .order_by('-created_at')
        .values('id')[:1],
    )
).filter(id_of_previous_post__isnull=True)

# 장고 3.1 SQL
SELECT
    "post"."id",
    "post"."created_at",
    "post"."name",
    (
        SELECT U0."id"
        FROM "post" U0
        WHERE U0."created_at" < "post"."created_at"
        ORDER BY U0."created_at"
        DESC LIMIT 1
    ) AS "id_of_previous_post"
FROM
    "post"
WHERE
    (
        SELECT U0."id"
        FROM "post" U0
        WHERE U0."created_at" < "post"."created_at"
        ORDER BY U0."created_at" DESC
        LIMIT 1
    ) IS NULL

장고 3.2에서는 annotate에서 alias로 변경하면, SELECT에 적용되지 않습니다.

# 장고 3.2
Post.objects.alias(
    id_of_previous_post=Subquery(
        Post.objects.filter(created_at__lt=OuterRef('created_at'))
        .order_by('-created_at')
        .values('id')[:1],
    )
).filter(id_of_previous_post__isnull=True).values('id')

# 장고 3.2 SQL
SELECT "post"."id", "post"."created_at", "post"."name"
FROM "post"
WHERE (
    SELECT U0."id"
    FROM "post" U0
    WHERE U0."created_at" < "post"."created_at"
    ORDER BY U0."created_at" DESC
    LIMIT 1
) IS NULL