window.pipedriveLeadboosterConfig = { base: 'leadbooster-chat.pipedrive.com', companyId: 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', version: 2, } ;(funktion () { var w = vindue if (w.LeadBooster) { console.warn('LeadBooster findes allerede') } 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. Hvad med performance? - The Codest
Codest
  • Om os
  • Serviceydelser
    • Udvikling af software
      • Frontend-udvikling
      • Backend-udvikling
    • Staff Augmentation
      • Frontend-udviklere
      • Backend-udviklere
      • Dataingeniører
      • Cloud-ingeniører
      • QA-ingeniører
      • Andet
    • Det rådgivende
      • Revision og rådgivning
  • Industrier
    • Fintech og bankvirksomhed
    • E-commerce
    • Adtech
    • Sundhedsteknologi
    • Produktion
    • Logistik
    • Biler
    • IOT
  • Værdi for
    • ADMINISTRERENDE DIREKTØR
    • CTO
    • Leder af levering
  • Vores team
  • Casestudier
  • Ved hvordan
    • Blog
    • Møder
    • Webinarer
    • Ressourcer
Karriere Tag kontakt til os
  • Om os
  • Serviceydelser
    • Udvikling af software
      • Frontend-udvikling
      • Backend-udvikling
    • Staff Augmentation
      • Frontend-udviklere
      • Backend-udviklere
      • Dataingeniører
      • Cloud-ingeniører
      • QA-ingeniører
      • Andet
    • Det rådgivende
      • Revision og rådgivning
  • Værdi for
    • ADMINISTRERENDE DIREKTØR
    • CTO
    • Leder af levering
  • Vores team
  • Casestudier
  • Ved hvordan
    • Blog
    • Møder
    • Webinarer
    • Ressourcer
Karriere Tag kontakt til os
Pil tilbage GÅ TILBAGE
2021-06-30
Udvikling af software

GraphQL Ruby. Hvad med performance?

Codest

Tomasz Szkaradek

Udviklingsarkitekt

GraphQL har som enhver anden teknologi sine problemer, og nogle af dem er et direkte resultat af arkitekturen, mens andre er identiske med det, vi ser i enhver anden applikation. Men løsningerne er helt forskellige.

For at præsentere problemet, lad os antage følgende applikationsarkitektur:

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

Og her er den tilsvarende forespørgsel i GraphQL for at downloade data. Vi henter alle links sammen med plakaten og dens links, der er tilføjet til systemet,

{
  allLinks {
    id
    url
    beskrivelse
    createdAt
    postedBy {
      id
      navn
      links {
        id
      }
    }
  }
}

Som vist nedenfor kan vi se det klassiske n + 1-problem med relationer her.

Indlæsning af links (0,4 ms) SELECT "links".* FROM "links" ORDER BY created_at DESC
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Brugerindlæsning (0,3 ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 40], ["LIMIT", 1]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Indlæsning af link (0,3 ms) SELECT "links".* FROM "links" WHERE "links"."user_id" = ?  [["user_id", 40]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 38], ["LIMIT", 1]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Indlæsning af link (0,1 ms) SELECT "links".* FROM "links" WHERE "links"."user_id" = ?  [["user_id", 38]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 36], ["LIMIT", 1]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Indlæsning af link (0,1 ms) SELECT "links".* FROM "links" WHERE "links"."user_id" = ?  [["user_id", 36]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 34], ["LIMIT", 1]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Indlæsning af link (0,2 ms) SELECT "links".* FROM "links" WHERE "links"."user_id" = ?  [["user_id", 34]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 32], ["LIMIT", 1]]

I dette tilfælde fungerer det nøjagtigt som dette stykke Kode:
Link.all.map(&:user).map(&:links).

Det ser ud til, at vi kender løsningen på problemet: Link.includes(user: :links).map(&:user).map(&:links)Men virker det virkelig? Lad os tjekke det ud!

For at bekræfte løsningen ændrede jeg GraphQL forespørgsel til kun at bruge nogle få felter og ingen relation.

{
  allLinks {
    id
    url
    beskrivelse
    oprettetAt
  }
}

Desværre viser resultatet, at vi på trods af manglen på links i forhold til brugeren og deres links stadig vedhæfter disse data til databaseforespørgslen. Desværre er de overflødige, og med en endnu mere kompliceret struktur viser det sig simpelthen at være ineffektivt.

