window.pipedriveLeadboosterConfig = { basis: 'leadbooster-chat.pipedrive.com', companyId: 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', versie: 2, } ;(functie () { var w = venster als (w.LeadBooster) { console.warn('LeadBooster bestaat al') } anders { w.LeadBooster = { q: [], on: functie (n, h) { this.q.push({ t: 'o', n: n, h: h }) }, trigger: functie (n) { this.q.push({ t: 't', n: n }) }, } } })() GraphQL Ruby. Hoe zit het met de prestaties? - The Codest
The Codest
  • Over ons
  • Diensten
    • Software Ontwikkeling
      • Frontend ontwikkeling
      • Backend ontwikkeling
    • Staff Augmentation
      • Frontend ontwikkelaars
      • Backend ontwikkelaars
      • Gegevensingenieurs
      • Cloud Ingenieurs
      • QA ingenieurs
      • Andere
    • Het advies
      • Audit & Consulting
  • Industrie
    • Fintech & Bankieren
    • E-commerce
    • Adtech
    • Gezondheidstechnologie
    • Productie
    • Logistiek
    • Automotive
    • IOT
  • Waarde voor
    • CEO
    • CTO
    • Leveringsmanager
  • Ons team
  • Case Studies
  • Weten hoe
    • Blog
    • Ontmoetingen
    • Webinars
    • Bronnen
Carrière Neem contact op
  • Over ons
  • Diensten
    • Software Ontwikkeling
      • Frontend ontwikkeling
      • Backend ontwikkeling
    • Staff Augmentation
      • Frontend ontwikkelaars
      • Backend ontwikkelaars
      • Gegevensingenieurs
      • Cloud Ingenieurs
      • QA ingenieurs
      • Andere
    • Het advies
      • Audit & Consulting
  • Waarde voor
    • CEO
    • CTO
    • Leveringsmanager
  • Ons team
  • Case Studies
  • Weten hoe
    • Blog
    • Ontmoetingen
    • Webinars
    • Bronnen
Carrière Neem contact op
Pijl terug KEREN TERUG
2021-06-30
Software Ontwikkeling

GraphQL Ruby. Hoe zit het met de prestaties?

The Codest

Tomasz Szkaradek

Ontwikkelingsarchitect

GraphQL heeft, net als elke technologie, zijn problemen, waarvan sommige direct voortvloeien uit de architectuur en sommige identiek zijn aan wat we in elke andere toepassing zien. De oplossingen zijn echter totaal verschillend.

Om het probleem voor te stellen, gaan we uit van de volgende applicatiearchitectuur:

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

En hier de overeenkomstige query in GraphQL om de gegevens te downloaden. We halen alle links op, samen met de poster en de links die aan het systeem zijn toegevoegd,

{
  alleLinks {
    id
    url
    beschrijving
    createdAt
    postedBy {
      id
      naam
      links {
        id
      }
    }
  }
}

Zoals hieronder weergegeven, zien we hier het klassieke n + 1 probleem met relaties.

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

In dit geval werkt het precies zoals dit stukje code:
Link.all.map(&:user).map(&:links).

We lijken de oplossing voor het probleem te kennen: Link.includes(user: :links).map(&:user).map(&:links)Maar zal het echt werken? Laten we het uitproberen!

Om de fix te verifiëren, veranderde ik de GraphQL query om slechts een paar velden te gebruiken en geen relatie.

{
  alleLinks {
    id
    url
    beschrijving
    createdAt
  }
}

Helaas laat het resultaat zien dat, ondanks het gebrek aan links met betrekking tot de gebruiker en hun links, we deze gegevens nog steeds koppelen aan een database query. Helaas zijn ze overbodig en, met een nog ingewikkelder structuur, blijkt het gewoon inefficiënt te zijn.

Verwerking door GraphqlController#execute als */*
  Parameters: {"query"=>"{n allLinks {n idn urln descriptionn createdAtn }n}", "graphql"=>{"query"=>"{n allLinks {n idn urln descriptionn createdAtn }n}"}}.
  Link laden (0.3ms) SELECT "links".* FROM "links" ORDER BY created_at DESC
  app/controllers/graphql_controller.rb:5:in `uitvoeren'.
  Laden gebruiker (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 `uitvoeren
  Link laden (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 `uitvoeren
Voltooid 200 OK in 39 ms (Views: 0.7ms | ActiveRecord: 0.9ms | Toewijzingen: 8730)

In GraphQLDergelijke problemen worden op een andere manier opgelost, namelijk door gegevens in batches te laden, ervan uitgaande dat de gegevens nodig zijn op het moment dat ze in de query worden gezet. Het is zo'n luie manier van laden. Een van de populairste bibliotheken is https://github.com/Shopify/graphql-batch/.

Helaas is de installatie ervan niet zo probleemloos als het lijkt. De dataladers zijn hier beschikbaar: https://github.com/Shopify/graphql-batch/tree/master/examples, ik bedoel de RecordLoader klasse en deVerenigingslader klasse. Laten we klassiek de gem 'graphql-batch' bibliotheek en voeg deze dan toe aan ons schema, evenals loaders:

# graphql-ruby/app/graphql/graphql_tutorial_schema.rb
klasse GraphqlTutorialSchema < GraphQL::Schema
  query Types::QueryType
  mutatie Types::MutationType
  gebruik GraphQL::Batch
  ...
einde

En onze types:

# graphql-ruby/app/graphql/types/link_type.rb
module Typen
  klasse LinkType < BaseNode
    veld :created_at, DateTimeType, null: false
    veld :url, String, null: false
    veld :description, String, null: false
    veld :posted_by, UserType, null: false, methode: :user
    veld :votes, [Types::VoteType], null: false

    def gebruiker
      Loaders::RecordLoader.for(User).load(object.user_id)
    einde
  einde
einde

# graphql-ruby/app/graphql/types/user_type.rb
module Typen
  klasse UserType < BaseNode
    veld :created_at, DateTimeType, null: false
    veld :name, String, null: false
    veld :email, String, null: false
    veld :votes, [VoteType], null: false
    veld :links, [LinkType], null: false

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

Als gevolg van het gebruik van de loaders, verzamelen we de gegevens in batches en kunnen we gegevens opvragen in twee eenvoudige sql-queries:

Gestart POST "/graphql" voor ::1 op 2021-06-16 22:40:17 +0200
   (0.1ms) SELECT sqlite_version(*)
Verwerking door GraphqlController#execute als */*
  Parameters: {"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}"}}.
  Link laden (0.4ms) SELECT "links".* FROM "links".
  ↳ app/controllers/graphql_controller.rb:5:in `uitvoeren
  Laden van gebruikers (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'
  Link laden (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'
Voltooid 200 OK in 62 ms (Views: 1.3ms | ActiveRecord: 1.8ms | Toewijzingen: 39887)

Er zijn ook andere oplossingen die dit probleem oplossen, zoals:

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

Complexiteit van query's

N + 1 query's zijn niet alles, in GraphQL kunnen we de volgende attributen vrij overdragen. Standaard staat dit op 1. Dit kan soms te veel zijn voor de server, vooral in een situatie waarin we vrij gegevens kunnen nesten. Hoe gaan we hiermee om? We kunnen de complexiteit van de query beperken, maar daarvoor moeten we ook de kosten ervan in de attributen opgeven. Standaard zijn deze ingesteld op 1. We stellen deze kosten in met de complexiteit: attribuut, waar we gegevens kunnen invoeren: veld: links, [LinkType], nul: onwaar, complexiteit: 101. Als beperking echt moet werken, moet je nog steeds de maximumlimiet invoeren in je regeling:

klasse GraphqlTutorialSchema < GraphQL::Schema
  query Types::QueryType
  mutatie Types::MutationType
  gebruik GraphQL::Batch
  max_complexiteit 100
  ...
einde

Opsporen

GraphQL queries anders verwerkt en traceren niet zo eenvoudig is als je het vergelijkt met wat we lokaal kunnen doen. Helaas zal de rack mini profiler of een gewone SQL log ons niet alles vertellen en zal niet aangeven welk deel van de query verantwoordelijk is voor een bepaalde tijdspanne. In het geval van GraphQL-Ruby kunnen we commerciële oplossingen gebruiken die hier beschikbaar zijn: https://graphql-ruby.org/queries/tracingof proberen onze eigen tracing voor te bereiden. Hieronder ziet het fragment eruit als een lokale tracer.

# lib/my_custom_tracer.rb
klasse 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
    resultaat = block.call
    duur = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start
    observeren(platform_key, key, duration)
    resultaat
  einde

  def platform_veld_key(type, veld)
    "graphql.#{type.graphql_naam}.#{veld.graphql_naam}"
  einde

  def platform_geautoriseerde_sleutel(type)
    "graphql.authorized.#{type.graphql_name}".
  einde

  def platform_resolve_type_key(type)
    "graphql.resolve_type.#{type.graphql_name}"
  einde

  def observeren(platform_key, key, duur)
    return als key == 'authorized

    puts "platform_key: #{platform_key}, key: #{key}, duration: #{(duration * 1000).round(5)} ms".yellow
  einde
einde

Installatie is ook heel eenvoudig, je moet de tracerinformatie opnemen in het schema tracer (MyCustomTracer.new) configuratie. Zoals in het onderstaande voorbeeld:

# graphql-ruby/app/graphql/graphql_tutorial_schema.rb
klasse GraphqlTutorialSchema < GraphQL::Schema
  query Types::QueryType
  mutatie Types::MutationType
  gebruik GraphQL::Batch
  tracer(MyCustomTracer.new)
  ...
einde

De uitvoer van een dergelijke tracing ziet er als volgt uit:

Gestart POST "/graphql" voor ::1 op 2021-06-17 22:02:44 +0200
   (0.1ms) SELECT sqlite_version(*)
Verwerking door GraphqlController#execute als */*
  Parameters: {"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, sleutel: lex, duur: 0.156 ms
platform_key: graphql.parse, sleutel: parse, duur: 0,108 ms
platform_key: graphql.validate, sleutel: validate, duur: 0,537 ms 0,537 ms
platform_key: graphql.analyze_query, sleutel: analyze_query, duur: 0,123 ms
platform_key: graphql.analyze_multiplex, sleutel: analyze_multiplex, duur: 0,159 ms
  Linkbelasting (0,4 ms) SELECT "links".* FROM "links".
  ↳ app/graphql/graphql_tutorial_schema.rb:21:in `platform_trace'
platform_key: graphql.execute_query, sleutel: execute_query, duur: 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, sleutel: execute_query_lazy, duur: 14.12 ms
platform_key: graphql.execute_multiplex, key: execute_multiplex, duration: 31.11 ms
Voltooid 200 OK in 48 ms (Views: 1.2 ms | ActiveRecord: 2.0 ms | Toewijzingen: 40128)

Samenvatting

GraphQL is geen nieuwe technologie meer, maar de oplossingen voor de problemen zijn niet volledig gestandaardiseerd als ze geen deel uitmaken van de bibliotheek. De implementatie van deze technologie in de project geeft veel mogelijkheden voor interactie met de frontend en ik beschouw het persoonlijk als een nieuwe kwaliteit ten opzichte van wat REST API biedt.

Waarom je (waarschijnlijk) Typescript zou moeten gebruiken

Hoe help je een project niet om zeep met slechte codeerpraktijken?

Strategieën voor het ophalen van gegevens in NextJS

Verwante artikelen

Software Ontwikkeling

Bouw Toekomstbestendige Web Apps: Inzichten van The Codest's Expert Team

Ontdek hoe The Codest uitblinkt in het creëren van schaalbare, interactieve webapplicaties met geavanceerde technologieën, het leveren van naadloze gebruikerservaringen op alle platforms. Ontdek hoe onze expertise digitale transformatie en business...

DE BESTE
Software Ontwikkeling

Top 10 in Letland gevestigde bedrijven voor softwareontwikkeling

Lees meer over de beste softwareontwikkelingsbedrijven van Letland en hun innovatieve oplossingen in ons nieuwste artikel. Ontdek hoe deze technologieleiders uw bedrijf kunnen helpen verbeteren.

thecodest
Oplossingen voor ondernemingen en schaalvergroting

Essentiële Java-softwareontwikkeling: Een gids voor succesvol uitbesteden

Verken deze essentiële gids over succesvolle outsourcing Java-softwareontwikkeling om de efficiëntie te verbeteren, toegang te krijgen tot expertise en projectsucces te stimuleren met The Codest.

thecodest
Software Ontwikkeling

De ultieme gids voor outsourcing in Polen

De sterke groei van outsourcing in Polen wordt gedreven door economische, educatieve en technologische vooruitgang, die IT-groei en een bedrijfsvriendelijk klimaat stimuleert.

DeCodest
Oplossingen voor ondernemingen en schaalvergroting

De complete gids voor IT-auditmiddelen en -technieken

IT-audits zorgen voor veilige, efficiënte en compliant systemen. Lees het volledige artikel om meer te weten te komen over het belang ervan.

The Codest
Jakub Jakubowicz CTO & medeoprichter

Abonneer je op onze kennisbank en blijf op de hoogte van de expertise uit de IT-sector.

    Over ons

    The Codest - Internationaal softwareontwikkelingsbedrijf met technische hubs in Polen.

    Verenigd Koninkrijk - Hoofdkantoor

    • Kantoor 303B, 182-184 High Street North E6 2JA
      Londen, Engeland

    Polen - Lokale technologieknooppunten

    • Fabryczna kantorenpark, Aleja
      Pokoju 18, 31-564 Krakau
    • Hersenambassade, Konstruktorska
      11, 02-673 Warschau, Polen

      The Codest

    • Home
    • Over ons
    • Diensten
    • Case Studies
    • Weten hoe
    • Carrière
    • Woordenboek

      Diensten

    • Het advies
    • Software Ontwikkeling
    • Backend ontwikkeling
    • Frontend ontwikkeling
    • Staff Augmentation
    • Backend ontwikkelaars
    • Cloud Ingenieurs
    • Gegevensingenieurs
    • Andere
    • QA ingenieurs

      Bronnen

    • Feiten en fabels over samenwerken met een externe partner voor softwareontwikkeling
    • Van de VS naar Europa: Waarom Amerikaanse startups besluiten naar Europa te verhuizen
    • Tech Offshore Ontwikkelingshubs Vergelijking: Tech Offshore Europa (Polen), ASEAN (Filippijnen), Eurazië (Turkije)
    • Wat zijn de grootste uitdagingen voor CTO's en CIO's?
    • The Codest
    • The Codest
    • The Codest
    • Privacy policy
    • Gebruiksvoorwaarden website

    Copyright © 2025 door The Codest. Alle rechten voorbehouden.

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