window.pipedriveLeadboosterConfig = { base : 'leadbooster-chat.pipedrive.com', companyId : 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', version : 2, } ;(function () { var w = window if (w.LeadBooster) { console.warn('LeadBooster existe déjà') } else { w.LeadBooster = { q : [], on : function (n, h) { this.q.push({ t : 'o', n : n, h : h }) }, trigger : function (n) { this.q.push({ t : 't', n : n }) }, } } })() GraphQL Ruby. Qu'en est-il de la performance ? - The Codest
The Codest
  • A propos de nous
  • Services
    • Développement de logiciels
      • Développement frontal
      • Développement backend
    • Staff Augmentation
      • Développeurs frontaux
      • Développeurs backend
      • Ingénieurs des données
      • Ingénieurs en informatique dématérialisée
      • Ingénieurs AQ
      • Autres
    • Conseil consultatif
      • Audit et conseil
  • Industries
    • Fintech et banque
    • E-commerce
    • Adtech
    • Santé (Healthtech)
    • Fabrication
    • Logistique
    • Automobile
    • IOT
  • Valeur pour
    • CEO
    • CTO
    • Gestionnaire des livraisons
  • Notre équipe
  • Études de cas
  • Savoir comment
    • Blog
    • Rencontres
    • Webinaires
    • Ressources
Carrières Prendre contact
  • A propos de nous
  • Services
    • Développement de logiciels
      • Développement frontal
      • Développement backend
    • Staff Augmentation
      • Développeurs frontaux
      • Développeurs backend
      • Ingénieurs des données
      • Ingénieurs en informatique dématérialisée
      • Ingénieurs AQ
      • Autres
    • Conseil consultatif
      • Audit et conseil
  • Valeur pour
    • CEO
    • CTO
    • Gestionnaire des livraisons
  • Notre équipe
  • Études de cas
  • Savoir comment
    • Blog
    • Rencontres
    • Webinaires
    • Ressources
Carrières Prendre contact
Flèche arrière RETOUR
2021-06-30
Développement de logiciels

GraphQL Ruby. Qu'en est-il des performances ?

The Codest

Tomasz Szkaradek

Architecte de développement

GraphQL, comme toute technologie, a ses problèmes, dont certains résultent directement de l'architecture et d'autres sont identiques à ce que nous voyons dans n'importe quelle autre application. Cependant, les solutions sont complètement différentes.

Pour présenter le problème, supposons l'architecture d'application suivante :

https://drive.google.com/file/d/1N4sWPJSls0S8FFHbpHCUVHBNBpEuSsyz/view

Et voici la requête correspondante en GraphQL pour télécharger les données. Nous récupérons tous les liens, ainsi que l'affiche et ses liens ajoutés au système,

{
  allLinks {
    id
    url
    description
    createdAt
    postedBy {
      id
      nom
      links {
        id
      }
    }
  }
}

Comme le montre l'illustration ci-dessous, on retrouve ici le problème classique des relations n + 1.

Chargement des liens (0.4ms) SELECT "links".* FROM "links" ORDER BY created_at DESC
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Chargement des utilisateurs (0.3ms) SELECT "users".* FROM "users" WHERE "users". "id" = ? LIMIT ?  [["id", 40], ["LIMIT", 1]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Chargement des liens (0.3ms) SELECT "links".* FROM "links" WHERE "links". "user_id" = ?  [["user_id", 40]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Chargement des utilisateurs (0.1ms) SELECT "users".* FROM "users" WHERE "users". "id" = ? LIMIT ?  [["id", 38], ["LIMIT", 1]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Chargement des liens (0.1ms) SELECT "links".* FROM "links" WHERE "links". "user_id" = ?  [["user_id", 38]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Chargement des utilisateurs (0.2ms) SELECT "users".* FROM "users" WHERE "users". "id" = ? LIMIT ?  [["id", 36], ["LIMIT", 1]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Chargement des liens (0.1ms) SELECT "links".* FROM "links" WHERE "links". "user_id" = ?  [["user_id", 36]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Chargement des utilisateurs (0.1ms) SELECT "users".* FROM "users" WHERE "users". "id" = ? LIMIT ?  [["id", 34], ["LIMIT", 1]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Chargement des liens (0.2ms) SELECT "links".* FROM "links" WHERE "links". "user_id" = ?  [["user_id", 34]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Chargement des utilisateurs (0.1ms) SELECT "users".* FROM "users" WHERE "users". "id" = ? LIMIT ?  [["id", 32], ["LIMIT", 1]]

Dans ce cas, cela fonctionne exactement comme ce morceau de code:
Link.all.map(&:user).map(&:links).

Nous semblons connaître la solution au problème : Link.includes(user : :links).map(&:user).map(&:links)mais est-ce que ça marche vraiment ? Vérifions-le !

Pour vérifier la correction, j'ai modifié le fichier GraphQL pour n'utiliser que quelques champs et aucune relation.

{
  allLinks {
    id
    url
    description
    createdAt
  }
}

Malheureusement, le résultat montre que, malgré l'absence de liens en rapport avec l'utilisateur et ses liens, nous attachons toujours ces données à la requête de base de données. Malheureusement, elles sont redondantes et, avec une structure encore plus compliquée, cela s'avère tout simplement inefficace.

Traitement par GraphqlController#execute comme */*
  Paramètres : {"query"=>"{n allLinks {n idn urln descriptionn createdAtn }n}", "graphql"=>{"query"=>"{n allLinks {n idn urln descriptionn createdAtn }n}"}}
  Chargement des liens (0.3ms) SELECT "links".* FROM "links" ORDER BY created_at DESC
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Chargement de l'utilisateur (0.3ms) SELECT "users".* FROM "users" WHERE "users". "id" IN ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) [["id", 40], ["id", 38], ["id", 36], ["id", 34], ["id", 32], ["id", 30], ["id", 28], ["id", 26], ["id", 24], ["id", 22], ["id", 20], ["id", 18], ["id", 16], ["id", 14], ["id", 12], ["id", 10], ["id", 8], ["id", 6], ["id", 4], ["id", 2].
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Chargement des liens (0.3ms) SELECT "links".* FROM "links" WHERE "links". "user_id" IN ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) [["user_id", 2], ["user_id", 4], ["user_id", 6], ["user_id", 8], ["user_id", 10], ["user_id", 12], ["user_id", 14], ["user_id", 16], ["user_id", 18], ["user_id", 20], ["user_id", 22], ["user_id", 24], ["user_id", 26], ["user_id", 28], ["user_id", 30], ["user_id", 32], ["user_id", 34], ["user_id", 36], ["user_id", 38], ["user_id", 40].
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
Terminé 200 OK en 39ms (Vues : 0.7ms | ActiveRecord : 0.9ms | Allocations : 8730)

En GraphQLEn revanche, ces problèmes sont résolus différemment, simplement en chargeant les données par lots, en supposant que les données sont nécessaires au moment où elles sont introduites dans la requête. Il s'agit d'un chargement paresseux. L'une des bibliothèques les plus populaires est https://github.com/Shopify/graphql-batch/.

Malheureusement, son installation n'est pas aussi simple qu'il n'y paraît. Les chargeurs de données sont disponibles ici : https://github.com/Shopify/graphql-batch/tree/master/examples, je veux dire le fichier RecordLoader et la classeAssociationLoader classe. Installons classiquement la classe gem 'graphql-batch' et l'ajouter à notre schéma, ainsi que les chargeurs :

# graphql-ruby/app/graphql/graphql_tutorial_schema.rb
class GraphqlTutorialSchema < GraphQL::Schema
  query Types::QueryType
  mutation Types::MutationType
  use GraphQL::Batch
  ...
fin

Et nos types :

# graphql-ruby/app/graphql/types/link_type.rb
module Types
  classe LinkType < BaseNode
    field :created_at, DateTimeType, null : false
    field :url, String, null : false
    field :description, String, null : false
    champ :posted_by, UserType, null : false, method : :user
    champ :votes, [Types::VoteType], null : false

    def user
      Loaders::RecordLoader.for(User).load(object.user_id)
    end
  end
end

# graphql-ruby/app/graphql/types/user_type.rb
module Types
  classe UserType < BaseNode
    champ :created_at, DateTimeType, null : false
    champ :name, Chaîne, null : false
    champ :email, Chaîne, null : false
    champ :votes, [VoteType], null : false
    champ :links, [LinkType], null : false

    def links
      Loaders::AssociationLoader.for(User, :links).load(object)
    end
  end
end

Grâce à l'utilisation des chargeurs, nous mettons les données en lots et nous les interrogeons au moyen de deux requêtes SQL simples :

Démarrage du POST "/graphql" pour ::1 à 2021-06-16 22:40:17 +0200
   (0.1ms) SELECT sqlite_version(*)
Traitement par GraphqlController#execute en tant que */*
  Paramètres : {"query"=>"{n allLinks {n idn urln descriptionn createdAtn postedBy {n idn namen links {n idn }n }n}", "graphql"=>{"query"=>"{n allLinks {n idn urln descriptionn createdAtn postedBy {n idn namen links {n idn }n }n}"}
  Chargement des liens (0.4ms) SELECT "links".* FROM "links"
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Chargement des utilisateurs (0.9ms) SELECT "users".* FROM "users" WHERE "users". "id" IN ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) [["id", 2], ["id", 4], ["id", 6], ["id", 8], ["id", 10], ["id", 12], ["id", 14], ["id", 16], ["id", 18], ["id", 20], ["id", 22], ["id", 24], ["id", 26], ["id", 28], ["id", 30], ["id", 32], ["id", 34], ["id", 36], ["id", 38], ["id", 40].
  ↳ app/graphql/loaders/record_loader.rb:12:in `perform'
  Chargement des liens (0.5ms) SELECT "links".* FROM "links" WHERE "links". "user_id" IN ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) [["user_id", 2], ["user_id", 4], ["user_id", 6], ["user_id", 8], ["user_id", 10], ["user_id", 12], ["user_id", 14], ["user_id", 16], ["user_id", 18], ["user_id", 20], ["user_id", 22], ["user_id", 24], ["user_id", 26], ["user_id", 28], ["user_id", 30], ["user_id", 32], ["user_id", 34], ["user_id", 36], ["user_id", 38], ["user_id", 40].
  ↳ app/graphql/loaders/association_loader.rb:46:in `preload_association'
Terminé 200 OK en 62ms (Vues : 1.3ms | ActiveRecord : 1.8ms | Allocations : 39887)

Il existe également d'autres solutions pour résoudre ce problème, comme par exemple :

https://github.com/exAspArk/batch-loader#basic-example

Complexité des requêtes

N + 1 requêtes ne sont pas tout, en GraphQL nous pouvons librement reporter les attributs suivants. Par défaut, il est fixé à 1. Cela peut parfois être trop pour le serveur, en particulier dans une situation où nous pouvons librement imbriquer des données. Comment y remédier ? Nous pouvons limiter la complexité de la requête, mais pour ce faire, nous devons également spécifier leur coût dans les attributs. Par défaut, il est fixé à 1. Nous définissons ce coût à l'aide de la fonction complexité : où l'on peut saisir des données : champ : links, [LinkType], null : false, complexity : 101. Pour que la limitation fonctionne réellement, vous devez encore introduire la limite maximale dans votre régime :

class GraphqlTutorialSchema < GraphQL::Schema
  query Types::QueryType
  mutation Types::MutationType
  use GraphQL::Batch
  max_complexity 100
  ...
fin

Traçage

GraphQL traite les requêtes différemment, et le traçage n'est pas si simple si on le compare à ce que l'on peut faire localement. Malheureusement, le rack mini profiler ou un log SQL classique ne nous dira pas tout et ne nous indiquera pas quelle partie de la requête est responsable d'une tranche de temps donnée. Dans le cas de GraphQL-Ruby, nous pouvons utiliser des solutions commerciales disponibles ici : https://graphql-ruby.org/queries/tracingou essayer de préparer notre propre traçage. Ci-dessous, l'extrait ressemble à un traceur local.

# lib/my_custom_tracer.rb
class MyCustomTracer  'graphql.lex',
    'parse' => 'graphql.parse',
    'validate' => 'graphql.validate',
    'analyze_query' => 'graphql.analyze_query',
    'analyze_multiplex' => 'graphql.analyze_multiplex',
    'execute_multiplex' => 'graphql.execute_multiplex',
    'execute_query' => 'graphql.execute_query',
    'execute_query_lazy' => 'graphql.execute_query_lazy'
  }

  def platform_trace(platform_key, key, _data, &block)
    start = ::Process.clock_gettime ::Process::CLOCK_MONOTONIC
    result = block.call
    duration = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start
    observe(clé_plateforme, clé, durée)
    résultat
  fin

  def platform_field_key(type, field)
    "graphql.#{type.nom.graphql_}.#{champ.nom.graphql_}"
  end

  def platform_authorized_key(type)
    "graphql.authorized.#{type.graphql_name}"
  end

  def plateforme_resolve_type_key(type)
    "graphql.resolve_type.#{type.graphql_name}"
  end

  def observe(clé_de_la_plateforme, clé, durée)
    return if key == 'authorized'

    puts "clé_plateforme : #{clé_plateforme}, clé : #{clé}, durée : #{(duration * 1000).round(5)} ms".yellow
  fin
fin

L'installation est également extrêmement simple, il suffit d'inclure les informations relatives au traceur dans le schéma tracer (MyCustomTracer.new) configuration. Comme dans l'exemple ci-dessous :

# graphql-ruby/app/graphql/graphql_tutorial_schema.rb
class GraphqlTutorialSchema < GraphQL::Schema
  query Types::QueryType
  mutation Types::MutationType
  use GraphQL::Batch
  tracer(MyCustomTracer.new)
  ...
fin

Le résultat d'un tel traçage ressemble à ceci :

Démarrage du POST "/graphql" pour ::1 à 2021-06-17 22:02:44 +0200
   (0.1ms) SELECT sqlite_version(*)
Traitement par GraphqlController#execute en tant que */*
  Paramètres : {"query"=>"{n allLinks {n idn urln descriptionn createdAtn postedBy {n idn namen links {n idn }n }n}", "graphql"=>{"query"=>"{n allLinks {n idn urln descriptionn createdAtn postedBy {n idn namen links {n idn }n }n}"}}
platform_key : graphql.lex, key : lex, duration : 0.156 ms
platform_key : graphql.parse, key : parse, duration : 0.108 ms
platform_key : graphql.validate, key : validate, duration : 0.537 ms
platform_key : graphql.analyze_query, key : analyze_query, duration : 0,123 ms
platform_key : graphql.analyze_multiplex, key : analyze_multiplex, duration : 0.159 ms
  Chargement des liens (0.4ms) SELECT "links".* FROM "links"
  ↳ app/graphql/graphql_tutorial_schema.rb:21:in `platform_trace'
platform_key : graphql.execute_query, key : execute_query, duration : 15.562 ms
  ↳ app/graphql/loaders/record_loader.rb:12:in `perform'
  ↳ app/graphql/loaders/association_loader.rb:46:in `preload_association'
platform_key : graphql.execute_query_lazy, key : execute_query_lazy, duration : 14.12 ms
platform_key : graphql.execute_multiplex, key : execute_multiplex, duration : 31.11 ms
Terminé 200 OK en 48ms (Vues : 1.2ms | ActiveRecord : 2.0ms | Allocations : 40128)

Résumé

GraphQL n'est plus une technologie nouvelle, mais les solutions à ses problèmes ne sont pas entièrement normalisées si elles ne font pas partie de la bibliothèque. La mise en œuvre de cette technologie dans la projet offre de nombreuses possibilités d'interaction avec le frontend et je considère personnellement qu'il s'agit d'une nouvelle qualité par rapport à ce qu'offre l'API REST.

Pourquoi vous devriez (probablement) utiliser Typescript

Comment ne pas tuer un projet avec de mauvaises pratiques de codage ?

Stratégies de récupération des données dans NextJS

Articles connexes

Développement de logiciels

Construire des applications web à l'épreuve du temps : les conseils de l'équipe d'experts de The Codest

Découvrez comment The Codest excelle dans la création d'applications web évolutives et interactives à l'aide de technologies de pointe, offrant une expérience utilisateur transparente sur toutes les plateformes. Découvrez comment notre expertise favorise la transformation numérique et la...

LE CODEST
Développement de logiciels

Les 10 premières entreprises de développement de logiciels basées en Lettonie

Découvrez les principales sociétés de développement de logiciels en Lettonie et leurs solutions innovantes dans notre dernier article. Découvrez comment ces leaders de la technologie peuvent vous aider à développer votre entreprise.

thecodest
Solutions pour les entreprises et les grandes entreprises

L'essentiel du développement de logiciels Java : Un guide pour une externalisation réussie

Explorez ce guide essentiel sur le développement réussi de logiciels Java outsourcing pour améliorer l'efficacité, accéder à l'expertise et assurer la réussite des projets avec The Codest.

thecodest
Développement de logiciels

Le guide ultime de l'externalisation en Pologne

L'essor de outsourcing en Pologne est dû aux progrès économiques, éducatifs et technologiques, qui favorisent la croissance des technologies de l'information et un climat propice aux entreprises.

TheCodest
Solutions pour les entreprises et les grandes entreprises

Le guide complet des outils et techniques d'audit informatique

Les audits informatiques garantissent la sécurité, l'efficacité et la conformité des systèmes. Pour en savoir plus sur leur importance, lisez l'article complet.

The Codest
Jakub Jakubowicz CTO & Co-Fondateur

Abonnez-vous à notre base de connaissances et restez au courant de l'expertise du secteur des technologies de l'information.

    A propos de nous

    The Codest - Entreprise internationale de développement de logiciels avec des centres technologiques en Pologne.

    Royaume-Uni - Siège

    • Bureau 303B, 182-184 High Street North E6 2JA
      Londres, Angleterre

    Pologne - Les pôles technologiques locaux

    • Parc de bureaux Fabryczna, Aleja
      Pokoju 18, 31-564 Kraków
    • Brain Embassy, Konstruktorska
      11, 02-673 Varsovie, Pologne

      The Codest

    • Accueil
    • A propos de nous
    • Services
    • Études de cas
    • Savoir comment
    • Carrières
    • Dictionnaire

      Services

    • Conseil consultatif
    • Développement de logiciels
    • Développement backend
    • Développement frontal
    • Staff Augmentation
    • Développeurs backend
    • Ingénieurs en informatique dématérialisée
    • Ingénieurs des données
    • Autres
    • Ingénieurs AQ

      Ressources

    • Faits et mythes concernant la coopération avec un partenaire externe de développement de logiciels
    • Des États-Unis à l'Europe : Pourquoi les startups américaines décident-elles de se délocaliser en Europe ?
    • Comparaison des pôles de développement Tech Offshore : Tech Offshore Europe (Pologne), ASEAN (Philippines), Eurasie (Turquie)
    • Quels sont les principaux défis des CTO et des DSI ?
    • The Codest
    • The Codest
    • The Codest
    • Privacy policy
    • Conditions d'utilisation du site web

    Copyright © 2025 par The Codest. Tous droits réservés.

    fr_FRFrench
    en_USEnglish de_DEGerman sv_SESwedish da_DKDanish nb_NONorwegian fiFinnish pl_PLPolish arArabic it_ITItalian jaJapanese ko_KRKorean es_ESSpanish nl_NLDutch etEstonian elGreek fr_FRFrench