Skip to content

Source grouping interactions with fiber_limit for async data loaderΒ #5493

@nickstanish

Description

@nickstanish

I recently added async data loader to my rails application as mentioned here https://graphql-ruby.org/dataloader/async_dataloader and set a fiber_limit in hope to restrict how many database connections might be checked out simultaneously (I also increased my pool size after I had some errors where database connections could not be checked out within the limit)

However I could use some guidance on setting the fiber_limit value other than the minimum value of 4. I noticed that it is not 1:1 with the number of dataloader sources that can run, and when set too low it seems to unpredictably change a single batch load into n+1 queries that run serially.

class CustomAsyncDataloader < GraphQL::Dataloader::AsyncDataloader
  def get_fiber_variables
    vars = super
    vars[:connected_to] = {
      role: ActiveRecord::Base.current_role,
      shard: ActiveRecord::Base.current_shard,
      prevent_writes: ActiveRecord::Base.current_preventing_writes,
    }
    vars
  end

  def set_fiber_variables(vars)
    connection_config = vars.delete(:connected_to)
    ActiveRecord::Base.connecting_to(**connection_config)
    super
  end
end
use CustomAsyncDataloader, fiber_limit: ENV.fetch('RAILS_GQL_FIBER_LIMIT', 10).to_i
class Types::ViewerType < Types::BaseObject
    field :example_field, String do
      argument :key, String, required: false
    end

    def example_field(key: 'default')
      dataloader.with(Sources::SlowSource, key).load(SecureRandom.uuid)
    end
end
query {
  viewer {
    default: exampleField

    # simulate loading the same nested source for a connection type
    b1result1: exampleField(key: "batch1")
    b1result2: exampleField(key: "batch1")
    b1result3: exampleField(key: "batch1")
    b1result4: exampleField(key: "batch1")
    b1result5: exampleField(key: "batch1")
    b1result6: exampleField(key: "batch1")
    b1result7: exampleField(key: "batch1")
    b1result8: exampleField(key: "batch1")
    b1result9: exampleField(key: "batch1")
    b1result10: exampleField(key: "batch1")
    b1result11: exampleField(key: "batch1")
    b1result12: exampleField(key: "batch1")
    b1result13: exampleField(key: "batch1")
    b1result14: exampleField(key: "batch1")
    b1result15: exampleField(key: "batch1")
  }
}

I would expect that this performs 2 queries: one for the "default" batch and 1 for "batch1"
But instead we see this:

00:00:35.373351 Rails -- [default] Fetching ids: ["56015c91-35d3-4843-a194-f9c52c24a0ef"]
00:00:35.375195 Rails -- [batch1] Fetching ids: ["64acf353-cf27-4e93-a46d-364196bc5f13", "2521c561-9183-42cc-9ee5-bdefa5862388", "4db4d60e-8639-4978-bf7b-1b64b2894bd7", "98ade717-5e1d-4e77-8272-46edf771d050", "e64be3b6-6c7b-4789-8ec6-156d04cae73e", "bfd59cc0-0f23-4db2-83de-0191f212e220"]
00:00:37.378597 ActiveRecord::Base --    (2004.4ms)  select pg_sleep(2) 
00:00:37.409277 ActiveRecord::Base --    (2033.3ms)  select pg_sleep(2) 
00:00:37.412677 Rails -- [batch1] Fetching ids: ["270cd2e4-eb26-4e21-b9db-1b923058f7c5", "f9c17bdc-47ad-4f97-8b69-dcd142748787", "011c1986-42b9-4b36-9864-a3e16cbe3fe0", "61cb7178-5c30-4fac-a95b-bd534a89a9d2", "74c93d5f-1d3b-4e47-865e-1024ba97a51d", "c11259e8-48b2-4912-a4d9-ff05d89259fa", "080040cd-09b2-456a-8214-a5faac750765"]
00:00:39.442014 ActiveRecord::Base --    (2028.7ms)  select pg_sleep(2) 
00:00:39.444284 Rails -- [batch1] Fetching ids: ["b9caade4-cba1-4e08-a47e-a5ef8325d95b", "6ce90e58-af0c-46a4-b486-5f3bd319d549"]
00:00:41.463919 ActiveRecord::Base --    (2019.0ms)  select pg_sleep(2) 

The first batch for "default" works as expected but the second "batch" is split into 3 different groups which don't complete until the previous completed.

Increasing the fiber_limit makes this work as expected. I'm guessing this needs to be in M*N minimum to work well with M sources that might be batch loading N items?

Using:
graphql-pro (1.29.14)
graphql (2.5.16)
async (2.24.0)
rails (7.2.x)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions