시작하기 전에
안녕하세요 똑똑한 개발자에서 백엔드 개발을 하고 있는 jujun입니다.
django orm 은 디폴트로 해당 모델의 모든 컬럼을 불러옵니다.
only 기능을 사용한다면 필요한 항목만 불러올 수 있게 됩니다.
이번 포스트에서는 only()와 반대(?)인 defer()기능은 명시된 컬럼을 빼고 모든 컬럼을 가져옵니다.
이는 불필요한 데이터를 불러오지 않게하여 컴퓨터 자원을 보다 효율적으로 사용할수 있습니다.
하지만 잘못 사용하면 lazy lading 에 의해 불필요한 쿼리를 발생시킵니다.
자세히 알아보겠습니다.
.
본론
예제코드
저번 블로그와 같이 아래와 같은 모델이 있습니다.
# api.raw.models
class Party(models.Model):
country = models.ForeignKey("raw.Country", verbose_name="국가", on_delete=models.SET_NULL ,null=True)
owner = models.ForeignKey("user.User", verbose_name="소유자", on_delete=models.SET_NULL ,null=True)
name = models.CharField("정당 이름", max_length=50)
description = models.TextField("설명", blank = True, null = True)
image = models.ImageField("사진", upload_to='raw/party/', blank = True, null = True)
color = models.CharField("정당 색", max_length=50)
class Meta:
verbose_name = '정당'
verbose_name_plural = verbose_name
ordering = ['country', 'name']
def __str__(self):
return self.name
defer() 기능
django orm 은 기본적으로 모델에 있는 모든 항목들을 DB로부터 불러옵니다.
>>> from api.raw.models import Party
>>> p_default = Party.objects.get(id=1)
(0.001) SELECT "raw_party"."id", "raw_party"."country_id", "raw_party"."owner_id", "raw_party"."name", "raw_party"."description", "raw_party"."image", "raw_party"."color" FROM "raw_party" WHERE "raw_party"."id" = 1 LIMIT 21; args=(1,)
>>> p_default.__dict__
{'_state': <django.db.models.base.ModelState object at 0x7fb915203790>, 'id': 1, 'country_id': 1, 'owner_id': 1, 'name': '국민의힘', 'description': '', 'image': '', 'color': '빨강'}
django shell 에서 Party 모델에서 id 값이 1인 인스턴스를 불러왔습니다. 0.001초가 걸렸고 Party 에 있는 id, country_id, 등등 모든 항목들을 가져왔습니다.
defer()기능을 통해 color를 뺴고 모든 컬럼을 가져오겠습니다.
>>> from api.raw.models import Party
>>> p_defer = Party.objects.defer('color','image','description','owner').get(id=2)
(0.001) SELECT "raw_party"."id", "raw_party"."country_id", "raw_party"."name" FROM "raw_party" WHERE "raw_party"."id" = 2 LIMIT 21; args=(2,)
>>> p_defer.__dict__
{'_state': <django.db.models.base.ModelState object at 0x7f2d9d0823d0>, 'id': 2, 'country_id': 1, 'name': '더불어민주당'}
0.001가 걸렸고 ‘color’,’image’,’description’,’owner’ 를 뺀 모든 컬럼을 가져왔습니다.
기능의 시연을 위해 억지 예제를 만들었습니다.
update
해당 인스턴스를 업데이트 하겠습니다.
>>> p_default.name ='국민의힘'
>>> p_default.save()
(0.003) UPDATE "raw_party" SET "country_id" = 1, "owner_id" = 1, "name" = '국민의힘', "description" = '', "image" = '', "color" = 'red' WHERE "raw_party"."id" = 1; args=(1, 1, '국민의힘', '', '', 'red', 1)
기본 orm 으로 불러왔던 모든 데이터가 DB에 반영되었습니다.
>>> p_defer.name = '더불어민주당'
>>> p_defer.save()
(0.003) UPDATE "raw_party" SET "country_id" = 1, "name" = '더불어민주당' WHERE "raw_party"."id" = 2; args=(1, '더불어민주당', 2)
반면 defer()을 통해 불러온 인스턴스는 ‘color’,’image’,’description’,’owner’ 컬럼이 없는 채로 DB에 반영되었습니다.
filter 에서 사용
>>> for party in Party.objects.filter(country_id=1).defer('color','image','description','owner'):
... party.name
...
(0.001) SELECT "raw_party"."id", "raw_party"."country_id", "raw_party"."name" FROM "raw_party" WHERE "raw_party"."country_id" = 1 ORDER BY "raw_party"."country_id" ASC, "raw_party"."name" ASC; args=(1,)
'국민의힘'
'더불어민주당'
>>>
Select_related 에서 사용
>>> q = Party.objects.defer('color','image','description','owner__email', 'country__name').select_related('owner', 'country').filter(country_id=1)
>>> for party in q:
... party.name
... party.country.name
... party.owner.email
...
(0.001) SELECT "raw_party"."id", "raw_party"."country_id", "raw_party"."owner_id", "raw_party"."name", "raw_country"."id", "user_user"."id", "user_user"."password", "user_user"."last_login", "user_user"."is_superuser", "user_user"."is_staff", "user_user"."is_active", "user_user"."date_joined", "user_user"."phone", "user_user"."nickname", "user_user"."level", "user_user"."country_id", "user_user"."region_sub_id", "user_user"."engaged_party_id", "user_user"."last_paint_updated_date", "user_user"."paints", "user_user"."acrlic_paints", "user_user"."is_register", "user_user"."occupation_num", "user_user"."used_paint_num", "user_user"."used_cash_item_num" FROM "raw_party" INNER JOIN "raw_country" ON ("raw_party"."country_id" = "raw_country"."id") LEFT OUTER JOIN "user_user" ON ("raw_party"."owner_id" = "user_user"."id") WHERE "raw_party"."country_id" = 1 ORDER BY "raw_party"."country_id" ASC, "raw_party"."name" ASC; args=(1,)
'국민의힘'
(0.000) SELECT "raw_country"."id", "raw_country"."name" FROM "raw_country" WHERE "raw_country"."id" = 1 LIMIT 21; args=(1,)
'Korea'
(0.001) SELECT "user_user"."id", "user_user"."email" FROM "user_user" WHERE "user_user"."id" = 1 LIMIT 21; args=(1,)
'admin@admin.com'
'더불어민주당'
(0.000) SELECT "raw_country"."id", "raw_country"."name" FROM "raw_country" WHERE "raw_country"."id" = 1 LIMIT 21; args=(1,)
'Korea'
(0.000) SELECT "user_user"."id", "user_user"."email" FROM "user_user" WHERE "user_user"."id" = 2 LIMIT 21; args=(2,)
'user1@user.com'
>>>
select_related로 많은 양의 데이터를 가져왔지만 defer로 제외한 컬럼은 가져오지 못했습니다. 그래서 해당 데이터를 호출할때 새로운 쿼리가 발생하였습니다. 이는 only와 defer를 사용할 때 주의해야할 부분입니다. 불필요한 쿼리를 발생시키지 않습니다.
get_or_create
# get 으로 사용해봄.
>>> exist_party , created = Party.objects.defer('color','image','description','owner').get_or_create(owner=u)
(0.001) SELECT "raw_party"."id", "raw_party"."country_id", "raw_party"."name" FROM "raw_party" WHERE "raw_party"."owner_id" = 1 LIMIT 21; args=(1,)
>>> created
False
>>> exist_party.name
'국민의힘'
>>>
# create 로 사용해봄.
>>> created_party , created = Party.objects.defer('color','image','description','owner').get_or_create(owner=u, name='정의당')
(0.001) SELECT "raw_party"."id", "raw_party"."country_id", "raw_party"."name" FROM "raw_party" WHERE ("raw_party"."name" = '정의당' AND "raw_party"."owner_id" = 1) LIMIT 21; args=('정의당', 1)
(0.003) INSERT INTO "raw_party" ("country_id", "owner_id", "name", "description", "image", "color") VALUES (NULL, 1, '정의당', NULL, '', '') RETURNING "raw_party"."id"; args=(None, 1, '정의당', None, '', '')
>>> created
True
>>> created_party.color
''
>>> created_party.name
'정의당'
get_or_create에서 사용해 보았습니다. get되는 경우는 defer가 잘 작동하지만 create된 경우에는 모든 항목이 불러옵니다.
마치는 말
모델이 커지면 커질 수록 데이터를 불러올 때 불필요한 데이터도 많아집니다. defer()기능을 사용하여 보다 가벼운 쿼리를 만드시길 바랍니다.