Dataloader를 이용하면 GraphQL에서 발생할 수 있는 N+1 problem을 해결할 수 있다.
아래 예시는 graphene에서 제공한 예시로, Promise를 리턴한다.
예시
from promise import Promise
from promise.dataloader import DataLoader
class UserLoader(DataLoader):
def batch_load_fn(self, keys):
# Here we return a promise that will result on the
# corresponding user for each key in keys
return Promise.resolve([get_user(id=key) for key in keys])
user_loader = UserLoader()
user_loader.load(1).then(lambda user: user_loader.load(user.best_friend_id))
user_loader.load(2).then(lambda user: user_loader.load(user.best_friend_id))
아래는 saleor 오픈소스에서 사용한 예시이다.
dataloaders.py
from collections import defaultdict
from ..graphql.core.dataloaders import DataLoader
from .models import Payment
# DataLoader
class AddressByIdLoader(DataLoader):
context_key = "address_by_id"
def batch_load(self, keys):
address_map = Address.objects.using(self.database_connection_name).in_bulk(keys)
# 아직 Promise는 아니다.
return [address_map.get(address_id) for address_id in keys]
# ObjectType
class Order(ModelObjectType):
id = graphene.GlobalID(required=True)
...
@staticmethod
@traced_resolver
def resolve_billing_address(root: models.Order, info):
def _resolve_billing_address(data):
...
if root.user_id:
# user, address는 list이다.
user = UserByUserIdLoader(info.context).load(root.user_id)
address = AddressByIdLoader(info.context).load(root.billing_address_id)
# Promise를 반환한다.
return Promise.all([user, address]).then(_resolve_billing_address)
self.database_connection_name
를 read replica로 설정해 Address에 해당하는 데이터를 가져올 때 read replica를 사용하도록 했다.batch_load
함수로 리스트를 리턴한다. AddressByIdLoader(info.context).load(root.billing_address_id)
를 통해 데이터를 load한 뒤 이에 대한 Promise를 리턴한다.아래와 같은 GraphQL 요청을 보내야 한다고 가정해 보자.
{
me {
name
bestFriend {
name
}
friends(first: 5) {
name
bestFriend {
name
}
}
}
}
me
필드는 bestFriend
, friends
필드를 가지고 있다.bestFriend
에 대한 요청 따로, friends
에 대한 요청 따로 백엔드로 보내야 한다(N+1 problem).DataLoader
를 사용하면 최대 4번의 데이터베이스 요청만으로 원하는 결과를 가져올 수 있다.UserLoader
의 인스턴스를 user_loader
로 받아 왔다.UserLoader
에 정의된 개별 load(best_friend_id, friend_ids)를 통합해 배치 함수를 1번 호출하게 된다. class User(graphene.ObjectType):
name = graphene.String()
best_friend = graphene.Field(lambda: User)
friends = graphene.List(lambda: User)
def resolve_best_friend(root, info):
return user_loader.load(root.best_friend_id)
def resolve_friends(root, info):
return user_loader.load_many(root.friend_ids)
안녕하세요. 쿄쿄님. 좋은 글 감사합니다.