strawberry-graphql / strawberry-django

Strawberry GraphQL Django extension
https://strawberry.rocks/docs/django
MIT License
406 stars 118 forks source link

Question Re. Nested GraphQL Optimization #475

Closed rsomani95 closed 7 months ago

rsomani95 commented 7 months ago

Hello, thanks for all your work on strawberry. This looks like a really exciting project and I'm excited to try this as a graphene replacement.

I wanted to try strawberry to see if the optimizer would work for nested GraphQL queries. This does not seem to work out of the box; I wanted to ask if this is feasible with strawberry with some additional work on my end (by modifying how I'm setting up the resolvers, for example), or if this is simply not feasible given how I'm formulating the graphql query.

Here is a minimal reproduction of my setup:


import strawberry_django
from django.db import models
from strawberry import auto

# --- Django Models
class Library(models.Model):
    library_name = models.CharField(max_length=200)
    description = models.TextField(null=True, default=None)

class VideoAsset(models.Model):
    duration = models.IntegerField(null=False, default=None)
    time_base = models.IntegerField(default=600)
    library = models.ForeignKey(
        "Library", on_delete=models.CASCADE, null=True, default=None
    )

class Segment(models.Model):
    video_asset = models.ForeignKey("VideoAsset", on_delete=models.CASCADE)
    transcript = models.TextField(null=True)

# --- Strawberry Types
@strawberry_django.type(VideoAsset)
class VideoAssetType:
    duration: int
    library: "LibraryType"
    time_base: int

    @strawberry.field
    def segment_set(self, info) -> list["SegmentType"]:
        segments = Segment.objects.filter(video_asset_id=self.id)
        return segments

@strawberry_django.type(Library)
class LibraryType:
    library_name: auto
    description: auto

@strawberry_django.type(Segment)
    video_asset: "VideoAssetType"
    transcript: str | None

# --- Resolvers
def get_all_video_assets() -> QuerySet[VideoAsset]:
    return VideoAsset.objects.all()

def get_all_segments() -> QuerySet[Segment]:
    return Segment.objects.all()

# --- Query / Schema
@strawberry.type
class Query:
    all_video_assets: list[VideoAssetType] = strawberry.field(
        resolver=get_all_video_assets
    )
    all_segments: list[SegmentType] = strawberry.field(resolver=get_all_segments)

schema_strawberry = strawberry.Schema(
    query=Query,
    extensions=[
        DjangoOptimizerExtension,
    ],
)

I also set up the debug toolbar to see the SQL queries being run. Now, in the GraphIQL console, I'm trying this query:

{
  allVideoAssets {
    duration
    segmentSet {
      transcript
    }
  }
}

And this is what the generated SQL looks like in the debug toolbar: CleanShot 2024-02-06 at 11 59 47@2x

To take a step back, what I'm trying to do is fetch all my VideoAssets, and for each asset, fetch all the Segments with some/all of its attributes. Ideally, this should be two queries, something like:

video_assets = VideoAsset.objects.all()
segments = Segment.objects.all()

But this results in a N+1 query. Any ideas regarding how I could optimize this? Thanks!

Upvote & Fund

Fund with Polar

rsomani95 commented 7 months ago

Err, turns out all I had to do was remove the custom segment_set resolver.

Changing from this:

@strawberry_django.type(VideoAsset)
class VideoAssetType:
    ...
    @strawberry.field
    def segment_set(self, info) -> list["SegmentType"]:
        segments = Segment.objects.filter(video_asset_id=self.id)
        return segments

to this:

@strawberry_django.type(VideoAsset)
class VideoAssetType:
    ...
    segment_set: list["SegmentType"]

fixed my issue, which is really cool and already a step up from what graphene-django-optimizer offers, which is super cool.