diary

I like Hatena Star with a text selection.

2021-01-22

GraphQL Rubyで書かれたサーバーで、edges.nodeを使って問い合わせるよりもnodesを使って問い合わせたほうが速い、しかもそこそこの差が出る、ということを知った。

require 'graphql'

module Types
  class PostType < GraphQL::Schema::Object
    description "A blog post"
    field :id, ID, null: false
    field :title, String, null: false
  end
end

Post = Struct.new(:id, :title, keyword_init: true)
Posts = 10000.times.map { |i| Post.new(id: i.to_s, title: "foo #{i}") }

class QueryType < GraphQL::Schema::Object
  description "The query root of this schema"

  field :posts, Types::PostType.connection_type, null: false

  def posts
    Posts
  end
end

class Schema < GraphQL::Schema
  query QueryType
end


edges_node = -> (first:) do
  Schema.execute(<<~Q, variables: { first: first })
  query Q($first: Int!) {
    posts(first: $first) {
      edges {
        node {
          id
          title
        }
      }
    }
  }
  Q
end

nodes = -> (first:) do
  Schema.execute(<<~Q, variables: { first: first })
  query Q($first: Int!) {
    posts(first: $first) {
      nodes {
        id
        title
      }
    }
  }
  Q
end

require 'benchmark'
Benchmark.bmbm(20) do |x|
  x.report('edges.node - 10') {3000.times{ edges_node.(first: 10) }}
  x.report('nodes - 10')      {3000.times{ nodes.(first: 10) }}

  x.report('edges.node - 100') {1000.times{ edges_node.(first: 100) }}
  x.report('nodes - 100')      {1000.times{ nodes.(first: 100) }}

  x.report('edges.node - 10000') {10.times{ edges_node.(first: 10000) }}
  x.report('nodes - 10000')      {10.times{ nodes.(first: 10000) }}
end
$ ruby bench.rb
Rehearsal --------------------------------------------------------
edges.node - 10        2.735607   0.000000   2.735607 (  2.739255)
nodes - 10             2.132201   0.000000   2.132201 (  2.134951)
edges.node - 100       3.924149   0.000000   3.924149 (  3.929026)
nodes - 100            2.454493   0.000000   2.454493 (  2.457573)
edges.node - 10000     3.625352   0.023201   3.648553 (  3.651399)
nodes - 10000          1.998549   0.000000   1.998549 (  2.000025)
---------------------------------------------- total: 16.893552sec

                           user     system      total        real
edges.node - 10        2.766556   0.000000   2.766556 (  2.770303)
nodes - 10             2.148939   0.000000   2.148939 (  2.152065)
edges.node - 100       3.837464   0.000000   3.837464 (  3.842603)
nodes - 100            2.441282   0.000000   2.441282 (  2.444682)
edges.node - 10000     3.562626   0.003326   3.565952 (  3.568947)
nodes - 10000          1.973643   0.000000   1.973643 (  1.975118)

firstが10の時に1.29倍、100の時に1.57倍、10000の時に1.81倍ぐらいの差がある。

これは適当な例だけど、手元のRails appでActive RecordでDBからレコードを引いてくるようなケースでも、件数によって1.1 ~ 1.3倍ぐらいの速度改善ができた。 もっとDBが支配的だと思っていたから、ここまでGraphQL Ruby側の影響が大きいのかーとびっくり。

単にオブジェクトの生成数が減るのと、GraphQL Rubyが各Nodeに色々やる処理のオーバーヘッドが減るのが大きいのかなあ。