Behandling af GraphqlController#execute som */*
  Parametre: {"query"=>"{n allLinks {n idn urln descriptionn createdAtn }n}", "graphql"=>{"query"=>"{n allLinks {n idn urln descriptionn createdAtn }n}"}}
  Indlæsning af link (0,3 ms) SELECT "links".* FROM "links" ORDER BY created_at DESC
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  User Load (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'
  Link Load (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'
Afsluttet 200 OK på 39 ms (Views: 0,7 ms | ActiveRecord: 0,9 ms | Allokeringer: 8730)

I GraphQLSådanne problemer løses på en anden måde, simpelthen ved at indlæse data i batches, idet man antager, at der er brug for dataene, når de sættes ind i forespørgslen. Det er sådan en doven indlæsning. Et af de mest populære biblioteker er https://github.com/Shopify/graphql-batch/.

Desværre er installationen ikke så problemfri, som det måske ser ud til. Data loaderne er tilgængelige her: https://github.com/Shopify/graphql-batch/tree/master/examples, jeg mener RecordLoader klassen ogAssociationLoader klasse. Lad os på klassisk vis installere gem 'graphql-batch' bibliotek og derefter tilføje det til vores schema samt loadere:

# graphql-ruby/app/graphql/graphql_tutorial_schema.rb
class GraphqlTutorialSchema < GraphQL::Schema
  forespørgsel Types::QueryType
  mutation Typer::MutationType
  brug GraphQL::Batch
  ...
end

Og vores typer:

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

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

# graphql-ruby/app/graphql/types/user_type.rb
modul Typer
  klassen UserType < BaseNode
    field :created_at, DateTimeType, null: false
    field :name, String, null: false
    field :email, String, null: false
    field :votes, [VoteType], null: false
    field :links, [LinkType], null: false

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

Som et resultat af brugen af loaderne samler vi data, og vi søger efter data i to enkle sql-forespørgsler:

Startede POST "/graphql" for ::1 kl. 2021-06-16 22:40:17 +0200
   (0.1ms) SELECT sqlite_version(*)
Behandlet af GraphqlController#execute som */*
  Parametre: {"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 }n}"}}
  Indlæsning af link (0,4 ms) SELECT "links".* FROM "links"
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  User Load (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 Load (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'
Afsluttet 200 OK på 62 ms (Views: 1,3 ms | ActiveRecord: 1,8 ms | Allokeringer: 39887)

Der er også andre løsninger, der løser dette problem, f.eks:

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

Forespørgslernes kompleksitet

N + 1 forespørgsler er ikke alt, i GraphQL kan vi frit overføre de næste attributter. Som standard er den sat til 1. Det kan nogle gange være for meget for serveren, især i en situation, hvor vi frit kan indlejre data. Hvordan håndterer man det? Vi kan begrænse forespørgslens kompleksitet, men for at gøre det skal vi også angive deres omkostninger i attributterne. Som standard er den sat til 1. Vi indstiller denne omkostning ved hjælp af kompleksitet: attribut, hvor vi kan indtaste data: field: links, [LinkType], null: false, complexity: 101. Hvis begrænsningen rent faktisk skal fungere, skal du stadig indføre en maksimumsgrænse i din ordning:

class GraphqlTutorialSchema < GraphQL::Schema
  query Typer::QueryType
  mutation Types::MutationType
  brug GraphQL::Batch
  max_kompleksitet 100
  ...
slut

Sporing

GraphQL behandler forespørgsler forskelligt, og sporing er ikke så enkelt, hvis man sammenligner med, hvad vi kan gøre lokalt. Desværre vil rack mini profiler eller en almindelig SQL-log ikke fortælle os alt og vil ikke pege på, hvilken del af forespørgslen der er ansvarlig for et givet tidsinterval. Når det gælder GraphQL-Ruby, kan vi bruge kommercielle løsninger, der er tilgængelige her: https://graphql-ruby.org/queries/tracingeller prøve at forberede vores egen sporing. Nedenfor ser uddraget ud som en lokal tracer.

# 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
    resultat = block.call
    duration = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start
    observe(platform_key, key, duration)
    resultat
  slut

  def platform_field_key(type, field)
    "graphql.#{type.graphql_name}.#{field.graphql_name}"
  end

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

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

  def observe(platform_key, key, duration)
    return if key == 'authorized'

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

Installationen er også ekstremt enkel, du skal blot inkludere tracer-oplysningerne i skemaet tracer (MyCustomTracer.new) konfiguration. Som i eksemplet nedenfor:

# graphql-ruby/app/graphql/graphql_tutorial_schema.rb
class GraphqlTutorialSchema < GraphQL::Schema
  forespørgsel Types::QueryType
  mutation Typer::MutationType
  brug GraphQL::Batch
  tracer(MyCustomTracer.new)
  ...
slut

Resultatet af en sådan sporing ser sådan ud:

Startede POST "/graphql" for ::1 den 2021-06-17 22:02:44 +0200
   (0.1ms) SELECT sqlite_version(*)
Behandlet af GraphqlController#execute som */*
  Parametre: {"query"=>"{n allLinks {n idn urln descriptionn createdAtn postedBy {n idn namen links {n idn }n }n }n}", "graphql"=>{"query"=>"{n allLinks {n idn urln descriptionn createdAtn postedBy {n idn namen links {n idn }n }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
  Indlæsning af links (0,4 ms) 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
Afsluttet 200 OK på 48 ms (Views: 1,2 ms | ActiveRecord: 2,0 ms | Allokeringer: 40128)

Sammenfatning

GraphQL er ikke længere en ny teknologi, men løsningerne på dens problemer er ikke fuldt ud standardiserede, hvis de ikke er en del af biblioteket. Implementeringen af denne teknologi i projekt giver mange muligheder for at interagere med frontend, og jeg anser det personligt for at være en ny kvalitet i forhold til, hvad REST API tilbyder.

Hvorfor du (sandsynligvis) bør bruge Typescript

Hvordan dræber man ikke et projekt med dårlig kodningspraksis?

Strategier for at hente data i NextJS

Relaterede artikler

Udvikling af software

Byg fremtidssikrede webapps: Indsigt fra The Codest's ekspertteam

Oplev, hvordan The Codest udmærker sig ved at skabe skalerbare, interaktive webapplikationer med banebrydende teknologier, der leverer sømløse brugeroplevelser på tværs af alle platforme. Lær, hvordan vores ekspertise driver digital transformation og...

DENKODEST
Udvikling af software

Top 10 Letlands-baserede softwareudviklingsvirksomheder

Læs om Letlands bedste softwareudviklingsvirksomheder og deres innovative løsninger i vores seneste artikel. Find ud af, hvordan disse teknologiledere kan hjælpe med at løfte din virksomhed.

thecodest
Løsninger til virksomheder og scaleups

Grundlæggende om Java-softwareudvikling: En guide til succesfuld outsourcing

Udforsk denne vigtige guide til vellykket outsourcing af Java-softwareudvikling for at forbedre effektiviteten, få adgang til ekspertise og skabe projektsucces med The Codest.

thecodest
Udvikling af software

Den ultimative guide til outsourcing i Polen

Den voldsomme stigning i outsourcing i Polen er drevet af økonomiske, uddannelsesmæssige og teknologiske fremskridt, der fremmer it-vækst og et erhvervsvenligt klima.

TheCodest
Løsninger til virksomheder og scaleups

Den komplette guide til IT-revisionsværktøjer og -teknikker

IT-revisioner sikrer sikre, effektive og kompatible systemer. Lær mere om deres betydning ved at læse hele artiklen.

Codest
Jakub Jakubowicz CTO og medstifter

Tilmeld dig vores vidensbase, og hold dig opdateret om ekspertisen fra it-sektoren.

    Om os

    The Codest - International softwareudviklingsvirksomhed med tech-hubs i Polen.

    Storbritannien - Hovedkvarter

    • Kontor 303B, 182-184 High Street North E6 2JA
      London, England

    Polen - Lokale teknologiske knudepunkter

    • Fabryczna Office Park, Aleja
      Pokoju 18, 31-564 Kraków
    • Hjerneambassaden, Konstruktorska
      11, 02-673 Warszawa, Polen

      Codest

    • Hjem
    • Om os
    • Serviceydelser
    • Casestudier
    • Ved hvordan
    • Karriere
    • Ordbog

      Serviceydelser

    • Det rådgivende
    • Udvikling af software
    • Backend-udvikling
    • Frontend-udvikling
    • Staff Augmentation
    • Backend-udviklere
    • Cloud-ingeniører
    • Dataingeniører
    • Andet
    • QA-ingeniører

      Ressourcer

    • Fakta og myter om at samarbejde med en ekstern softwareudviklingspartner
    • Fra USA til Europa: Hvorfor beslutter amerikanske startups sig for at flytte til Europa?
    • Sammenligning af Tech Offshore-udviklingsknudepunkter: Tech Offshore Europa (Polen), ASEAN (Filippinerne), Eurasien (Tyrkiet)
    • Hvad er de største udfordringer for CTO'er og CIO'er?
    • Codest
    • Codest
    • Codest
    • Privacy policy
    • Vilkår for brug af hjemmesiden

    Copyright © 2025 af The Codest. Alle rettigheder forbeholdes.

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