strawberry-django icon indicating copy to clipboard operation
strawberry-django copied to clipboard

Avoid unnecessary query for edges when only fetching totalCount in connection

Open Carlisle345748 opened this issue 10 months ago • 2 comments

Feature Request Type

  • [ ] Core functionality
  • [x] Alteration (enhancement/optimization) of existing feature(s)
  • [x] New behavior

Description

When I was analyzing the SQL queries using the debug toolbar, I found that Django creates two queries for such a connection query. However, one of the queries is unnecessary.

# Query for a connection
query {
  books {
    totalCount
  }
}
# Query for edges (unnecessary)
SELECT books_book.id, books_book.title, books_book.author, books_book.price
FROM books_book
LIMIT 101;

# Query for totalCount
SELECT COUNT(*) AS "__count"
FROM "books_book";

The first SQL query is created when the strawberry connection resolver (not strawberry_django) iterates over the queryset to create edges. The creation of edges will always happen even if edges are not requested in the query. Because a Connection, which includes edges and page_info, is always created first and then used as a source to construct its sub_fields such as edges, totalCount, and __typename.

# strawberry/relay/types.py 
@classmethod
def resolve_connection(...)
...
  edges = [
      edge_class.resolve_edge(
          cls.resolve_node(v, info=info, **kwargs),
          cursor=start + i,
      )
      for i, v in enumerate(iterator) # SQL query is triggered here
  ]
...

I saw there is a connection resolver for ListConnectionWithTotalCount. However, it is just a wrapper around the connection resolver in strawberry. Should we create a customized connection resolver for Django, which checks the request fields before doing operations that trigger SQL queries? For example, we can check if the edges is in the query before creating edges in the resolver.

selections = {s.name for s in info.selected_fields[0].selections}

edges = []
if "edges" in selections:
  edges = [
      edge_class.resolve_edge(
          cls.resolve_node(v, info=info, **kwargs),
          cursor=start + i,
      )
      for i, v in enumerate(iterator)
  ]

If this is a valid idea. I can create a PR for this optimization. Thanks for your time and consideration.

Upvote & Fund

  • We're using Polar.sh so you can upvote and help fund this issue.
  • We receive the funding once the issue is completed & confirmed by you.
  • Thank you in advance for helping prioritize & fund our backlog.
Fund with Polar

Carlisle345748 avatar Oct 15 '23 22:10 Carlisle345748

Hey @Carlisle345748,

Hrm, that's interesting. It is actually the first time I see someone only wanting to query the totalCount and not the edges =P

I do understand your use case, and I do welcome you to create a PR to optimize this :)

Feel free to ping me on discord if you need any help with the development

bellini666 avatar Oct 26 '23 17:10 bellini666

is a patch available somewhere for this one ?

stygmate avatar Mar 01 '24 11:03 stygmate