Queue pending Dataloader sources#5666
Conversation
|
Thanks so much for this great speed-up! It makes a lot of sense: previously, each I have one question about the implementation but this looks good as-is. Edit: oops, I see that manual concurrency tests are failing. I can take a look at those now 👀 |
| def initialize(nonblocking: self.class.default_nonblocking, fiber_limit: self.class.default_fiber_limit) | ||
| @source_cache = Hash.new { |h, k| h[k] = {} }.compare_by_identity | ||
| @pending_sources = [] | ||
| @pending_source_set = {}.compare_by_identity |
There was a problem hiding this comment.
It looks like this has is being used as a set. Why not use Ruby's Set instead of a Hash?
|
I pushed a change so that |
|
I identified a couple other issues after pushing that change:
|
e35471f to
cc43ff6
Compare
This changes
GraphQL::Dataloaderto keep a queue of pending sources instead of repeatedly scanning every source in@source_cache.Previously,
run_pending_stepsandspawn_source_fiberwalked the full source cache to find pending sources. That makes scheduling overhead grow with the total number of cached source instances, even when only a few sources are pending. Now sources enqueue themselves when they become pending, and the run loop drains that queue.This also updates
GraphQL::Dataloader::AsyncDataloaderto use the same pending source queue and adds coverage for avoiding full-cache scans and preserving pending sources acrossrun_isolated.Benchmark
This benchmark isolates
GraphQL::Dataloaderscheduling overhead by using a no-opfetch. Real DB/API-backed workloads will see smaller end-to-end gains, but this shows the internal source lookup cost that this PR removes.Ruby:
ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISMpending?checkspending?checksBenchmark script