/rails-graphql-async-demo

A Rails App with GraphQL-Ruby and Async for parallel HTTP and database queries

Primary LanguageRuby

Rails, GraphQL, Async demo

This app demonstrates parallel HTTP and ActiveRecord calls using AsyncDataloader. You could use this to speed up GraphQL-Ruby queries that do long-running external service calls.

To enable the AsyncDataloader, use it in the schema configuration:

- use GraphQL::Dataloader
+ use GraphQL::Dataloader::AsyncDataloader
+ # from a development branch of graphql-ruby

Consider a query like this one:

{
  count1: remoteDataloaderCount(set: "zen")
  count2: remoteDataloaderCount(set: "lrw")
  count3: remoteDataloaderCount(set: "m10")
}

Without AsyncDataloader, these operations run in sequence, for example:

Processing by GraphqlController#execute as */*

Started Sources::RemoteSet / zen
Finished Sources::RemoteSet / zen
Started Sources::RemoteSet / lrw
Finished Sources::RemoteSet / lrw
Started Sources::RemoteSet / m10
Finished Sources::RemoteSet / m10

Completed 200 OK in 219ms (Views: 0.2ms | ActiveRecord: 0.0ms | Allocations: 56576)

But, when AsyncDataloader is enabled, the operations run simultaneously:

Processing by GraphqlController#execute as */*

Started Sources::RemoteSet / zen
Started Sources::RemoteSet / lrw
Started Sources::RemoteSet / m10
Finished Sources::RemoteSet / lrw
Finished Sources::RemoteSet / m10
Finished Sources::RemoteSet / zen

Completed 200 OK in 120ms (Views: 0.2ms | ActiveRecord: 0.0ms | Allocations: 88201)

HTTP Calls

def fetch(_ids)
raise(ArgumentError, _ids) unless _ids == [:count]
Rails.logger.debug("Started #{self.class} / #{@set}")
res = Net::HTTP.get(URI("https://api.scryfall.com/cards/search?q=set:#{@set}"))
data = JSON.parse(res)
Rails.logger.debug("Finished #{self.class} / #{@set}")
[data["total_cards"]]
end

ActiveRecord Calls

Note: with sqlite3, these won't be parallel because sqlite only supports one operation at a time.

def fetch(_ids)
raise(ArgumentError, _ids) unless _ids == [:count]
Rails.logger.debug("Started #{self.class} / #{@set}")
count = Card.where(set: @set).count(:*)
Rails.logger.debug("Finished #{self.class} / #{@set}")
[count]
end