Skip to content

Query Builder + Promise 2.0 + DataLoader #74

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 58 commits into from
Apr 13, 2017

Conversation

syrusakbary
Copy link
Member

@syrusakbary syrusakbary commented Sep 7, 2016

This PR focuses on:

  • Creating a Query Builder so the function resolution is not discovered in runtime (check detailed reasoning here). This will help to improve resolution performance about ~3-10x
  • Using new promise implementation Promise 2.0. This will help to improve performance by 2x.
  • Add first version of the DataLoader API with GraphQL ( @tomelm 😉 )
  • Extra:
    • Avoid OrderedDict, using an enhanced dict (with field ordering) that will improves speed about ~35% if cyordereddict is installed and ~3x if using the native OrderedDict (the OrderedDict implementation in Python < 3.5 is pure python, while in newer python versions is implemented in C).

Try it in your repo

Install the dependencies

# Install the branch corresponding to this PR
pip install https://github.com/graphql-python/graphql-core/archive/features/next-query-builder.zip

# Install Promise 2.0
pip install "promise>=2.0.dev"

If you want to use the faster experimental executor, add this code in your GraphQL implementation before executing any query:

from graphql.execution import executor

executor.use_experimental_executor = True

Details

Performance

Using the Query Builder strategy the performance for resolving objects is expected to improve from 2 to 10x.

  • PyPy improvements: ~3x
  • CPython improvements: ~2-10x

Related issue in Graphene: graphql-python/graphene#268

Promise 2.0

The new promise implementation is quite performant, has non-blocking lazy Events (for faster waits on promises) and a initial DataLoader implementation.

Related PR in promise with the details: syrusakbary/promise#23

DataLoader

With the new promise package, we can use also a DataLoader for solving the N+1 problem in GraphQL.

How to use DataLoader with Graphene

import graphene
from promise import Promise
from promise.dataloader import DataLoader


class Location(graphene.ObjectType):
    id = graphene.ID()


class Business(graphene.ObjectType):
    id = graphene.ID()
    location = graphene.Field(Location)

    def resolve_location(self, args, context, info):
        # This will load the location with the LocationDataLoader
        # (it will batch the location loads together automatically for us)
        return context.location_data_loader.load(self.id)


class Query(graphene.ObjectType):
    get_business = graphene.Field(Business, id=graphene.ID())

    def resolve_get_business(self, args, context, info):
        # This will load the business with the BusinessDataLoader
        # (it will batch the business loads together automatically for us)
        return context.business_data_loader.load(args.get('id'))

schema = graphene.Schema(query=Query)


# Here we create the custom DataLoaders

# We will use this for testing that the calls are batched
business_load_calls = []
location_load_calls = []

class BusinessDataLoader(DataLoader):
    def batch_load_fn(self, keys):
        business_load_calls.append(keys)
        return Promise.resolve([Business(id=key) for key in keys])


class LocationDataLoader(DataLoader):
    def batch_load_fn(self, keys):
        location_load_calls.append(keys)
        return Promise.resolve([Location(id=key) for key in keys])


class Context(object):
    business_data_loader = BusinessDataLoader()
    location_data_loader = LocationDataLoader()


query = '''
{
    business1: getBusiness(id: "1") {
        id
        location {
            id
        }
    }
    business2: getBusiness(id: "2") {
        id
        location {
            id
        }
    }
}
'''

result = schema.execute(query, context_value=Context())
print result.data

# Here we test that the loads are batched together.
# Each inner list represents the loads that are batched in the same call.

assert business_load_calls == [['1','2']]
assert location_load_calls == [['1','2']]

@syrusakbary syrusakbary force-pushed the features/next-query-builder branch from 528d60c to 8bc7003 Compare March 1, 2017 10:29
@syrusakbary syrusakbary force-pushed the features/next-query-builder branch from b0e4319 to b356f66 Compare March 1, 2017 17:35
@syrusakbary syrusakbary force-pushed the features/next-query-builder branch from 446a2c3 to d22798f Compare March 8, 2017 05:23
@syrusakbary syrusakbary changed the title Added first proof of concept of query builder Query Builder + Promise 2.0 + DataLoader Mar 14, 2017
@syrusakbary syrusakbary merged commit c496eed into master Apr 13, 2017
@ProjectCheshire ProjectCheshire deleted the features/next-query-builder branch June 2, 2019 18:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants