Quickstart

The database schema these examples will be using can be seen here.

Let's say we have defined a graphql schema like this:

import graphene
from graphene_django import DjangoObjectType, DjangoListField
from example_project.app.models import Apartment

class ApartmentType(DjangoObjectType):
    class Meta:
        model = Apartment

class Query(graphene.ObjectType):
    # Imagine the rest of the types are also here,
    # and we omit it for brevity.
    all_apartments = DjangoListField(ApartmentType)

schema = graphene.Schema(query=Query)

Now, based on our database schema, we want to make a query like this:

query {
  allApartments {
    streetAddress
    stair
    apartmentNumber
    sales {
      purchaseDate
      ownerships {
        percentage
        owner {
          name
        }
      }
    }
  }
}

As is, this query will result in:

  • 1 query for all apartments
  • 1 query or each sale
  • 1 query for each ownership in each sale
  • 1 query for each owner in each ownership in each sale

Let's say that we have:

  • a modest 20 apartments
  • each apartment has 3 sales
  • each sale has 2 ownerships

In total, that's...

1 + (20 * 3) + (20 * 3 * 2) + (20 * 3 * 2 * 1) = 301 queries

It's important to notice, that the amount of queries is proportional to the amount of records in our database, so the number of queries is only going to increase. This is called an N+1 problem.

We are also over-fetching all fields on each model, and thus not taking advantage of GraphQLs schema at all.

This is the issue this library hopes to solve.

Shoutout to graphene-django-optimizer, which inspired this library. The library seem to no longer work in modern versions of graphene-django. Hopefully this library can replace it, while offering a cleaner API.

We can optimize this query by simply using DjangoObjectType from query_optimizer instead of graphene_django

import graphene
from example_project.app.models import Apartment

from query_optimizer import DjangoListField, DjangoObjectType

class ApartmentType(DjangoObjectType):
    class Meta:
        model = Apartment

class Query(graphene.ObjectType):
    all_apartments = DjangoListField(ApartmentType)

schema = graphene.Schema(query=Query)

We could also use the optimize function to wrap a custom resolver queryset:

import graphene
from query_optimizer import DjangoObjectType, optimize  # new import
from example_project.app.models import Apartment

class ApartmentType(DjangoObjectType):
    class Meta:
        model = Apartment

class Query(graphene.ObjectType):
    all_apartments = graphene.List(ApartmentType)

    def resolve_all_apartments(root, info):
        return optimize(Apartment.objects.all(), info)  # wrapped function

schema = graphene.Schema(query=Query)

That's it!

With the following configuration, the same query will result in just 3 database queries, regardless of the number of database records.

  • 1 query for all apartments
  • 1 query for all sales in all apartments
  • 1 query for all ownerships with their owners for each sale in each apartment

Also, the optimization will only fetch the fields given in the GraphQL query, as the query intended.

See technical details on how this works.